![Arnd Bergmann](/assets/img/avatar_default.png)
gcc-4.4 points out suspicious code in compute_mnt_perms, where the aa_perms structure is only partially initialized before getting returned: security/apparmor/mount.c: In function 'compute_mnt_perms': security/apparmor/mount.c:227: error: 'perms.prompt' is used uninitialized in this function security/apparmor/mount.c:227: error: 'perms.hide' is used uninitialized in this function security/apparmor/mount.c:227: error: 'perms.cond' is used uninitialized in this function security/apparmor/mount.c:227: error: 'perms.complain' is used uninitialized in this function security/apparmor/mount.c:227: error: 'perms.stop' is used uninitialized in this function security/apparmor/mount.c:227: error: 'perms.deny' is used uninitialized in this function Returning or assigning partially initialized structures is a bit tricky, in particular it is explicitly allowed in c99 to assign a partially initialized structure to another, as long as only members are read that have been initialized earlier. Looking at what various compilers do here, the version that produced the warning copied uninitialized stack data, while newer versions (and also clang) either set the other members to zero or don't update the parts of the return buffer that are not modified in the temporary structure, but they never warn about this. In case of apparmor, it seems better to be a little safer and always initialize the aa_perms structure. Most users already do that, this changes the remaining ones, including the one instance that I got the warning for. Fixes: fa488437d0f9 ("apparmor: add mount mediation") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Reviewed-by: Seth Arnold <seth.arnold@canonical.com> Acked-by: Geert Uytterhoeven <geert@linux-m68k.org> Signed-off-by: John Johansen <john.johansen@canonical.com>
696 lines
18 KiB
C
696 lines
18 KiB
C
/*
|
|
* AppArmor security module
|
|
*
|
|
* This file contains AppArmor mediation of files
|
|
*
|
|
* Copyright (C) 1998-2008 Novell/SUSE
|
|
* Copyright 2009-2017 Canonical Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, version 2 of the
|
|
* License.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/namei.h>
|
|
|
|
#include "include/apparmor.h"
|
|
#include "include/audit.h"
|
|
#include "include/context.h"
|
|
#include "include/domain.h"
|
|
#include "include/file.h"
|
|
#include "include/match.h"
|
|
#include "include/mount.h"
|
|
#include "include/path.h"
|
|
#include "include/policy.h"
|
|
|
|
|
|
static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
|
|
{
|
|
if (flags & MS_RDONLY)
|
|
audit_log_format(ab, "ro");
|
|
else
|
|
audit_log_format(ab, "rw");
|
|
if (flags & MS_NOSUID)
|
|
audit_log_format(ab, ", nosuid");
|
|
if (flags & MS_NODEV)
|
|
audit_log_format(ab, ", nodev");
|
|
if (flags & MS_NOEXEC)
|
|
audit_log_format(ab, ", noexec");
|
|
if (flags & MS_SYNCHRONOUS)
|
|
audit_log_format(ab, ", sync");
|
|
if (flags & MS_REMOUNT)
|
|
audit_log_format(ab, ", remount");
|
|
if (flags & MS_MANDLOCK)
|
|
audit_log_format(ab, ", mand");
|
|
if (flags & MS_DIRSYNC)
|
|
audit_log_format(ab, ", dirsync");
|
|
if (flags & MS_NOATIME)
|
|
audit_log_format(ab, ", noatime");
|
|
if (flags & MS_NODIRATIME)
|
|
audit_log_format(ab, ", nodiratime");
|
|
if (flags & MS_BIND)
|
|
audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind");
|
|
if (flags & MS_MOVE)
|
|
audit_log_format(ab, ", move");
|
|
if (flags & MS_SILENT)
|
|
audit_log_format(ab, ", silent");
|
|
if (flags & MS_POSIXACL)
|
|
audit_log_format(ab, ", acl");
|
|
if (flags & MS_UNBINDABLE)
|
|
audit_log_format(ab, flags & MS_REC ? ", runbindable" :
|
|
", unbindable");
|
|
if (flags & MS_PRIVATE)
|
|
audit_log_format(ab, flags & MS_REC ? ", rprivate" :
|
|
", private");
|
|
if (flags & MS_SLAVE)
|
|
audit_log_format(ab, flags & MS_REC ? ", rslave" :
|
|
", slave");
|
|
if (flags & MS_SHARED)
|
|
audit_log_format(ab, flags & MS_REC ? ", rshared" :
|
|
", shared");
|
|
if (flags & MS_RELATIME)
|
|
audit_log_format(ab, ", relatime");
|
|
if (flags & MS_I_VERSION)
|
|
audit_log_format(ab, ", iversion");
|
|
if (flags & MS_STRICTATIME)
|
|
audit_log_format(ab, ", strictatime");
|
|
if (flags & MS_NOUSER)
|
|
audit_log_format(ab, ", nouser");
|
|
}
|
|
|
|
/**
|
|
* audit_cb - call back for mount specific audit fields
|
|
* @ab: audit_buffer (NOT NULL)
|
|
* @va: audit struct to audit values of (NOT NULL)
|
|
*/
|
|
static void audit_cb(struct audit_buffer *ab, void *va)
|
|
{
|
|
struct common_audit_data *sa = va;
|
|
|
|
if (aad(sa)->mnt.type) {
|
|
audit_log_format(ab, " fstype=");
|
|
audit_log_untrustedstring(ab, aad(sa)->mnt.type);
|
|
}
|
|
if (aad(sa)->mnt.src_name) {
|
|
audit_log_format(ab, " srcname=");
|
|
audit_log_untrustedstring(ab, aad(sa)->mnt.src_name);
|
|
}
|
|
if (aad(sa)->mnt.trans) {
|
|
audit_log_format(ab, " trans=");
|
|
audit_log_untrustedstring(ab, aad(sa)->mnt.trans);
|
|
}
|
|
if (aad(sa)->mnt.flags) {
|
|
audit_log_format(ab, " flags=\"");
|
|
audit_mnt_flags(ab, aad(sa)->mnt.flags);
|
|
audit_log_format(ab, "\"");
|
|
}
|
|
if (aad(sa)->mnt.data) {
|
|
audit_log_format(ab, " options=");
|
|
audit_log_untrustedstring(ab, aad(sa)->mnt.data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* audit_mount - handle the auditing of mount operations
|
|
* @profile: the profile being enforced (NOT NULL)
|
|
* @op: operation being mediated (NOT NULL)
|
|
* @name: name of object being mediated (MAYBE NULL)
|
|
* @src_name: src_name of object being mediated (MAYBE_NULL)
|
|
* @type: type of filesystem (MAYBE_NULL)
|
|
* @trans: name of trans (MAYBE NULL)
|
|
* @flags: filesystem idependent mount flags
|
|
* @data: filesystem mount flags
|
|
* @request: permissions requested
|
|
* @perms: the permissions computed for the request (NOT NULL)
|
|
* @info: extra information message (MAYBE NULL)
|
|
* @error: 0 if operation allowed else failure error code
|
|
*
|
|
* Returns: %0 or error on failure
|
|
*/
|
|
static int audit_mount(struct aa_profile *profile, const char *op,
|
|
const char *name, const char *src_name,
|
|
const char *type, const char *trans,
|
|
unsigned long flags, const void *data, u32 request,
|
|
struct aa_perms *perms, const char *info, int error)
|
|
{
|
|
int audit_type = AUDIT_APPARMOR_AUTO;
|
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
|
|
|
|
if (likely(!error)) {
|
|
u32 mask = perms->audit;
|
|
|
|
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
|
mask = 0xffff;
|
|
|
|
/* mask off perms that are not being force audited */
|
|
request &= mask;
|
|
|
|
if (likely(!request))
|
|
return 0;
|
|
audit_type = AUDIT_APPARMOR_AUDIT;
|
|
} else {
|
|
/* only report permissions that were denied */
|
|
request = request & ~perms->allow;
|
|
|
|
if (request & perms->kill)
|
|
audit_type = AUDIT_APPARMOR_KILL;
|
|
|
|
/* quiet known rejects, assumes quiet and kill do not overlap */
|
|
if ((request & perms->quiet) &&
|
|
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
|
AUDIT_MODE(profile) != AUDIT_ALL)
|
|
request &= ~perms->quiet;
|
|
|
|
if (!request)
|
|
return error;
|
|
}
|
|
|
|
aad(&sa)->name = name;
|
|
aad(&sa)->mnt.src_name = src_name;
|
|
aad(&sa)->mnt.type = type;
|
|
aad(&sa)->mnt.trans = trans;
|
|
aad(&sa)->mnt.flags = flags;
|
|
if (data && (perms->audit & AA_AUDIT_DATA))
|
|
aad(&sa)->mnt.data = data;
|
|
aad(&sa)->info = info;
|
|
aad(&sa)->error = error;
|
|
|
|
return aa_audit(audit_type, profile, &sa, audit_cb);
|
|
}
|
|
|
|
/**
|
|
* match_mnt_flags - Do an ordered match on mount flags
|
|
* @dfa: dfa to match against
|
|
* @state: state to start in
|
|
* @flags: mount flags to match against
|
|
*
|
|
* Mount flags are encoded as an ordered match. This is done instead of
|
|
* checking against a simple bitmask, to allow for logical operations
|
|
* on the flags.
|
|
*
|
|
* Returns: next state after flags match
|
|
*/
|
|
static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
|
|
unsigned long flags)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i <= 31 ; ++i) {
|
|
if ((1 << i) & flags)
|
|
state = aa_dfa_next(dfa, state, i + 1);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* compute_mnt_perms - compute mount permission associated with @state
|
|
* @dfa: dfa to match against (NOT NULL)
|
|
* @state: state match finished in
|
|
*
|
|
* Returns: mount permissions
|
|
*/
|
|
static struct aa_perms compute_mnt_perms(struct aa_dfa *dfa,
|
|
unsigned int state)
|
|
{
|
|
struct aa_perms perms = {
|
|
.allow = dfa_user_allow(dfa, state),
|
|
.audit = dfa_user_audit(dfa, state),
|
|
.quiet = dfa_user_quiet(dfa, state),
|
|
.xindex = dfa_user_xindex(dfa, state),
|
|
};
|
|
|
|
return perms;
|
|
}
|
|
|
|
static const char * const mnt_info_table[] = {
|
|
"match succeeded",
|
|
"failed mntpnt match",
|
|
"failed srcname match",
|
|
"failed type match",
|
|
"failed flags match",
|
|
"failed data match"
|
|
};
|
|
|
|
/*
|
|
* Returns 0 on success else element that match failed in, this is the
|
|
* index into the mnt_info_table above
|
|
*/
|
|
static int do_match_mnt(struct aa_dfa *dfa, unsigned int start,
|
|
const char *mntpnt, const char *devname,
|
|
const char *type, unsigned long flags,
|
|
void *data, bool binary, struct aa_perms *perms)
|
|
{
|
|
unsigned int state;
|
|
|
|
AA_BUG(!dfa);
|
|
AA_BUG(!perms);
|
|
|
|
state = aa_dfa_match(dfa, start, mntpnt);
|
|
state = aa_dfa_null_transition(dfa, state);
|
|
if (!state)
|
|
return 1;
|
|
|
|
if (devname)
|
|
state = aa_dfa_match(dfa, state, devname);
|
|
state = aa_dfa_null_transition(dfa, state);
|
|
if (!state)
|
|
return 2;
|
|
|
|
if (type)
|
|
state = aa_dfa_match(dfa, state, type);
|
|
state = aa_dfa_null_transition(dfa, state);
|
|
if (!state)
|
|
return 3;
|
|
|
|
state = match_mnt_flags(dfa, state, flags);
|
|
if (!state)
|
|
return 4;
|
|
*perms = compute_mnt_perms(dfa, state);
|
|
if (perms->allow & AA_MAY_MOUNT)
|
|
return 0;
|
|
|
|
/* only match data if not binary and the DFA flags data is expected */
|
|
if (data && !binary && (perms->allow & AA_MNT_CONT_MATCH)) {
|
|
state = aa_dfa_null_transition(dfa, state);
|
|
if (!state)
|
|
return 4;
|
|
|
|
state = aa_dfa_match(dfa, state, data);
|
|
if (!state)
|
|
return 5;
|
|
*perms = compute_mnt_perms(dfa, state);
|
|
if (perms->allow & AA_MAY_MOUNT)
|
|
return 0;
|
|
}
|
|
|
|
/* failed at end of flags match */
|
|
return 4;
|
|
}
|
|
|
|
|
|
static int path_flags(struct aa_profile *profile, const struct path *path)
|
|
{
|
|
AA_BUG(!profile);
|
|
AA_BUG(!path);
|
|
|
|
return profile->path_flags |
|
|
(S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0);
|
|
}
|
|
|
|
/**
|
|
* match_mnt_path_str - handle path matching for mount
|
|
* @profile: the confining profile
|
|
* @mntpath: for the mntpnt (NOT NULL)
|
|
* @buffer: buffer to be used to lookup mntpath
|
|
* @devnme: string for the devname/src_name (MAY BE NULL OR ERRPTR)
|
|
* @type: string for the dev type (MAYBE NULL)
|
|
* @flags: mount flags to match
|
|
* @data: fs mount data (MAYBE NULL)
|
|
* @binary: whether @data is binary
|
|
* @devinfo: error str if (IS_ERR(@devname))
|
|
*
|
|
* Returns: 0 on success else error
|
|
*/
|
|
static int match_mnt_path_str(struct aa_profile *profile,
|
|
const struct path *mntpath, char *buffer,
|
|
const char *devname, const char *type,
|
|
unsigned long flags, void *data, bool binary,
|
|
const char *devinfo)
|
|
{
|
|
struct aa_perms perms = { };
|
|
const char *mntpnt = NULL, *info = NULL;
|
|
int pos, error;
|
|
|
|
AA_BUG(!profile);
|
|
AA_BUG(!mntpath);
|
|
AA_BUG(!buffer);
|
|
|
|
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
|
|
&mntpnt, &info, profile->disconnected);
|
|
if (error)
|
|
goto audit;
|
|
if (IS_ERR(devname)) {
|
|
error = PTR_ERR(devname);
|
|
devname = NULL;
|
|
info = devinfo;
|
|
goto audit;
|
|
}
|
|
|
|
error = -EACCES;
|
|
pos = do_match_mnt(profile->policy.dfa,
|
|
profile->policy.start[AA_CLASS_MOUNT],
|
|
mntpnt, devname, type, flags, data, binary, &perms);
|
|
if (pos) {
|
|
info = mnt_info_table[pos];
|
|
goto audit;
|
|
}
|
|
error = 0;
|
|
|
|
audit:
|
|
return audit_mount(profile, OP_MOUNT, mntpnt, devname, type, NULL,
|
|
flags, data, AA_MAY_MOUNT, &perms, info, error);
|
|
}
|
|
|
|
/**
|
|
* match_mnt - handle path matching for mount
|
|
* @profile: the confining profile
|
|
* @mntpath: for the mntpnt (NOT NULL)
|
|
* @buffer: buffer to be used to lookup mntpath
|
|
* @devpath: path devname/src_name (MAYBE NULL)
|
|
* @devbuffer: buffer to be used to lookup devname/src_name
|
|
* @type: string for the dev type (MAYBE NULL)
|
|
* @flags: mount flags to match
|
|
* @data: fs mount data (MAYBE NULL)
|
|
* @binary: whether @data is binary
|
|
*
|
|
* Returns: 0 on success else error
|
|
*/
|
|
static int match_mnt(struct aa_profile *profile, const struct path *path,
|
|
char *buffer, struct path *devpath, char *devbuffer,
|
|
const char *type, unsigned long flags, void *data,
|
|
bool binary)
|
|
{
|
|
const char *devname = NULL, *info = NULL;
|
|
int error = -EACCES;
|
|
|
|
AA_BUG(!profile);
|
|
AA_BUG(devpath && !devbuffer);
|
|
|
|
if (devpath) {
|
|
error = aa_path_name(devpath, path_flags(profile, devpath),
|
|
devbuffer, &devname, &info,
|
|
profile->disconnected);
|
|
if (error)
|
|
devname = ERR_PTR(error);
|
|
}
|
|
|
|
return match_mnt_path_str(profile, path, buffer, devname, type, flags,
|
|
data, binary, info);
|
|
}
|
|
|
|
int aa_remount(struct aa_label *label, const struct path *path,
|
|
unsigned long flags, void *data)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL;
|
|
bool binary;
|
|
int error;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!path);
|
|
|
|
binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
|
|
|
|
get_buffers(buffer);
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt(profile, path, buffer, NULL, NULL, NULL,
|
|
flags, data, binary));
|
|
put_buffers(buffer);
|
|
|
|
return error;
|
|
}
|
|
|
|
int aa_bind_mount(struct aa_label *label, const struct path *path,
|
|
const char *dev_name, unsigned long flags)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL, *old_buffer = NULL;
|
|
struct path old_path;
|
|
int error;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!path);
|
|
|
|
if (!dev_name || !*dev_name)
|
|
return -EINVAL;
|
|
|
|
flags &= MS_REC | MS_BIND;
|
|
|
|
error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
|
|
if (error)
|
|
return error;
|
|
|
|
get_buffers(buffer, old_buffer);
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt(profile, path, buffer, &old_path, old_buffer,
|
|
NULL, flags, NULL, false));
|
|
put_buffers(buffer, old_buffer);
|
|
path_put(&old_path);
|
|
|
|
return error;
|
|
}
|
|
|
|
int aa_mount_change_type(struct aa_label *label, const struct path *path,
|
|
unsigned long flags)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL;
|
|
int error;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!path);
|
|
|
|
/* These are the flags allowed by do_change_type() */
|
|
flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
|
|
MS_UNBINDABLE);
|
|
|
|
get_buffers(buffer);
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt(profile, path, buffer, NULL, NULL, NULL,
|
|
flags, NULL, false));
|
|
put_buffers(buffer);
|
|
|
|
return error;
|
|
}
|
|
|
|
int aa_move_mount(struct aa_label *label, const struct path *path,
|
|
const char *orig_name)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL, *old_buffer = NULL;
|
|
struct path old_path;
|
|
int error;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!path);
|
|
|
|
if (!orig_name || !*orig_name)
|
|
return -EINVAL;
|
|
|
|
error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
|
|
if (error)
|
|
return error;
|
|
|
|
get_buffers(buffer, old_buffer);
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt(profile, path, buffer, &old_path, old_buffer,
|
|
NULL, MS_MOVE, NULL, false));
|
|
put_buffers(buffer, old_buffer);
|
|
path_put(&old_path);
|
|
|
|
return error;
|
|
}
|
|
|
|
int aa_new_mount(struct aa_label *label, const char *dev_name,
|
|
const struct path *path, const char *type, unsigned long flags,
|
|
void *data)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL, *dev_buffer = NULL;
|
|
bool binary = true;
|
|
int error;
|
|
int requires_dev = 0;
|
|
struct path tmp_path, *dev_path = NULL;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!path);
|
|
|
|
if (type) {
|
|
struct file_system_type *fstype;
|
|
|
|
fstype = get_fs_type(type);
|
|
if (!fstype)
|
|
return -ENODEV;
|
|
binary = fstype->fs_flags & FS_BINARY_MOUNTDATA;
|
|
requires_dev = fstype->fs_flags & FS_REQUIRES_DEV;
|
|
put_filesystem(fstype);
|
|
|
|
if (requires_dev) {
|
|
if (!dev_name || !*dev_name)
|
|
return -ENOENT;
|
|
|
|
error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path);
|
|
if (error)
|
|
return error;
|
|
dev_path = &tmp_path;
|
|
}
|
|
}
|
|
|
|
get_buffers(buffer, dev_buffer);
|
|
if (dev_path) {
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt(profile, path, buffer, dev_path, dev_buffer,
|
|
type, flags, data, binary));
|
|
} else {
|
|
error = fn_for_each_confined(label, profile,
|
|
match_mnt_path_str(profile, path, buffer, dev_name,
|
|
type, flags, data, binary, NULL));
|
|
}
|
|
put_buffers(buffer, dev_buffer);
|
|
if (dev_path)
|
|
path_put(dev_path);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int profile_umount(struct aa_profile *profile, struct path *path,
|
|
char *buffer)
|
|
{
|
|
struct aa_perms perms = { };
|
|
const char *name = NULL, *info = NULL;
|
|
unsigned int state;
|
|
int error;
|
|
|
|
AA_BUG(!profile);
|
|
AA_BUG(!path);
|
|
|
|
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
|
|
&info, profile->disconnected);
|
|
if (error)
|
|
goto audit;
|
|
|
|
state = aa_dfa_match(profile->policy.dfa,
|
|
profile->policy.start[AA_CLASS_MOUNT],
|
|
name);
|
|
perms = compute_mnt_perms(profile->policy.dfa, state);
|
|
if (AA_MAY_UMOUNT & ~perms.allow)
|
|
error = -EACCES;
|
|
|
|
audit:
|
|
return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
|
|
AA_MAY_UMOUNT, &perms, info, error);
|
|
}
|
|
|
|
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
|
|
{
|
|
struct aa_profile *profile;
|
|
char *buffer = NULL;
|
|
int error;
|
|
struct path path = { .mnt = mnt, .dentry = mnt->mnt_root };
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!mnt);
|
|
|
|
get_buffers(buffer);
|
|
error = fn_for_each_confined(label, profile,
|
|
profile_umount(profile, &path, buffer));
|
|
put_buffers(buffer);
|
|
|
|
return error;
|
|
}
|
|
|
|
/* helper fn for transition on pivotroot
|
|
*
|
|
* Returns: label for transition or ERR_PTR. Does not return NULL
|
|
*/
|
|
static struct aa_label *build_pivotroot(struct aa_profile *profile,
|
|
const struct path *new_path,
|
|
char *new_buffer,
|
|
const struct path *old_path,
|
|
char *old_buffer)
|
|
{
|
|
const char *old_name, *new_name = NULL, *info = NULL;
|
|
const char *trans_name = NULL;
|
|
struct aa_perms perms = { };
|
|
unsigned int state;
|
|
int error;
|
|
|
|
AA_BUG(!profile);
|
|
AA_BUG(!new_path);
|
|
AA_BUG(!old_path);
|
|
|
|
if (profile_unconfined(profile))
|
|
return aa_get_newest_label(&profile->label);
|
|
|
|
error = aa_path_name(old_path, path_flags(profile, old_path),
|
|
old_buffer, &old_name, &info,
|
|
profile->disconnected);
|
|
if (error)
|
|
goto audit;
|
|
error = aa_path_name(new_path, path_flags(profile, new_path),
|
|
new_buffer, &new_name, &info,
|
|
profile->disconnected);
|
|
if (error)
|
|
goto audit;
|
|
|
|
error = -EACCES;
|
|
state = aa_dfa_match(profile->policy.dfa,
|
|
profile->policy.start[AA_CLASS_MOUNT],
|
|
new_name);
|
|
state = aa_dfa_null_transition(profile->policy.dfa, state);
|
|
state = aa_dfa_match(profile->policy.dfa, state, old_name);
|
|
perms = compute_mnt_perms(profile->policy.dfa, state);
|
|
|
|
if (AA_MAY_PIVOTROOT & perms.allow)
|
|
error = 0;
|
|
|
|
audit:
|
|
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
|
|
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
|
|
&perms, info, error);
|
|
if (error)
|
|
return ERR_PTR(error);
|
|
|
|
return aa_get_newest_label(&profile->label);
|
|
}
|
|
|
|
int aa_pivotroot(struct aa_label *label, const struct path *old_path,
|
|
const struct path *new_path)
|
|
{
|
|
struct aa_profile *profile;
|
|
struct aa_label *target = NULL;
|
|
char *old_buffer = NULL, *new_buffer = NULL, *info = NULL;
|
|
int error;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!old_path);
|
|
AA_BUG(!new_path);
|
|
|
|
get_buffers(old_buffer, new_buffer);
|
|
target = fn_label_build(label, profile, GFP_ATOMIC,
|
|
build_pivotroot(profile, new_path, new_buffer,
|
|
old_path, old_buffer));
|
|
if (!target) {
|
|
info = "label build failed";
|
|
error = -ENOMEM;
|
|
goto fail;
|
|
} else if (!IS_ERR(target)) {
|
|
error = aa_replace_current_label(target);
|
|
if (error) {
|
|
/* TODO: audit target */
|
|
aa_put_label(target);
|
|
goto out;
|
|
}
|
|
} else
|
|
/* already audited error */
|
|
error = PTR_ERR(target);
|
|
out:
|
|
put_buffers(old_buffer, new_buffer);
|
|
|
|
return error;
|
|
|
|
fail:
|
|
/* TODO: add back in auditing of new_name and old_name */
|
|
error = fn_for_each(label, profile,
|
|
audit_mount(profile, OP_PIVOTROOT, NULL /*new_name */,
|
|
NULL /* old_name */,
|
|
NULL, NULL,
|
|
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
|
|
error));
|
|
goto out;
|
|
}
|