apparmor: convert to profile block critical sections

There are still a few places where profile replacement fails to update
and a stale profile is used for mediation. Fix this by moving to
accessing the current label through a critical section that will
always ensure mediation is using the current label regardless of
whether the tasks cred has been updated or not.

Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
John Johansen 2017-06-09 02:08:28 -07:00
parent fe864821d5
commit cf797c0e5e
8 changed files with 162 additions and 56 deletions

View File

@ -407,7 +407,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
{ {
ssize_t error; ssize_t error;
struct aa_loaddata *data; struct aa_loaddata *data;
struct aa_profile *profile = aa_current_profile(); struct aa_profile *profile;
profile = begin_current_profile_crit_section();
/* high level check about policy management - fine grained in /* high level check about policy management - fine grained in
* below after unpack * below after unpack
*/ */
@ -421,6 +424,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
error = aa_replace_profiles(ns, profile, mask, data); error = aa_replace_profiles(ns, profile, mask, data);
aa_put_loaddata(data); aa_put_loaddata(data);
} }
end_current_profile_crit_section(profile);
return error; return error;
} }
@ -468,7 +472,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
ssize_t error; ssize_t error;
struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
profile = aa_current_profile(); profile = begin_current_profile_crit_section();
/* high level check about policy management - fine grained in /* high level check about policy management - fine grained in
* below after unpack * below after unpack
*/ */
@ -489,6 +493,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
aa_put_loaddata(data); aa_put_loaddata(data);
} }
out: out:
end_current_profile_crit_section(profile);
aa_put_ns(ns); aa_put_ns(ns);
return error; return error;
} }
@ -667,8 +672,9 @@ static ssize_t query_data(char *buf, size_t buf_len,
if (buf_len < sizeof(bytes) + sizeof(blocks)) if (buf_len < sizeof(bytes) + sizeof(blocks))
return -EINVAL; /* not enough space */ return -EINVAL; /* not enough space */
curr = aa_current_profile(); curr = begin_current_profile_crit_section();
profile = aa_fqlookupn_profile(curr, query, strnlen(query, query_len)); profile = aa_fqlookupn_profile(curr, query, strnlen(query, query_len));
end_current_profile_crit_section(curr);
if (!profile) if (!profile)
return -ENOENT; return -ENOENT;
@ -754,8 +760,9 @@ static ssize_t query_label(char *buf, size_t buf_len,
match_str = label_name + label_name_len + 1; match_str = label_name + label_name_len + 1;
match_len = query_len - label_name_len - 1; match_len = query_len - label_name_len - 1;
curr = aa_current_profile(); curr = begin_current_profile_crit_section();
profile = aa_fqlookupn_profile(curr, label_name, label_name_len); profile = aa_fqlookupn_profile(curr, label_name, label_name_len);
end_current_profile_crit_section(curr);
if (!profile) if (!profile)
return -ENOENT; return -ENOENT;
@ -1094,18 +1101,22 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \
static int seq_ns_level_show(struct seq_file *seq, void *v) static int seq_ns_level_show(struct seq_file *seq, void *v)
{ {
struct aa_ns *ns = aa_current_profile()->ns; struct aa_profile *profile;
seq_printf(seq, "%d\n", ns->level); profile = begin_current_profile_crit_section();
seq_printf(seq, "%d\n", profile->ns->level);
end_current_profile_crit_section(profile);
return 0; return 0;
} }
static int seq_ns_name_show(struct seq_file *seq, void *v) static int seq_ns_name_show(struct seq_file *seq, void *v)
{ {
struct aa_ns *ns = aa_current_profile()->ns; struct aa_profile *profile;
seq_printf(seq, "%s\n", aa_ns_name(ns, ns, true)); profile = begin_current_profile_crit_section();
seq_printf(seq, "%s\n", aa_ns_name(profile->ns, profile->ns, true));
end_current_profile_crit_section(profile);
return 0; return 0;
} }
@ -1530,9 +1541,9 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode)
{ {
struct aa_ns *ns, *parent; struct aa_ns *ns, *parent;
/* TODO: improve permission check */ /* TODO: improve permission check */
struct aa_profile *profile = aa_current_profile(); struct aa_profile *profile = begin_current_profile_crit_section();
int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY);
end_current_profile_crit_section(profile);
if (error) if (error)
return error; return error;
@ -1576,9 +1587,9 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
{ {
struct aa_ns *ns, *parent; struct aa_ns *ns, *parent;
/* TODO: improve permission check */ /* TODO: improve permission check */
struct aa_profile *profile = aa_current_profile(); struct aa_profile *profile = begin_current_profile_crit_section();
int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY);
end_current_profile_crit_section(profile);
if (error) if (error)
return error; return error;
@ -1922,10 +1933,9 @@ static struct aa_profile *next_profile(struct aa_ns *root,
static void *p_start(struct seq_file *f, loff_t *pos) static void *p_start(struct seq_file *f, loff_t *pos)
{ {
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
struct aa_ns *root = aa_current_profile()->ns; struct aa_ns *root = aa_get_current_ns();
loff_t l = *pos; loff_t l = *pos;
f->private = aa_get_ns(root); f->private = root;
/* find the first profile */ /* find the first profile */
mutex_lock(&root->lock); mutex_lock(&root->lock);

View File

@ -79,7 +79,7 @@ struct aa_profile *aa_get_task_profile(struct task_struct *task)
struct aa_profile *p; struct aa_profile *p;
rcu_read_lock(); rcu_read_lock();
p = aa_get_profile(__aa_task_profile(task)); p = aa_get_newest_profile(__aa_task_raw_profile(task));
rcu_read_unlock(); rcu_read_unlock();
return p; return p;

View File

@ -594,7 +594,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* released below */ /* released below */
cred = get_current_cred(); cred = get_current_cred();
ctx = cred_ctx(cred); ctx = cred_ctx(cred);
profile = aa_get_newest_profile(aa_cred_profile(cred)); profile = aa_get_newest_cred_profile(cred);
previous_profile = aa_get_newest_profile(ctx->previous); previous_profile = aa_get_newest_profile(ctx->previous);
if (unconfined(profile)) { if (unconfined(profile)) {
@ -737,7 +737,7 @@ int aa_change_profile(const char *fqname, bool onexec,
} }
cred = get_current_cred(); cred = get_current_cred();
profile = aa_cred_profile(cred); profile = aa_get_newest_cred_profile(cred);
/* /*
* Fail explicitly requested domain transitions if no_new_privs * Fail explicitly requested domain transitions if no_new_privs
@ -795,6 +795,7 @@ audit:
fqname, GLOBAL_ROOT_UID, info, error); fqname, GLOBAL_ROOT_UID, info, error);
aa_put_profile(target); aa_put_profile(target);
aa_put_profile(profile);
put_cred(cred); put_cred(cred);
return error; return error;

View File

@ -56,14 +56,14 @@ struct aa_profile *aa_get_task_profile(struct task_struct *task);
/** /**
* aa_cred_profile - obtain cred's profiles * aa_cred_raw_profile - obtain cred's profiles
* @cred: cred to obtain profiles from (NOT NULL) * @cred: cred to obtain profiles from (NOT NULL)
* *
* Returns: confining profile * Returns: confining profile
* *
* does NOT increment reference count * does NOT increment reference count
*/ */
static inline struct aa_profile *aa_cred_profile(const struct cred *cred) static inline struct aa_profile *aa_cred_raw_profile(const struct cred *cred)
{ {
struct aa_task_ctx *ctx = cred_ctx(cred); struct aa_task_ctx *ctx = cred_ctx(cred);
@ -72,16 +72,28 @@ static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
} }
/** /**
* __aa_task_profile - retrieve another task's profile * aa_get_newest_cred_profile - obtain the newest profile on a cred
* @cred: cred to obtain profile from (NOT NULL)
*
* Returns: newest version of confining profile
*/
static inline
struct aa_profile *aa_get_newest_cred_profile(const struct cred *cred)
{
return aa_get_newest_profile(aa_cred_raw_profile(cred));
}
/**
* __aa_task_raw_profile - retrieve another task's profile
* @task: task to query (NOT NULL) * @task: task to query (NOT NULL)
* *
* Returns: @task's profile without incrementing its ref count * Returns: @task's profile without incrementing its ref count
* *
* If @task != current needs to be called in RCU safe critical section * If @task != current needs to be called in RCU safe critical section
*/ */
static inline struct aa_profile *__aa_task_profile(struct task_struct *task) static inline struct aa_profile *__aa_task_raw_profile(struct task_struct *task)
{ {
return aa_cred_profile(__task_cred(task)); return aa_cred_raw_profile(__task_cred(task));
} }
/** /**
@ -92,50 +104,115 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
*/ */
static inline bool __aa_task_is_confined(struct task_struct *task) static inline bool __aa_task_is_confined(struct task_struct *task)
{ {
return !unconfined(__aa_task_profile(task)); return !unconfined(__aa_task_raw_profile(task));
} }
/** /**
* __aa_current_profile - find the current tasks confining profile * aa_current_raw_profile - find the current tasks confining profile
* *
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL) * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
* *
* This fn will not update the tasks cred to the most up to date version * This fn will not update the tasks cred to the most up to date version
* of the profile so it is safe to call when inside of locks. * of the profile so it is safe to call when inside of locks.
*/ */
static inline struct aa_profile *__aa_current_profile(void) static inline struct aa_profile *aa_current_raw_profile(void)
{ {
return aa_cred_profile(current_cred()); return aa_cred_raw_profile(current_cred());
} }
/** /**
* aa_current_profile - find the current tasks confining profile and do updates * aa_get_current_profile - get the newest version of the current tasks profile
*
* Returns: newest version of confining profile (NOT NULL)
*
* This fn will not update the tasks cred, so it is safe inside of locks
*
* The returned reference must be put with aa_put_profile()
*/
static inline struct aa_profile *aa_get_current_profile(void)
{
struct aa_profile *p = aa_current_raw_profile();
if (profile_is_stale(p))
return aa_get_newest_profile(p);
return aa_get_profile(p);
}
#define __end_current_profile_crit_section(X) \
end_current_profile_crit_section(X)
/**
* end_profile_crit_section - put a reference found with begin_current_profile..
* @profile: profile reference to put
*
* Should only be used with a reference obtained with
* begin_current_profile_crit_section and never used in situations where the
* task cred may be updated
*/
static inline void end_current_profile_crit_section(struct aa_profile *profile)
{
if (profile != aa_current_raw_profile())
aa_put_profile(profile);
}
/**
* __begin_current_profile_crit_section - current's confining profile
* *
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL) * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
* *
* This fn will update the tasks cred structure if the profile has been * safe to call inside locks
* replaced. Not safe to call inside locks *
* The returned reference must be put with __end_current_profile_crit_section()
* This must NOT be used if the task cred could be updated within the
* critical section between __begin_current_profile_crit_section() ..
* __end_current_profile_crit_section()
*/ */
static inline struct aa_profile *aa_current_profile(void) static inline struct aa_profile *__begin_current_profile_crit_section(void)
{ {
const struct aa_task_ctx *ctx = current_ctx(); struct aa_profile *profile = aa_current_raw_profile();
struct aa_profile *profile;
AA_BUG(!ctx || !ctx->profile); if (profile_is_stale(profile))
profile = aa_get_newest_profile(profile);
if (profile_is_stale(ctx->profile)) { return profile;
profile = aa_get_newest_profile(ctx->profile); }
aa_replace_current_profile(profile);
/**
* begin_current_profile_crit_section - current's profile and update if needed
*
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
*
* Not safe to call inside locks
*
* The returned reference must be put with end_current_profile_crit_section()
* This must NOT be used if the task cred could be updated within the
* critical section between begin_current_profile_crit_section() ..
* end_current_profile_crit_section()
*/
static inline struct aa_profile *begin_current_profile_crit_section(void)
{
struct aa_profile *profile = aa_current_raw_profile();
if (profile_is_stale(profile)) {
profile = aa_get_newest_profile(profile);
if (aa_replace_current_profile(profile) == 0)
/* task cred will keep the reference */
aa_put_profile(profile); aa_put_profile(profile);
ctx = current_ctx();
} }
return ctx->profile; return profile;
} }
static inline struct aa_ns *aa_get_current_ns(void) static inline struct aa_ns *aa_get_current_ns(void)
{ {
return aa_get_ns(__aa_current_profile()->ns); struct aa_profile *profile;
struct aa_ns *ns;
profile = __begin_current_profile_crit_section();
ns = aa_get_ns(profile->ns);
__end_current_profile_crit_section(profile);
return ns;
} }
/** /**

View File

@ -120,7 +120,7 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
rcu_read_lock(); rcu_read_lock();
cred = __task_cred(target); cred = __task_cred(target);
profile = aa_cred_profile(cred); profile = aa_get_newest_cred_profile(cred);
/* /*
* cap_capget is stacked ahead of this and will * cap_capget is stacked ahead of this and will
@ -131,6 +131,7 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
*permitted = cap_intersect(*permitted, profile->caps.allow); *permitted = cap_intersect(*permitted, profile->caps.allow);
} }
rcu_read_unlock(); rcu_read_unlock();
aa_put_profile(profile);
return 0; return 0;
} }
@ -141,9 +142,11 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
struct aa_profile *profile; struct aa_profile *profile;
int error = 0; int error = 0;
profile = aa_cred_profile(cred); profile = aa_get_newest_cred_profile(cred);
if (!unconfined(profile)) if (!unconfined(profile))
error = aa_capable(profile, cap, audit); error = aa_capable(profile, cap, audit);
aa_put_profile(profile);
return error; return error;
} }
@ -162,9 +165,10 @@ static int common_perm(const char *op, const struct path *path, u32 mask,
struct aa_profile *profile; struct aa_profile *profile;
int error = 0; int error = 0;
profile = __aa_current_profile(); profile = __begin_current_profile_crit_section();
if (!unconfined(profile)) if (!unconfined(profile))
error = aa_path_perm(op, profile, path, 0, mask, cond); error = aa_path_perm(op, profile, path, 0, mask, cond);
__end_current_profile_crit_section(profile);
return error; return error;
} }
@ -297,9 +301,11 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_
if (!path_mediated_fs(old_dentry)) if (!path_mediated_fs(old_dentry))
return 0; return 0;
profile = aa_current_profile(); profile = begin_current_profile_crit_section();
if (!unconfined(profile)) if (!unconfined(profile))
error = aa_path_link(profile, old_dentry, new_dir, new_dentry); error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
end_current_profile_crit_section(profile);
return error; return error;
} }
@ -312,7 +318,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d
if (!path_mediated_fs(old_dentry)) if (!path_mediated_fs(old_dentry))
return 0; return 0;
profile = aa_current_profile(); profile = begin_current_profile_crit_section();
if (!unconfined(profile)) { if (!unconfined(profile)) {
struct path old_path = { .mnt = old_dir->mnt, struct path old_path = { .mnt = old_dir->mnt,
.dentry = old_dentry }; .dentry = old_dentry };
@ -332,6 +338,8 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d
AA_MAY_CREATE, &cond); AA_MAY_CREATE, &cond);
} }
end_current_profile_crit_section(profile);
return error; return error;
} }
@ -369,7 +377,7 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
return 0; return 0;
} }
profile = aa_cred_profile(cred); profile = aa_get_newest_cred_profile(cred);
if (!unconfined(profile)) { if (!unconfined(profile)) {
struct inode *inode = file_inode(file); struct inode *inode = file_inode(file);
struct path_cond cond = { inode->i_uid, inode->i_mode }; struct path_cond cond = { inode->i_uid, inode->i_mode };
@ -379,18 +387,23 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
/* todo cache full allowed permissions set and state */ /* todo cache full allowed permissions set and state */
fctx->allow = aa_map_file_to_perms(file); fctx->allow = aa_map_file_to_perms(file);
} }
aa_put_profile(profile);
return error; return error;
} }
static int apparmor_file_alloc_security(struct file *file) static int apparmor_file_alloc_security(struct file *file)
{ {
int error = 0;
/* freed by apparmor_file_free_security */ /* freed by apparmor_file_free_security */
struct aa_profile *profile = begin_current_profile_crit_section();
file->f_security = aa_alloc_file_context(GFP_KERNEL); file->f_security = aa_alloc_file_context(GFP_KERNEL);
if (!file->f_security) if (!file->f_security)
return -ENOMEM; return -ENOMEM;
return 0; end_current_profile_crit_section(profile);
return error;
} }
static void apparmor_file_free_security(struct file *file) static void apparmor_file_free_security(struct file *file)
@ -403,16 +416,17 @@ static void apparmor_file_free_security(struct file *file)
static int common_file_perm(const char *op, struct file *file, u32 mask) static int common_file_perm(const char *op, struct file *file, u32 mask)
{ {
struct aa_file_ctx *fctx = file->f_security; struct aa_file_ctx *fctx = file->f_security;
struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); struct aa_profile *profile, *fprofile;
int error = 0; int error = 0;
fprofile = aa_cred_raw_profile(file->f_cred);
AA_BUG(!fprofile); AA_BUG(!fprofile);
if (!file->f_path.mnt || if (!file->f_path.mnt ||
!path_mediated_fs(file->f_path.dentry)) !path_mediated_fs(file->f_path.dentry))
return 0; return 0;
profile = __aa_current_profile(); profile = __begin_current_profile_crit_section();
/* revalidate access, if task is unconfined, or the cached cred /* revalidate access, if task is unconfined, or the cached cred
* doesn't match or if the request is for more permissions than * doesn't match or if the request is for more permissions than
@ -424,6 +438,7 @@ static int common_file_perm(const char *op, struct file *file, u32 mask)
if (!unconfined(profile) && !unconfined(fprofile) && if (!unconfined(profile) && !unconfined(fprofile) &&
((fprofile != profile) || (mask & ~fctx->allow))) ((fprofile != profile) || (mask & ~fctx->allow)))
error = aa_file_perm(op, profile, file, mask); error = aa_file_perm(op, profile, file, mask);
__end_current_profile_crit_section(profile);
return error; return error;
} }
@ -568,10 +583,11 @@ out:
return error; return error;
fail: fail:
aad(&sa)->profile = aa_current_profile(); aad(&sa)->profile = begin_current_profile_crit_section();
aad(&sa)->info = name; aad(&sa)->info = name;
aad(&sa)->error = error = -EINVAL; aad(&sa)->error = error = -EINVAL;
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
end_current_profile_crit_section(aad(&sa)->profile);
goto out; goto out;
} }
@ -581,7 +597,7 @@ fail:
*/ */
static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
{ {
struct aa_profile *profile = __aa_current_profile(); struct aa_profile *profile = aa_current_raw_profile();
struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred);
/* bail out if unconfined or not changing profile */ /* bail out if unconfined or not changing profile */
@ -608,11 +624,12 @@ static void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
static int apparmor_task_setrlimit(struct task_struct *task, static int apparmor_task_setrlimit(struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim) unsigned int resource, struct rlimit *new_rlim)
{ {
struct aa_profile *profile = __aa_current_profile(); struct aa_profile *profile = __begin_current_profile_crit_section();
int error = 0; int error = 0;
if (!unconfined(profile)) if (!unconfined(profile))
error = aa_task_setrlimit(profile, task, resource, new_rlim); error = aa_task_setrlimit(profile, task, resource, new_rlim);
__end_current_profile_crit_section(profile);
return error; return error;
} }

View File

@ -107,7 +107,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
const char *name, const char *info, struct aa_ext *e, const char *name, const char *info, struct aa_ext *e,
int error) int error)
{ {
struct aa_profile *profile = __aa_current_profile(); struct aa_profile *profile = aa_current_raw_profile();
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
if (e) if (e)
aad(&sa)->iface.pos = e->pos - e->start; aad(&sa)->iface.pos = e->pos - e->start;

View File

@ -41,7 +41,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string)
const char *mode_str = aa_profile_mode_names[profile->mode]; const char *mode_str = aa_profile_mode_names[profile->mode];
const char *ns_name = NULL; const char *ns_name = NULL;
struct aa_ns *ns = profile->ns; struct aa_ns *ns = profile->ns;
struct aa_ns *current_ns = __aa_current_profile()->ns; struct aa_ns *current_ns = aa_get_current_ns();
char *s; char *s;
if (!aa_ns_visible(current_ns, ns, true)) if (!aa_ns_visible(current_ns, ns, true))
@ -75,6 +75,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string)
else else
sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
*string = str; *string = str;
aa_put_ns(current_ns);
/* NOTE: len does not include \0 of string, not saved as part of file */ /* NOTE: len does not include \0 of string, not saved as part of file */
return len; return len;

View File

@ -90,7 +90,7 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
int error = 0; int error = 0;
rcu_read_lock(); rcu_read_lock();
task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); task_profile = aa_get_newest_cred_profile((__task_cred(task)));
rcu_read_unlock(); rcu_read_unlock();
/* TODO: extend resource control to handle other (non current) /* TODO: extend resource control to handle other (non current)