apparmor/kernel-patches/for-mainline/profile-namespaces.diff
2007-11-19 23:18:48 +00:00

1158 lines
33 KiB
Diff

Add profile namespace's to AppArmor.
Each namespace defines are set of profiles that will be searched when
looking up profiles. This allows for seperate /bin/foo profiles per
namespace allowing, for different confinement of users, etc.
When profiles are reported it is now in a namespace:profile pair.
User side profiles that begin with / (old style) are assumed to be
in the namespace specified on the command line or fall back to the
"default" namespace.
profile namespace names can be [alpha]([[:alnum:]_]*
currently
- the parser allows setting the namespace a profile should go in using
--namespace name option. This will override the namespace specified
for a profile.
- the parser will accept profiles using a namespace:/profile/name syntax
the specified namespace will be used unless it is overriden by
the -namespace name command line option
- the only way to change names spaces is the use of change_profile and
set_profile. change_profile will allow for pam style change_profile
that drops, users into different "roles".
The goal with namespaces is to provide a generic mechanism for:
- roles
- chroot & fsnames profile sets
- confinement of users (partial default policy)
- user profile sets (requires a little more and user side support)
namespaces will be better supported with policy based transitions when
the named transition patch is finished (sorry I didn't get time to
finish it in hack week, but I may do it one of these weekends).
The best way to play with namespaces current is setprofile
echo "setprofile namespace:/profile" >/proc/<pid>/attr/current
---
security/apparmor/apparmor.h | 59 ++++++---
security/apparmor/inline.h | 35 ++++-
security/apparmor/list.c | 106 ++++++++++++----
security/apparmor/lsm.c | 30 +---
security/apparmor/main.c | 143 ++++++++++++----------
security/apparmor/module_interface.c | 227 +++++++++++++++++++++++++++++++----
security/apparmor/procattr.c | 62 ++++++++-
7 files changed, 504 insertions(+), 158 deletions(-)
--- a/security/apparmor/apparmor.h
+++ b/security/apparmor/apparmor.h
@@ -80,18 +80,40 @@ extern unsigned int apparmor_path_max;
#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+struct aa_profile;
+
+/* struct aa_namespace - namespace for a set of profiles
+ * @name: the name of the namespace
+ * @list: list the namespace is on
+ * @profiles: list of profile in the namespace
+ * @profile_count: the number of profiles in the namespace
+ * @null_complain_profile: special profile used for learning in this namespace
+ * @count: reference count on the namespace
+ * @lock: lock for adding/removing profile to the namespace
+ */
+struct aa_namespace {
+ char *name;
+ struct list_head list;
+ struct list_head profiles;
+ int profile_count;
+ struct aa_profile *null_complain_profile;
+
+ struct kref count;
+ rwlock_t lock;
+};
+
/* struct aa_profile - basic confinement data
* @name: the profiles name
- * @file_rules: dfa containing the profiles file rules
* @list: list this profile is on
+ * @ns: namespace the profile is in
+ * @file_rules: dfa containing the profiles file rules
* @flags: flags controlling profile behavior
* @isstale: flag indicating if profile is stale
* @capabilities: capabilities granted by the process
* @count: reference count of the profile
*
* The AppArmor profile contains the basic confinement data. Each profile
- * has a name and potentially a list of sub profile entries. All non stale
- * profiles are on the profile_list.
+ * has a name, and all nonstale profile are in a profile namespace.
*
* The task_contexts list and the isstale flag are protected by the
* profile lock.
@@ -102,8 +124,10 @@ extern unsigned int apparmor_path_max;
*/
struct aa_profile {
char *name;
- struct aa_dfa *file_rules;
struct list_head list;
+ struct aa_namespace *ns;
+
+ struct aa_dfa *file_rules;
struct {
int complain;
int audit;
@@ -118,8 +142,8 @@ struct aa_profile {
u16 network_families[AF_MAX];
};
-extern struct list_head profile_list;
-extern rwlock_t profile_list_lock;
+extern struct list_head profile_ns_list;
+extern rwlock_t profile_ns_list_lock;
extern struct mutex aa_interface_lock;
/**
@@ -145,7 +169,7 @@ struct aa_task_context {
kernel_cap_t caps_logged;
};
-extern struct aa_profile *null_complain_profile;
+extern struct aa_namespace *default_namespace;
/* aa_audit - AppArmor auditing structure
* Structure is populated by access control code and passed to aa_audit which
@@ -158,6 +182,7 @@ struct aa_audit {
const char *info;
const char *name;
const char *name2;
+ const char *name3;
int requested_mask, denied_mask;
struct iattr *iattr;
pid_t task, parent;
@@ -177,9 +202,8 @@ enum aa_lock_class {
};
/* main.c */
-extern int alloc_null_complain_profile(void);
-extern void free_null_complain_profile(void);
-extern int attach_nullprofile(struct aa_profile *profile);
+extern int alloc_default_namespace(void);
+extern void free_default_namespace(void);
extern int aa_audit_message(struct aa_profile *profile, struct aa_audit *sa,
int type);
void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa);
@@ -210,9 +234,7 @@ extern int aa_clone(struct task_struct *
extern int aa_register(struct linux_binprm *bprm);
extern void aa_release(struct task_struct *task);
extern int aa_change_hat(const char *id, u64 hat_magic);
-extern int aa_change_profile(const char *name);
-extern struct aa_profile *__aa_find_profile(const char *name,
- struct list_head *list);
+extern int aa_change_profile(const char *ns_name, const char *name);
extern struct aa_profile *__aa_replace_profile(struct task_struct *task,
struct aa_profile *profile);
extern struct aa_task_context *lock_task_and_profiles(struct task_struct *task,
@@ -231,12 +253,19 @@ extern int aa_net_perm(struct aa_profile
extern int aa_revalidate_sk(struct sock *sk, char *operation);
/* list.c */
-extern void aa_profilelist_release(void);
+extern struct aa_namespace *__aa_find_namespace(const char *name,
+ struct list_head *list);
+extern struct aa_profile *__aa_find_profile(const char *name,
+ struct list_head *list);
+extern void aa_profile_ns_list_release(void);
/* module_interface.c */
extern ssize_t aa_add_profile(void *, size_t);
extern ssize_t aa_replace_profile(void *, size_t);
-extern ssize_t aa_remove_profile(const char *, size_t);
+extern ssize_t aa_remove_profile(char *, size_t);
+extern struct aa_namespace *alloc_aa_namespace(char *name);
+extern void free_aa_namespace(struct aa_namespace *ns);
+extern void free_aa_namespace_kref(struct kref *kref);
extern struct aa_profile *alloc_aa_profile(void);
extern void free_aa_profile(struct aa_profile *profile);
extern void free_aa_profile_kref(struct kref *kref);
--- a/security/apparmor/inline.h
+++ b/security/apparmor/inline.h
@@ -24,6 +24,32 @@ static inline struct aa_task_context *aa
return (struct aa_task_context *) rcu_dereference(task->security);
}
+static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_get(&(ns->count));
+
+ return ns;
+}
+
+static inline void aa_put_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_put(&ns->count, free_aa_namespace_kref);
+}
+
+
+static inline struct aa_namespace *aa_find_namespace(const char *name)
+{
+ struct aa_namespace *ns = NULL;
+
+ read_lock(&profile_ns_list_lock);
+ ns = aa_get_namespace(__aa_find_namespace(name, &profile_ns_list));
+ read_unlock(&profile_ns_list_lock);
+
+ return ns;
+}
+
/**
* aa_dup_profile - increment refcount on profile @p
* @p: profile
@@ -62,13 +88,14 @@ static inline struct aa_profile *aa_get_
return profile;
}
-static inline struct aa_profile *aa_find_profile(const char *name)
+static inline struct aa_profile *aa_find_profile(struct aa_namespace *ns,
+ const char *name)
{
struct aa_profile *profile = NULL;
- read_lock(&profile_list_lock);
- profile = aa_dup_profile(__aa_find_profile(name, &profile_list));
- read_unlock(&profile_list_lock);
+ read_lock(&ns->lock);
+ profile = aa_dup_profile(__aa_find_profile(name, &ns->profiles));
+ read_unlock(&ns->lock);
return profile;
}
--- a/security/apparmor/list.c
+++ b/security/apparmor/list.c
@@ -13,9 +13,30 @@
#include "apparmor.h"
#include "inline.h"
-/* list of all profiles and lock */
-LIST_HEAD(profile_list);
-rwlock_t profile_list_lock = RW_LOCK_UNLOCKED;
+/* list of profile namespaces and lock */
+LIST_HEAD(profile_ns_list);
+rwlock_t profile_ns_list_lock = RW_LOCK_UNLOCKED;
+
+/**
+ * __aa_find_namespace - look up a profile namespace on the namespace list
+ * @name: name of namespace to find
+ * @head: list to search
+ *
+ * Returns a pointer to the namespace on the list, or NULL if no namespace
+ * called @name exists. The caller must hold the profile_ns_list_lock.
+ */
+struct aa_namespace *__aa_find_namespace(const char *name,
+ struct list_head *head)
+{
+ struct aa_namespace *ns;
+
+ list_for_each_entry(ns, head, list) {
+ if (!strcmp(ns->name, name))
+ return ns;
+ }
+
+ return NULL;
+}
/**
* __aa_find_profile - look up a profile on the profile list
@@ -37,51 +58,92 @@ struct aa_profile *__aa_find_profile(con
return NULL;
}
+static void aa_profile_list_release(struct list_head *head)
+{
+ struct aa_profile *profile, *tmp;
+ list_for_each_entry_safe(profile, tmp, head, list) {
+ /* Remove the profile from each task context it is on. */
+ lock_profile(profile);
+ profile->isstale = 1;
+ aa_unconfine_tasks(profile);
+ list_del_init(&profile->list);
+ unlock_profile(profile);
+ aa_put_profile(profile);
+ }
+}
+
/**
* aa_profilelist_release - Remove all profiles from profile_list
*/
-void aa_profilelist_release(void)
+void aa_profile_ns_list_release(void)
{
- struct aa_profile *p, *tmp;
+ struct aa_namespace *ns, *tmp;
- write_lock(&profile_list_lock);
- list_for_each_entry_safe(p, tmp, &profile_list, list) {
- list_del_init(&p->list);
- aa_put_profile(p);
+ /* Remove and release all the profiles on namespace profile lists. */
+ write_lock(&profile_ns_list_lock);
+ list_for_each_entry_safe(ns, tmp, &profile_ns_list, list) {
+ write_lock(&ns->lock);
+ aa_profile_list_release(&ns->profiles);
+ list_del_init(&ns->list);
+ write_unlock(&ns->lock);
+ aa_put_namespace(ns);
}
- write_unlock(&profile_list_lock);
+ write_unlock(&profile_ns_list_lock);
}
static void *p_start(struct seq_file *f, loff_t *pos)
{
- struct aa_profile *node;
+ struct aa_namespace *ns;
+ struct aa_profile *profile;
loff_t l = *pos;
-
- read_lock(&profile_list_lock);
- list_for_each_entry(node, &profile_list, list)
- if (!l--)
- return node;
+ read_lock(&profile_ns_list_lock);
+ if (l--)
+ return NULL;
+ list_for_each_entry(ns, &profile_ns_list, list) {
+ read_lock(&ns->lock);
+ list_for_each_entry(profile, &ns->profiles, list)
+ return profile;
+ read_unlock(&ns->lock);
+ }
return NULL;
}
static void *p_next(struct seq_file *f, void *p, loff_t *pos)
{
- struct list_head *lh = ((struct aa_profile *)p)->list.next;
+ struct aa_profile *profile = (struct aa_profile *) p;
+ struct list_head *lh = profile->list.next;
+ struct aa_namespace *ns;
(*pos)++;
- return lh == &profile_list ?
- NULL : list_entry(lh, struct aa_profile, list);
+ if (lh != &profile->ns->profiles)
+ return list_entry(lh, struct aa_profile, list);
+
+ lh = profile->ns->list.next;
+ read_unlock(&profile->ns->lock);
+ while (lh != &profile_ns_list) {
+ ns = list_entry(lh, struct aa_namespace, list);
+ read_lock(&ns->lock);
+ list_for_each_entry(profile, &ns->profiles, list)
+ return profile;
+ read_unlock(&ns->lock);
+ lh = ns->list.next;
+ }
+ return NULL;
}
static void p_stop(struct seq_file *f, void *v)
{
- read_unlock(&profile_list_lock);
+ read_unlock(&profile_ns_list_lock);
}
static int seq_show_profile(struct seq_file *f, void *v)
{
struct aa_profile *profile = (struct aa_profile *)v;
- seq_printf(f, "%s (%s)\n", profile->name,
- PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+ if (profile->ns == default_namespace)
+ seq_printf(f, "%s (%s)\n", profile->name,
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+ else
+ seq_printf(f, "%s:%s (%s)\n", profile->ns->name, profile->name,
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
return 0;
}
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -887,12 +887,12 @@ static int __init apparmor_init(void)
goto createfs_out;
}
- if ((error = alloc_null_complain_profile())){
- AA_ERROR("Unable to allocate null complain profile\n");
+ if ((error = alloc_default_namespace())){
+ AA_ERROR("Unable to allocate default profile namespace\n");
goto alloc_out;
}
- if ((error = register_security(&apparmor_ops))) {
+ if ((error = register_security(&apparmor_ops))) {
AA_ERROR("Unable to register AppArmor\n");
goto register_security_out;
}
@@ -905,10 +905,10 @@ static int __init apparmor_init(void)
return error;
register_security_out:
- free_null_complain_profile();
+ free_default_namespace();
alloc_out:
- destroy_apparmorfs();
+ destroy_apparmorfs();
createfs_out:
return error;
@@ -919,26 +919,10 @@ static void __exit apparmor_exit(void)
{
/* Remove and release all the profiles on the profile list. */
mutex_lock(&aa_interface_lock);
- write_lock(&profile_list_lock);
- while (!list_empty(&profile_list)) {
- struct aa_profile *profile =
- list_entry(profile_list.next, struct aa_profile, list);
-
- /* Remove the profile from each task context it is on. */
- lock_profile(profile);
- profile->isstale = 1;
- aa_unconfine_tasks(profile);
- unlock_profile(profile);
-
- /* Release the profile itself. */
- list_del_init(&profile->list);
- aa_put_profile(profile);
- }
- write_unlock(&profile_list_lock);
+ aa_profile_ns_list_release();
/* FIXME: cleanup profiles references on files */
-
- free_null_complain_profile();
+ free_default_namespace();
/*
* Delay for an rcu cycle to make sure that all active task
--- a/security/apparmor/main.c
+++ b/security/apparmor/main.c
@@ -29,17 +29,7 @@ static const char *capability_names[] =
#include "capability_names.h"
};
-/* NULL complain profile
- *
- * Used when in complain mode, to emit Permitting messages for non-existant
- * profiles and hats. This is necessary because of selective mode, in which
- * case we need a complain null_profile and enforce null_profile
- *
- * The null_complain_profile cannot be statically allocated, because it
- * can be associated to files which keep their reference even if apparmor is
- * unloaded
- */
-struct aa_profile *null_complain_profile;
+struct aa_namespace *default_namespace;
/**
* aa_file_denied - check for @mask access on a file
@@ -209,42 +199,34 @@ static int aa_perm_dentry(struct aa_prof
return error;
}
-/**
- * alloc_null_complain_profile - Allocate the global null_complain_profile.
- *
- * Return %0 (success) or error (-%ENOMEM)
- */
-int alloc_null_complain_profile(void)
+int alloc_default_namespace(void)
{
- null_complain_profile = alloc_aa_profile();
- if (!null_complain_profile)
- goto fail;
-
- null_complain_profile->name =
- kstrdup("null-complain-profile", GFP_KERNEL);
-
- if (!null_complain_profile->name)
- goto fail;
+ struct aa_namespace *ns;
+ char *name = kstrdup("default", GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+ ns = alloc_aa_namespace(name);
+ if (!ns) {
+ kfree(name);
+ return -ENOMEM;
+ }
- null_complain_profile->flags.complain = 1;
+ write_lock(&profile_ns_list_lock);
+ default_namespace = ns;
+ aa_get_namespace(ns);
+ list_add(&ns->list, &profile_ns_list);
+ write_unlock(&profile_ns_list_lock);
return 0;
-
-fail:
- /* free_aa_profile is safe for freeing partially constructed objects */
- free_aa_profile(null_complain_profile);
- null_complain_profile = NULL;
-
- return -ENOMEM;
}
-/**
- * free_null_complain_profile - Free null profiles
- */
-void free_null_complain_profile(void)
+void free_default_namespace(void)
{
- aa_put_profile(null_complain_profile);
- null_complain_profile = NULL;
+ write_lock(&profile_ns_list_lock);
+ list_del_init(&default_namespace->list);
+ write_unlock(&profile_ns_list_lock);
+ aa_put_namespace(default_namespace);
+ default_namespace = NULL;
}
static void aa_audit_file_mask(struct audit_buffer *ab, const char *name,
@@ -394,6 +376,9 @@ static int aa_audit_base(struct aa_profi
if (profile) {
audit_log_format(ab, " profile=");
audit_log_untrustedstring(ab, profile->name);
+
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab, profile->ns->name);
}
audit_log_end(ab);
@@ -801,7 +786,7 @@ repeat:
unlock_profile(profile);
if (APPARMOR_COMPLAIN(child_cxt) &&
- profile == null_complain_profile) {
+ profile == profile->ns->null_complain_profile) {
struct aa_audit sa;
memset(&sa, 0, sizeof(sa));
sa.operation = "clone";
@@ -823,7 +808,11 @@ aa_register_find(struct aa_profile *prof
struct aa_profile *new_profile;
/* Locate new profile */
- new_profile = aa_find_profile(name);
+ if (profile)
+ new_profile = aa_find_profile(profile->ns, name);
+ else
+ new_profile = aa_find_profile(default_namespace, name);
+
if (new_profile) {
AA_DEBUG("%s: setting profile %s\n",
__FUNCTION__, new_profile->name);
@@ -832,7 +821,8 @@ aa_register_find(struct aa_profile *prof
sa->denied_mask = MAY_EXEC;
if (complain) {
aa_audit_hint(profile, sa);
- new_profile = aa_dup_profile(null_complain_profile);
+ new_profile =
+ aa_dup_profile(profile->ns->null_complain_profile);
} else {
aa_audit_reject(profile, sa);
return ERR_PTR(-EACCES); /* was -EPERM */
@@ -922,7 +912,8 @@ repeat:
* describing mode to execute image in.
* Drop into null-profile (disabling secure exec).
*/
- new_profile = aa_dup_profile(null_complain_profile);
+ new_profile =
+ aa_dup_profile(profile->ns->null_complain_profile);
exec_mode |= AA_EXEC_UNSAFE;
} else {
sa.denied_mask = MAY_EXEC;
@@ -974,7 +965,8 @@ repeat:
((unsigned long)bprm->security | bprm_flags);
}
- if (complain && new_profile == null_complain_profile) {
+ if (complain && new_profile &&
+ new_profile == new_profile->ns->null_complain_profile) {
sa.requested_mask = 0;
sa.name = NULL;
sa.info = "set profile";
@@ -1034,7 +1026,8 @@ repeat:
}
}
-static int do_change_profile(struct aa_profile *expected, const char *name,
+static int do_change_profile(struct aa_profile *expected,
+ struct aa_namespace *ns, const char *name,
u64 cookie, int restore, struct aa_audit *sa)
{
struct aa_profile *new_profile = NULL, *old_profile = NULL,
@@ -1048,11 +1041,11 @@ static int do_change_profile(struct aa_p
if (!new_cxt)
return -ENOMEM;
- new_profile = aa_find_profile(name);
+ new_profile = aa_find_profile(ns, name);
if (!new_profile && !restore) {
if (!PROFILE_COMPLAIN(expected))
return -ENOENT;
- new_profile = aa_dup_profile(null_complain_profile);
+ new_profile = aa_dup_profile(ns->null_complain_profile);
}
cxt = lock_task_and_profiles(current, new_profile);
@@ -1087,7 +1080,7 @@ static int do_change_profile(struct aa_p
goto out;
}
- if (new_profile == null_complain_profile)
+ if (new_profile == ns->null_complain_profile)
aa_audit_hint(cxt->profile, sa);
if (APPARMOR_AUDIT(cxt))
@@ -1111,19 +1104,24 @@ out:
/**
* aa_change_profile - perform a one-way profile transition
+ * @ns_name: name of the profile namespace to change to
* @name: name of profile to change to
- *
* Change to new profile @name. Unlike with hats, there is no way
* to change back.
*
* Returns %0 on success, error otherwise.
*/
-int aa_change_profile(const char *name)
+int aa_change_profile(const char *ns_name, const char *name)
{
struct aa_task_context *cxt;
struct aa_profile *profile;
+ struct aa_namespace *ns = NULL;
struct aa_audit sa;
- int error = 0;
+ unsigned int state;
+ int error = -EINVAL;
+
+ if (!name)
+ return -EINVAL;
memset(&sa, 0, sizeof(sa));
sa.gfp_mask = GFP_ATOMIC;
@@ -1139,17 +1137,34 @@ repeat:
profile = aa_dup_profile(cxt->profile);
task_unlock(current);
- if (name) {
- if (profile != null_complain_profile &&
- !(aa_match(profile->file_rules, name) &
- AA_CHANGE_PROFILE)) {
+ if (ns_name)
+ ns = aa_find_namespace(ns_name);
+ else
+ ns = aa_get_namespace(profile->ns);
+ if (!ns) {
+ aa_put_profile(profile);
+ return -ENOENT;
+ }
+
+ if (PROFILE_COMPLAIN(profile) ||
+ (ns == profile->ns &&
+ (aa_match(profile->file_rules, name) & AA_CHANGE_PROFILE)))
+ error = do_change_profile(profile, ns, name, 0, 0, &sa);
+ else {
+ /* check for a rule with a namespace prepended */
+ aa_match_state(profile->file_rules, DFA_START, ns->name,
+ &state);
+ state = aa_dfa_null_transition(profile->file_rules, state);
+ if ((aa_match_state(profile->file_rules, state, name, NULL) &
+ AA_CHANGE_PROFILE))
+ error = do_change_profile(profile, ns, name, 0, 0,
+ &sa);
+ else
/* no permission to transition to profile @name */
- aa_put_profile(profile);
- return -EACCES;
- }
- error = do_change_profile(profile, name, 0, 0, &sa);
+ error = -EACCES;
}
+ aa_put_namespace(ns);
aa_put_profile(profile);
if (error == -ESTALE)
goto repeat;
@@ -1204,11 +1219,13 @@ repeat:
goto out;
}
sprintf(name, "%s//%s", profile_name, hat_name);
- error = do_change_profile(profile, name, cookie, 0, &sa);
+ error = do_change_profile(profile, profile->ns, name, cookie,
+ 0, &sa);
kfree(name);
} else if (previous_profile)
- error = do_change_profile(profile, previous_profile->name,
- cookie, 1, &sa);
+ error = do_change_profile(profile, profile->ns,
+ previous_profile->name, cookie, 1,
+ &sa);
/* else ignore restores when there is no saved profile */
out:
--- a/security/apparmor/module_interface.c
+++ b/security/apparmor/module_interface.c
@@ -58,6 +58,7 @@ struct aa_ext {
void *end;
void *pos; /* pointer to current position in the buffer */
u32 version;
+ char *ns_name;
};
static inline int aa_inbounds(struct aa_ext *e, size_t size)
@@ -387,6 +388,12 @@ static int aa_verify_header(struct aa_ex
aa_audit_status(NULL, &sa);
return -EPROTONOSUPPORT;
}
+
+ /* read the namespace if present */
+ if (!aa_is_dynstring(e, &e->ns_name, "namespace")) {
+ e->ns_name = NULL;
+ }
+
return 0;
}
@@ -398,10 +405,12 @@ static int aa_verify_header(struct aa_ex
ssize_t aa_add_profile(void *data, size_t size)
{
struct aa_profile *profile = NULL;
+ struct aa_namespace *ns = NULL;
struct aa_ext e = {
.start = data,
.end = data + size,
- .pos = data
+ .pos = data,
+ .ns_name = NULL
};
ssize_t error = aa_verify_header(&e, "profile_load");
if (error)
@@ -412,16 +421,42 @@ ssize_t aa_add_profile(void *data, size_
return PTR_ERR(profile);
mutex_lock(&aa_interface_lock);
- write_lock(&profile_list_lock);
- if (__aa_find_profile(profile->name, &profile_list)) {
+ write_lock(&profile_ns_list_lock);
+ if (e.ns_name)
+ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
+ else
+ ns = default_namespace;
+ if (!ns) {
+ struct aa_namespace *new_ns;
+ write_unlock(&profile_ns_list_lock);
+ new_ns = alloc_aa_namespace(e.ns_name);
+ if (!new_ns) {
+ mutex_unlock(&aa_interface_lock);
+ return -ENOMEM;
+ }
+ write_lock(&profile_ns_list_lock);
+ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
+ if (!ns) {
+ list_add(&new_ns->list, &profile_ns_list);
+ ns = new_ns;
+ } else
+ free_aa_namespace(new_ns);
+ }
+
+ write_lock(&ns->lock);
+ if (__aa_find_profile(profile->name, &ns->profiles)) {
/* A profile with this name exists already. */
- write_unlock(&profile_list_lock);
+ write_unlock(&ns->lock);
+ write_unlock(&profile_ns_list_lock);
mutex_unlock(&aa_interface_lock);
aa_put_profile(profile);
return -EEXIST;
}
- list_add(&profile->list, &profile_list);
- write_unlock(&profile_list_lock);
+ profile->ns = aa_get_namespace(ns);
+ ns->profile_count++;
+ list_add(&profile->list, &ns->profiles);
+ write_unlock(&ns->lock);
+ write_unlock(&profile_ns_list_lock);
mutex_unlock(&aa_interface_lock);
return size;
@@ -461,12 +496,15 @@ static inline void task_replace(struct t
ssize_t aa_replace_profile(void *udata, size_t size)
{
struct aa_profile *old_profile, *new_profile;
+ struct aa_namespace *ns;
struct aa_task_context *new_cxt;
struct aa_ext e = {
.start = udata,
.end = udata + size,
- .pos = udata
+ .pos = udata,
+ .ns_name = NULL
};
+
ssize_t error = aa_verify_header(&e, "profile_replace");
if (error)
return error;
@@ -476,16 +514,42 @@ ssize_t aa_replace_profile(void *udata,
return PTR_ERR(new_profile);
mutex_lock(&aa_interface_lock);
- write_lock(&profile_list_lock);
- old_profile = __aa_find_profile(new_profile->name, &profile_list);
+ write_lock(&profile_ns_list_lock);
+ if (e.ns_name)
+ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
+ else
+ ns = default_namespace;
+ if (!ns) {
+ struct aa_namespace *new_ns;
+ write_unlock(&profile_ns_list_lock);
+ new_ns = alloc_aa_namespace(e.ns_name);
+ if (!new_ns) {
+ mutex_unlock(&aa_interface_lock);
+ return -ENOMEM;
+ }
+ write_lock(&profile_ns_list_lock);
+ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
+ if (!ns) {
+ list_add(&new_ns->list, &profile_ns_list);
+ ns = new_ns;
+ } else
+ free_aa_namespace(new_ns);
+ }
+
+ write_lock(&ns->lock);
+ old_profile = __aa_find_profile(new_profile->name, &ns->profiles);
if (old_profile) {
lock_profile(old_profile);
old_profile->isstale = 1;
- unlock_profile(old_profile);
list_del_init(&old_profile->list);
+ unlock_profile(old_profile);
+ ns->profile_count--;
}
- list_add(&new_profile->list, &profile_list);
- write_unlock(&profile_list_lock);
+ new_profile->ns = aa_get_namespace(ns);
+ ns->profile_count++;
+ list_add(&new_profile->list, &ns->profiles);
+ write_unlock(&ns->lock);
+ write_unlock(&profile_ns_list_lock);
if (!old_profile)
goto out;
@@ -530,32 +594,148 @@ out:
* remove a profile from the profile list and all aa_task_context references
* to said profile.
*/
-ssize_t aa_remove_profile(const char *name, size_t size)
+ssize_t aa_remove_profile(char *name, size_t size)
{
+ struct aa_namespace *ns;
struct aa_profile *profile;
mutex_lock(&aa_interface_lock);
- write_lock(&profile_list_lock);
- profile = __aa_find_profile(name, &profile_list);
+ write_lock(&profile_ns_list_lock);
+
+ if (name[0] == '/') {
+ ns = default_namespace;
+ } else {
+ char *split = strchr(name, ':');
+ if (!split)
+ goto noent;
+ *split = 0;
+ ns = __aa_find_namespace(name, &profile_ns_list);
+ name = split + 1;
+ }
+
+ if (!ns)
+ goto noent;
+ write_lock(&ns->lock);
+ profile = __aa_find_profile(name, &ns->profiles);
if (!profile) {
- write_unlock(&profile_list_lock);
- mutex_unlock(&aa_interface_lock);
- return -ENOENT;
+ write_unlock(&ns->lock);
+ goto noent;
}
/* Remove the profile from each task context it is on. */
lock_profile(profile);
profile->isstale = 1;
aa_unconfine_tasks(profile);
+ list_del_init(&profile->list);
+ ns->profile_count--;
unlock_profile(profile);
-
/* Release the profile itself. */
- list_del_init(&profile->list);
- aa_put_profile(profile);
- write_unlock(&profile_list_lock);
+ write_unlock(&ns->lock);
+ /* check to see if the namespace has become stale */
+ if (ns != default_namespace && ns->profile_count == 0) {
+ list_del_init(&ns->list);
+ aa_put_namespace(ns);
+ }
+ write_unlock(&profile_ns_list_lock);
mutex_unlock(&aa_interface_lock);
+ aa_put_profile(profile);
return size;
+
+noent:
+ write_unlock(&profile_ns_list_lock);
+ mutex_unlock(&aa_interface_lock);
+ return -ENOENT;
+}
+
+/**
+ * free_aa_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
+ * @kr: kref callback for freeing of a namespace
+ */
+void free_aa_namespace_kref(struct kref *kref)
+{
+ struct aa_namespace *ns=container_of(kref, struct aa_namespace, count);
+
+ free_aa_namespace(ns);
+}
+
+/**
+ * alloc_aa_namespace - allocate, initialize and return a new namespace
+ * @name: a preallocated name
+ * Returns NULL on failure.
+ */
+struct aa_namespace *alloc_aa_namespace(char *name)
+{
+ struct aa_namespace *ns;
+
+ ns = kzalloc(sizeof(*ns), GFP_KERNEL);
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns);
+ if (ns) {
+ ns->name = name;
+ INIT_LIST_HEAD(&ns->list);
+ INIT_LIST_HEAD(&ns->profiles);
+ kref_init(&ns->count);
+ rwlock_init(&ns->lock);
+
+ ns->null_complain_profile = alloc_aa_profile();
+ if (!ns->null_complain_profile) {
+ if (!name)
+ kfree(ns->name);
+ kfree(ns);
+ return NULL;
+ }
+ ns->null_complain_profile->name =
+ kstrdup("null-complain-profile", GFP_KERNEL);
+ if (!ns->null_complain_profile->name) {
+ free_aa_profile(ns->null_complain_profile);
+ if (!name)
+ kfree(ns->name);
+ kfree(ns);
+ return NULL;
+ }
+ ns->null_complain_profile->flags.complain = 1;
+ /* null_complain_profile doesn't contribute to ns ref count */
+ ns->null_complain_profile->ns = ns;
+ }
+ return ns;
+}
+
+/**
+ * free_aa_namespace - free a profile namespace
+ * @namespace: the namespace to free
+ *
+ * Free a namespace. All references to the namespace must have been put.
+ * If the namespace was referenced by a profile confining a task,
+ * free_aa_namespace will be called indirectly (through free_aa_profile)
+ * from an rcu callback routine, so we must not sleep here.
+ */
+void free_aa_namespace(struct aa_namespace *ns)
+{
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns);
+
+ if (!ns)
+ return;
+
+ /* namespace still contains profiles -- invalid */
+ if (!list_empty(&ns->profiles)) {
+ AA_ERROR("%s: internal error, "
+ "namespace '%s' still contains profiles\n",
+ __FUNCTION__,
+ ns->name);
+ BUG();
+ }
+ if (!list_empty(&ns->list)) {
+ AA_ERROR("%s: internal error, "
+ "namespace '%s' still on list\n",
+ __FUNCTION__,
+ ns->name);
+ BUG();
+ }
+ /* null_complain_profile doesn't contribute to ns ref counting */
+ ns->null_complain_profile->ns = NULL;
+ aa_put_profile(ns->null_complain_profile);
+ kfree(ns->name);
+ kfree(ns);
}
/**
@@ -605,7 +785,7 @@ void free_aa_profile(struct aa_profile *
if (!profile)
return;
- /* profile is still on global profile list -- invalid */
+ /* profile is still on profile namespace list -- invalid */
if (!list_empty(&profile->list)) {
AA_ERROR("%s: internal error, "
"profile '%s' still on global list\n",
@@ -613,6 +793,7 @@ void free_aa_profile(struct aa_profile *
profile->name);
BUG();
}
+ aa_put_namespace(profile->ns);
aa_match_free(profile->file_rules);
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -19,15 +19,22 @@ int aa_getprocattr(struct aa_profile *pr
if (profile) {
const char *mode_str = PROFILE_COMPLAIN(profile) ?
" (complain)" : " (enforce)";
- int mode_len, name_len;
+ int mode_len, name_len, ns_len = 0;
mode_len = strlen(mode_str);
name_len = strlen(profile->name);
- *len = mode_len + name_len + 1;
+ if (profile->ns != default_namespace)
+ ns_len = strlen(profile->ns->name) + 1;
+ *len = mode_len + ns_len + name_len + 1;
str = kmalloc(*len, GFP_ATOMIC);
if (!str)
return -ENOMEM;
+ if (ns_len) {
+ memcpy(str, profile->ns->name, ns_len - 1);
+ str += ns_len - 1;
+ *str++ = ':';
+ }
memcpy(str, profile->name, name_len);
str += name_len;
memcpy(str, mode_str, mode_len);
@@ -87,13 +94,26 @@ int aa_setprocattr_changehat(char *args)
int aa_setprocattr_changeprofile(char *args)
{
- return aa_change_profile(args);
+ char *name = args, *ns_name = NULL;
+
+ if (name[0] != '/') {
+ char *split = strchr(name, ':');
+ if (split) {
+ *split = 0;
+ ns_name = name;
+ name = split + 1;
+ }
+ }
+
+ return aa_change_profile(ns_name, name);
}
int aa_setprocattr_setprofile(struct task_struct *task, char *args)
{
struct aa_profile *old_profile, *new_profile;
+ struct aa_namespace *ns;
struct aa_audit sa;
+ char *name, *ns_name = NULL;
memset(&sa, 0, sizeof(sa));
sa.operation = "profile_set";
@@ -103,15 +123,38 @@ int aa_setprocattr_setprofile(struct tas
AA_DEBUG("%s: current %d\n",
__FUNCTION__, current->pid);
+ name = args;
+ if (args[0] != '/') {
+ char *split = strchr(args, ':');
+ if (split) {
+ *split = 0;
+ ns_name = args;
+ name = split + 1;
+ }
+ }
+ if (ns_name)
+ ns = aa_find_namespace(ns_name);
+ else
+ ns = aa_get_namespace(default_namespace);
+ if (!ns) {
+ sa.name = ns_name;
+ sa.info = "unknown namespace";
+ aa_audit_reject(NULL, &sa);
+ aa_put_namespace(ns);
+ return -EINVAL;
+ }
+
repeat:
- if (strcmp(args, "unconfined") == 0)
+ if (strcmp(name, "unconfined") == 0)
new_profile = NULL;
else {
- new_profile = aa_find_profile(args);
+ new_profile = aa_find_profile(ns, name);
if (!new_profile) {
- sa.name = args;
+ sa.name = ns_name;
+ sa.name2 = name;
sa.info = "unknown profile";
aa_audit_reject(NULL, &sa);
+ aa_put_namespace(ns);
return -EINVAL;
}
}
@@ -124,12 +167,14 @@ repeat:
error = PTR_ERR(old_profile);
if (error == -ESTALE)
goto repeat;
+ aa_put_namespace(ns);
return error;
}
if (new_profile) {
- sa.name = args;
- sa.name2 = old_profile ? old_profile->name :
+ sa.name = ns_name;
+ sa.name2 = name;
+ sa.name3 = old_profile ? old_profile->name :
"unconfined";
aa_audit_status(NULL, &sa);
} else {
@@ -142,6 +187,7 @@ repeat:
aa_audit_status(NULL, &sa);
}
}
+ aa_put_namespace(ns);
aa_put_profile(old_profile);
aa_put_profile(new_profile);
return 0;