mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-06 09:21:00 +01:00
1158 lines
33 KiB
Diff
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;
|