]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - security/apparmor/apparmorfs.c
UBUNTU: SAUCE: (no-up) apparmor: rebase of apparmor3.5-beta1 snapshot for 4.8
[mirror_ubuntu-zesty-kernel.git] / security / apparmor / apparmorfs.c
index 5923d5665209df45ef37e116f147f9cac2f846d3..9c2b4e2c5817d34e65095f0451ea4e04eff35d36 100644 (file)
 #include <linux/module.h>
 #include <linux/seq_file.h>
 #include <linux/uaccess.h>
+#include <linux/mount.h>
 #include <linux/namei.h>
 #include <linux/capability.h>
 #include <linux/rcupdate.h>
+#include <uapi/linux/major.h>
 
 #include "include/apparmor.h"
 #include "include/apparmorfs.h"
 #include "include/audit.h"
 #include "include/context.h"
 #include "include/crypto.h"
+#include "include/ipc.h"
+#include "include/policy_ns.h"
+#include "include/label.h"
 #include "include/policy.h"
 #include "include/resource.h"
+#include "include/lib.h"
 
 /**
  * aa_mangle_name - mangle a profile name to std profile layout form
@@ -37,7 +43,7 @@
  *
  * Returns: length of mangled name
  */
-static int mangle_name(char *name, char *target)
+static int mangle_name(const char *name, char *target)
 {
        char *t = target;
 
@@ -71,7 +77,6 @@ static int mangle_name(char *name, char *target)
 
 /**
  * aa_simple_write_to_buffer - common routine for getting policy from user
- * @op: operation doing the user buffer copy
  * @userbuf: user buffer to copy data from  (NOT NULL)
  * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size)
  * @copy_size: size of data to copy from user buffer
@@ -80,7 +85,7 @@ static int mangle_name(char *name, char *target)
  * Returns: kernel buffer containing copy of user buffer data or an
  *          ERR_PTR on failure.
  */
-static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
                                       size_t alloc_size, size_t copy_size,
                                       loff_t *pos)
 {
@@ -92,13 +97,6 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
                /* only writes from pos 0, that is complete writes */
                return ERR_PTR(-ESPIPE);
 
-       /*
-        * Don't allow profile load/replace/remove from profiles that don't
-        * have CAP_MAC_ADMIN
-        */
-       if (!aa_may_manage_policy(op))
-               return ERR_PTR(-EACCES);
-
        /* freed by caller to simple_write_to_buffer */
        data = kvmalloc(alloc_size);
        if (data == NULL)
@@ -112,25 +110,40 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
        return data;
 }
 
-
-/* .load file hook fn to load policy */
-static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
-                           loff_t *pos)
+static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
+                        loff_t *pos)
 {
-       char *data;
+       struct aa_label *label;
        ssize_t error;
+       char *data;
 
-       data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos);
+       label = aa_begin_current_label(DO_UPDATE);
 
+       /* high level check about policy management - fine grained in
+        * below after unpack
+        */
+       error = aa_may_manage_policy(label, mask);
+       if (error)
+               return error;
+
+       data = aa_simple_write_to_buffer(buf, size, size, pos);
        error = PTR_ERR(data);
        if (!IS_ERR(data)) {
-               error = aa_replace_profiles(data, size, PROF_ADD);
+               error = aa_replace_profiles(label, mask, data, size);
                kvfree(data);
        }
+       aa_end_current_label(label);
 
        return error;
 }
 
+/* .load file hook fn to load policy */
+static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
+                           loff_t *pos)
+{
+       return policy_update(AA_MAY_LOAD_POLICY, buf, size, pos);
+}
+
 static const struct file_operations aa_fs_profile_load = {
        .write = profile_load,
        .llseek = default_llseek,
@@ -140,17 +153,8 @@ static const struct file_operations aa_fs_profile_load = {
 static ssize_t profile_replace(struct file *f, const char __user *buf,
                               size_t size, loff_t *pos)
 {
-       char *data;
-       ssize_t error;
-
-       data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos);
-       error = PTR_ERR(data);
-       if (!IS_ERR(data)) {
-               error = aa_replace_profiles(data, size, PROF_REPLACE);
-               kvfree(data);
-       }
-
-       return error;
+       return policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY,
+                            buf, size, pos);
 }
 
 static const struct file_operations aa_fs_profile_replace = {
@@ -162,21 +166,31 @@ static const struct file_operations aa_fs_profile_replace = {
 static ssize_t profile_remove(struct file *f, const char __user *buf,
                              size_t size, loff_t *pos)
 {
-       char *data;
+       struct aa_label *label;
        ssize_t error;
+       char *data;
+
+       label = aa_begin_current_label(DO_UPDATE);
+       /* high level check about policy management - fine grained in
+        * below after unpack
+        */
+       error = aa_may_manage_policy(label, AA_MAY_REMOVE_POLICY);
+       if (error)
+               return error;
 
        /*
         * aa_remove_profile needs a null terminated string so 1 extra
         * byte is allocated and the copied data is null terminated.
         */
-       data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos);
+       data = aa_simple_write_to_buffer(buf, size + 1, size, pos);
 
        error = PTR_ERR(data);
        if (!IS_ERR(data)) {
                data[size] = 0;
-               error = aa_remove_profiles(data, size);
+               error = aa_remove_profiles(label, data, size);
                kvfree(data);
        }
+       aa_end_current_label(label);
 
        return error;
 }
@@ -186,6 +200,176 @@ static const struct file_operations aa_fs_profile_remove = {
        .llseek = default_llseek,
 };
 
+
+static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
+                            const char *match_str, size_t match_len)
+{
+       struct aa_perms tmp;
+       struct aa_dfa *dfa;
+       unsigned int state = 0;
+
+       if (profile_unconfined(profile))
+               return;
+       if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
+               dfa = profile->file.dfa;
+               state = aa_dfa_match_len(dfa, profile->file.start,
+                                        match_str + 1, match_len - 1);
+               tmp = nullperms;
+               if (state) {
+                       struct path_cond cond = { };
+                       tmp = aa_compute_fperms(dfa, state, &cond);
+               }
+       } else if (profile->policy.dfa) {
+               if (!PROFILE_MEDIATES_SAFE(profile, *match_str))
+                       return; /* no change to current perms */
+               dfa = profile->policy.dfa;
+               state = aa_dfa_match_len(dfa, profile->policy.start[0],
+                                        match_str, match_len);
+               if (state)
+                       aa_compute_perms(dfa, state, &tmp);
+               else
+                       tmp = nullperms;
+       }
+       aa_apply_modes_to_perms(profile, &tmp);
+       aa_perms_accum_raw(perms, &tmp);
+}
+
+/**
+ * query_label - queries a label and writes permissions to buf
+ * @buf: the resulting permissions string is stored here (NOT NULL)
+ * @buf_len: size of buf
+ * @query: binary query string to match against the dfa
+ * @query_len: size of query
+ *
+ * The buffers pointed to by buf and query may overlap. The query buffer is
+ * parsed before buf is written to.
+ *
+ * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is
+ * the name of the label, in the current namespace, that is to be queried and
+ * DFA_STRING is a binary string to match against the label(s)'s DFA.
+ *
+ * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters
+ * but must *not* be NUL terminated.
+ *
+ * Returns: number of characters written to buf or -errno on failure
+ */
+static ssize_t query_label(char *buf, size_t buf_len,
+                          char *query, size_t query_len, bool ns_only)
+{
+       struct aa_profile *profile;
+       struct aa_label *label, *curr;
+       char *label_name, *match_str;
+       size_t label_name_len, match_len;
+       struct aa_perms perms;
+       struct label_it i;
+
+       if (!query_len)
+               return -EINVAL;
+
+       label_name = query;
+       label_name_len = strnlen(query, query_len);
+       if (!label_name_len || label_name_len == query_len)
+               return -EINVAL;
+
+       /**
+        * The extra byte is to account for the null byte between the
+        * profile name and dfa string. profile_name_len is greater
+        * than zero and less than query_len, so a byte can be safely
+        * added or subtracted.
+        */
+       match_str = label_name + label_name_len + 1;
+       match_len = query_len - label_name_len - 1;
+
+       curr = aa_begin_current_label(DO_UPDATE);
+       label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false);
+       aa_end_current_label(curr);
+       if (IS_ERR(label))
+               return PTR_ERR(label);
+
+       perms = allperms;
+       if (ns_only) {
+               label_for_each_in_ns(i, labels_ns(label), label, profile) {
+                       profile_query_cb(profile, &perms, match_str, match_len);
+               }
+       } else {
+               label_for_each(i, label, profile) {
+                       profile_query_cb(profile, &perms, match_str, match_len);
+               }
+       }
+       aa_put_label(label);
+
+       return scnprintf(buf, buf_len,
+                     "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n",
+                     perms.allow, perms.deny, perms.audit, perms.quiet);
+}
+
+#define QUERY_CMD_LABEL                "label\0"
+#define QUERY_CMD_LABEL_LEN    6
+#define QUERY_CMD_PROFILE      "profile\0"
+#define QUERY_CMD_PROFILE_LEN  8
+#define QUERY_CMD_LABELALL     "labelall\0"
+#define QUERY_CMD_LABELALL_LEN 9
+
+/**
+ * aa_write_access - generic permissions query
+ * @file: pointer to open apparmorfs/access file
+ * @ubuf: user buffer containing the complete query string (NOT NULL)
+ * @count: size of ubuf
+ * @ppos: position in the file (MUST BE ZERO)
+ *
+ * Allows for one permission query per open(), write(), and read() sequence.
+ * The only query currently supported is a label-based query. For this query
+ * ubuf must begin with "label\0", followed by the profile query specific
+ * format described in the query_label() function documentation.
+ *
+ * Returns: number of bytes written or -errno on failure
+ */
+static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
+                              size_t count, loff_t *ppos)
+{
+       char *buf;
+       ssize_t len;
+
+       if (*ppos)
+               return -ESPIPE;
+
+       buf = simple_transaction_get(file, ubuf, count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       if (count > QUERY_CMD_PROFILE_LEN &&
+           !memcmp(buf, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) {
+               len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
+                                 buf + QUERY_CMD_PROFILE_LEN,
+                                 count - QUERY_CMD_PROFILE_LEN, true);
+       } else if (count > QUERY_CMD_LABEL_LEN &&
+                  !memcmp(buf, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) {
+               len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
+                                 buf + QUERY_CMD_LABEL_LEN,
+                                 count - QUERY_CMD_LABEL_LEN, true);
+       } else if (count > QUERY_CMD_LABELALL_LEN &&
+                  !memcmp(buf, QUERY_CMD_LABELALL, QUERY_CMD_LABELALL_LEN)) {
+               len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
+                                 buf + QUERY_CMD_LABELALL_LEN,
+                                 count - QUERY_CMD_LABELALL_LEN, false);
+       } else
+               len = -EINVAL;
+
+       if (len < 0)
+               return len;
+
+       simple_transaction_set(file, len);
+
+       return count;
+}
+
+static const struct file_operations aa_fs_access = {
+       .write          = aa_write_access,
+       .read           = simple_transaction_read,
+       .release        = simple_transaction_release,
+       .llseek         = generic_file_llseek,
+};
+
 static int aa_fs_seq_show(struct seq_file *seq, void *v)
 {
        struct aa_fs_entry *fs_file = seq->private;
@@ -227,12 +411,12 @@ const struct file_operations aa_fs_seq_file_ops = {
 static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
                                  int (*show)(struct seq_file *, void *))
 {
-       struct aa_replacedby *r = aa_get_replacedby(inode->i_private);
-       int error = single_open(file, show, r);
+       struct aa_proxy *proxy = aa_get_proxy(inode->i_private);
+       int error = single_open(file, show, proxy);
 
        if (error) {
                file->private_data = NULL;
-               aa_put_replacedby(r);
+               aa_put_proxy(proxy);
        }
 
        return error;
@@ -242,16 +426,17 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
 {
        struct seq_file *seq = (struct seq_file *) file->private_data;
        if (seq)
-               aa_put_replacedby(seq->private);
+               aa_put_proxy(seq->private);
        return single_release(inode, file);
 }
 
 static int aa_fs_seq_profname_show(struct seq_file *seq, void *v)
 {
-       struct aa_replacedby *r = seq->private;
-       struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
+       struct aa_proxy *proxy = seq->private;
+       struct aa_label *label = aa_get_label_rcu(&proxy->label);
+       struct aa_profile *profile = labels_profile(label);
        seq_printf(seq, "%s\n", profile->base.name);
-       aa_put_profile(profile);
+       aa_put_label(label);
 
        return 0;
 }
@@ -271,10 +456,11 @@ static const struct file_operations aa_fs_profname_fops = {
 
 static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v)
 {
-       struct aa_replacedby *r = seq->private;
-       struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
+       struct aa_proxy *proxy = seq->private;
+       struct aa_label *label = aa_get_label_rcu(&proxy->label);
+       struct aa_profile *profile = labels_profile(label);
        seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]);
-       aa_put_profile(profile);
+       aa_put_label(label);
 
        return 0;
 }
@@ -294,15 +480,16 @@ static const struct file_operations aa_fs_profmode_fops = {
 
 static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v)
 {
-       struct aa_replacedby *r = seq->private;
-       struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
+       struct aa_proxy *proxy = seq->private;
+       struct aa_label *label = aa_get_label_rcu(&proxy->label);
+       struct aa_profile *profile = labels_profile(label);
        if (profile->attach)
                seq_printf(seq, "%s\n", profile->attach);
        else if (profile->xmatch)
                seq_puts(seq, "<unknown>\n");
        else
                seq_printf(seq, "%s\n", profile->base.name);
-       aa_put_profile(profile);
+       aa_put_label(label);
 
        return 0;
 }
@@ -322,8 +509,9 @@ static const struct file_operations aa_fs_profattach_fops = {
 
 static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
 {
-       struct aa_replacedby *r = seq->private;
-       struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
+       struct aa_proxy *proxy = seq->private;
+       struct aa_label *label = aa_get_label_rcu(&proxy->label);
+       struct aa_profile *profile = labels_profile(label);
        unsigned int i, size = aa_hash_size();
 
        if (profile->hash) {
@@ -331,7 +519,7 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
                        seq_printf(seq, "%.2x", profile->hash[i]);
                seq_puts(seq, "\n");
        }
-       aa_put_profile(profile);
+       aa_put_label(label);
 
        return 0;
 }
@@ -350,6 +538,11 @@ static const struct file_operations aa_fs_seq_hash_fops = {
 };
 
 /** fns to setup dynamic per profile/namespace files **/
+
+/**
+ *
+ * Requires: @profile->ns->lock held
+ */
 void __aa_fs_profile_rmdir(struct aa_profile *profile)
 {
        struct aa_profile *child;
@@ -357,27 +550,36 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile)
 
        if (!profile)
                return;
+       AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
 
        list_for_each_entry(child, &profile->base.profiles, base.list)
                __aa_fs_profile_rmdir(child);
 
        for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) {
-               struct aa_replacedby *r;
+               struct aa_proxy *proxy;
                if (!profile->dents[i])
                        continue;
 
-               r = d_inode(profile->dents[i])->i_private;
+               proxy = d_inode(profile->dents[i])->i_private;
                securityfs_remove(profile->dents[i]);
-               aa_put_replacedby(r);
+               aa_put_proxy(proxy);
                profile->dents[i] = NULL;
        }
 }
 
+/**
+ *
+ * Requires: @old->ns->lock held
+ */
 void __aa_fs_profile_migrate_dents(struct aa_profile *old,
                                   struct aa_profile *new)
 {
        int i;
 
+       AA_BUG(!old);
+       AA_BUG(!new);
+       AA_BUG(!mutex_is_locked(&profiles_ns(old)->lock));
+
        for (i = 0; i < AAFS_PROF_SIZEOF; i++) {
                new->dents[i] = old->dents[i];
                if (new->dents[i])
@@ -390,23 +592,29 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name,
                                          struct aa_profile *profile,
                                          const struct file_operations *fops)
 {
-       struct aa_replacedby *r = aa_get_replacedby(profile->replacedby);
+       struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy);
        struct dentry *dent;
 
-       dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops);
+       dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops);
        if (IS_ERR(dent))
-               aa_put_replacedby(r);
+               aa_put_proxy(proxy);
 
        return dent;
 }
 
-/* requires lock be held */
+/**
+ *
+ * Requires: @profile->ns->lock held
+ */
 int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
 {
        struct aa_profile *child;
        struct dentry *dent = NULL, *dir;
        int error;
 
+       AA_BUG(!profile);
+       AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
+
        if (!parent) {
                struct aa_profile *p;
                p = aa_deref_parent(profile);
@@ -477,21 +685,26 @@ fail2:
        return error;
 }
 
-void __aa_fs_namespace_rmdir(struct aa_namespace *ns)
+/**
+ *
+ * Requires: @ns->lock held
+ */
+void __aa_fs_ns_rmdir(struct aa_ns *ns)
 {
-       struct aa_namespace *sub;
+       struct aa_ns *sub;
        struct aa_profile *child;
        int i;
 
        if (!ns)
                return;
+       AA_BUG(!mutex_is_locked(&ns->lock));
 
        list_for_each_entry(child, &ns->base.profiles, base.list)
                __aa_fs_profile_rmdir(child);
 
        list_for_each_entry(sub, &ns->sub_ns, base.list) {
                mutex_lock(&sub->lock);
-               __aa_fs_namespace_rmdir(sub);
+               __aa_fs_ns_rmdir(sub);
                mutex_unlock(&sub->lock);
        }
 
@@ -501,14 +714,21 @@ void __aa_fs_namespace_rmdir(struct aa_namespace *ns)
        }
 }
 
-int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
-                           const char *name)
+/**
+ *
+ * Requires: @ns->lock held
+ */
+int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
 {
-       struct aa_namespace *sub;
+       struct aa_ns *sub;
        struct aa_profile *child;
        struct dentry *dent, *dir;
        int error;
 
+       AA_BUG(!ns);
+       AA_BUG(!parent);
+       AA_BUG(!mutex_is_locked(&ns->lock));
+
        if (!name)
                name = ns->base.name;
 
@@ -535,7 +755,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
 
        list_for_each_entry(sub, &ns->sub_ns, base.list) {
                mutex_lock(&sub->lock);
-               error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL);
+               error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL);
                mutex_unlock(&sub->lock);
                if (error)
                        goto fail2;
@@ -547,7 +767,7 @@ fail:
        error = PTR_ERR(dent);
 
 fail2:
-       __aa_fs_namespace_rmdir(ns);
+       __aa_fs_ns_rmdir(ns);
 
        return error;
 }
@@ -556,7 +776,7 @@ fail2:
 #define list_entry_is_head(pos, head, member) (&pos->member == (head))
 
 /**
- * __next_namespace - find the next namespace to list
+ * __next_ns - find the next namespace to list
  * @root: root namespace to stop search at (NOT NULL)
  * @ns: current ns position (NOT NULL)
  *
@@ -567,10 +787,13 @@ fail2:
  * Requires: ns->parent->lock to be held
  * NOTE: will not unlock root->lock
  */
-static struct aa_namespace *__next_namespace(struct aa_namespace *root,
-                                            struct aa_namespace *ns)
+static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns)
 {
-       struct aa_namespace *parent, *next;
+       struct aa_ns *parent, *next;
+
+       AA_BUG(!root);
+       AA_BUG(!ns);
+       AA_BUG(ns != root && !mutex_is_locked(&ns->parent->lock));
 
        /* is next namespace a child */
        if (!list_empty(&ns->sub_ns)) {
@@ -598,15 +821,17 @@ static struct aa_namespace *__next_namespace(struct aa_namespace *root,
 /**
  * __first_profile - find the first profile in a namespace
  * @root: namespace that is root of profiles being displayed (NOT NULL)
- * @ns: namespace to start in   (NOT NULL)
+ * @ns: namespace to start in   (MAY BE NULL)
  *
  * Returns: unrefcounted profile or NULL if no profile
- * Requires: profile->ns.lock to be held
+ * Requires: ns.lock to be held
  */
-static struct aa_profile *__first_profile(struct aa_namespace *root,
-                                         struct aa_namespace *ns)
+static struct aa_profile *__first_profile(struct aa_ns *root, struct aa_ns *ns)
 {
-       for (; ns; ns = __next_namespace(root, ns)) {
+       AA_BUG(!root);
+       AA_BUG(ns && !mutex_is_locked(&ns->lock));
+
+       for (; ns; ns = __next_ns(root, ns)) {
                if (!list_empty(&ns->base.profiles))
                        return list_first_entry(&ns->base.profiles,
                                                struct aa_profile, base.list);
@@ -626,7 +851,9 @@ static struct aa_profile *__first_profile(struct aa_namespace *root,
 static struct aa_profile *__next_profile(struct aa_profile *p)
 {
        struct aa_profile *parent;
-       struct aa_namespace *ns = p->ns;
+       struct aa_ns *ns = p->ns;
+
+       AA_BUG(!mutex_is_locked(&profiles_ns(p)->lock));
 
        /* is next profile a child */
        if (!list_empty(&p->base.profiles))
@@ -660,7 +887,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p)
  *
  * Returns: next profile or NULL if there isn't one
  */
-static struct aa_profile *next_profile(struct aa_namespace *root,
+static struct aa_profile *next_profile(struct aa_ns *root,
                                       struct aa_profile *profile)
 {
        struct aa_profile *next = __next_profile(profile);
@@ -668,7 +895,7 @@ static struct aa_profile *next_profile(struct aa_namespace *root,
                return next;
 
        /* finished all profiles in namespace move to next namespace */
-       return __first_profile(root, __next_namespace(root, profile->ns));
+       return __first_profile(root, __next_ns(root, profile->ns));
 }
 
 /**
@@ -683,10 +910,9 @@ static struct aa_profile *next_profile(struct aa_namespace *root,
 static void *p_start(struct seq_file *f, loff_t *pos)
 {
        struct aa_profile *profile = NULL;
-       struct aa_namespace *root = aa_current_profile()->ns;
+       struct aa_ns *root = aa_get_current_ns();
        loff_t l = *pos;
-       f->private = aa_get_namespace(root);
-
+       f->private = root;
 
        /* find the first profile */
        mutex_lock(&root->lock);
@@ -712,7 +938,7 @@ static void *p_start(struct seq_file *f, loff_t *pos)
 static void *p_next(struct seq_file *f, void *p, loff_t *pos)
 {
        struct aa_profile *profile = p;
-       struct aa_namespace *ns = f->private;
+       struct aa_ns *ns = f->private;
        (*pos)++;
 
        return next_profile(ns, profile);
@@ -728,14 +954,14 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos)
 static void p_stop(struct seq_file *f, void *p)
 {
        struct aa_profile *profile = p;
-       struct aa_namespace *root = f->private, *ns;
+       struct aa_ns *root = f->private, *ns;
 
        if (profile) {
                for (ns = profile->ns; ns && ns != root; ns = ns->parent)
                        mutex_unlock(&ns->lock);
        }
        mutex_unlock(&root->lock);
-       aa_put_namespace(root);
+       aa_put_ns(root);
 }
 
 /**
@@ -748,12 +974,11 @@ static void p_stop(struct seq_file *f, void *p)
 static int seq_show_profile(struct seq_file *f, void *p)
 {
        struct aa_profile *profile = (struct aa_profile *)p;
-       struct aa_namespace *root = f->private;
+       struct aa_ns *root = f->private;
 
-       if (profile->ns != root)
-               seq_printf(f, ":%s://", aa_ns_name(root, profile->ns));
-       seq_printf(f, "%s (%s)\n", profile->base.hname,
-                  aa_profile_mode_names[profile->mode]);
+       aa_label_seq_xprint(f, root, &profile->label,
+                           FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL);
+       seq_printf(f, "\n");
 
        return 0;
 }
@@ -767,6 +992,9 @@ static const struct seq_operations aa_fs_profiles_op = {
 
 static int profiles_open(struct inode *inode, struct file *file)
 {
+       if (!policy_admin_capable())
+               return -EACCES;
+
        return seq_open(file, &aa_fs_profiles_op);
 }
 
@@ -790,34 +1018,76 @@ static struct aa_fs_entry aa_fs_entry_file[] = {
        { }
 };
 
+static struct aa_fs_entry aa_fs_entry_ptrace[] = {
+       AA_FS_FILE_STRING("mask", "read trace"),
+       { }
+};
+
+static struct aa_fs_entry aa_fs_entry_signal[] = {
+       AA_FS_FILE_STRING("mask", AA_FS_SIG_MASK),
+       { }
+};
+
 static struct aa_fs_entry aa_fs_entry_domain[] = {
        AA_FS_FILE_BOOLEAN("change_hat",        1),
        AA_FS_FILE_BOOLEAN("change_hatv",       1),
        AA_FS_FILE_BOOLEAN("change_onexec",     1),
        AA_FS_FILE_BOOLEAN("change_profile",    1),
+       AA_FS_FILE_BOOLEAN("stack",             1),
+       { }
+};
+
+static struct aa_fs_entry aa_fs_entry_versions[] = {
+       AA_FS_FILE_BOOLEAN("v5",        1),
+       AA_FS_FILE_BOOLEAN("v6",        1),
+       AA_FS_FILE_BOOLEAN("v7",        1),
        { }
 };
 
 static struct aa_fs_entry aa_fs_entry_policy[] = {
-       AA_FS_FILE_BOOLEAN("set_load",          1),
-       {}
+       AA_FS_DIR("versions",                   aa_fs_entry_versions),
+       AA_FS_FILE_BOOLEAN("set_load",          1),
+       { }
+};
+
+static struct aa_fs_entry aa_fs_entry_mount[] = {
+       AA_FS_FILE_STRING("mask", "mount umount"),
+       { }
+};
+
+static struct aa_fs_entry aa_fs_entry_ns[] = {
+       AA_FS_FILE_BOOLEAN("profile",           1),
+       AA_FS_FILE_BOOLEAN("pivot_root",        1),
+       { }
+};
+
+static struct aa_fs_entry aa_fs_entry_dbus[] = {
+       AA_FS_FILE_STRING("mask", "acquire send receive"),
+       { }
 };
 
 static struct aa_fs_entry aa_fs_entry_features[] = {
        AA_FS_DIR("policy",                     aa_fs_entry_policy),
        AA_FS_DIR("domain",                     aa_fs_entry_domain),
        AA_FS_DIR("file",                       aa_fs_entry_file),
+       AA_FS_DIR("network",                    aa_fs_entry_network),
+       AA_FS_DIR("mount",                      aa_fs_entry_mount),
+       AA_FS_DIR("namespaces",                 aa_fs_entry_ns),
        AA_FS_FILE_U64("capability",            VFS_CAP_FLAGS_MASK),
        AA_FS_DIR("rlimit",                     aa_fs_entry_rlimit),
        AA_FS_DIR("caps",                       aa_fs_entry_caps),
+       AA_FS_DIR("ptrace",                     aa_fs_entry_ptrace),
+       AA_FS_DIR("signal",                     aa_fs_entry_signal),
+       AA_FS_DIR("dbus",                       aa_fs_entry_dbus),
        { }
 };
 
 static struct aa_fs_entry aa_fs_entry_apparmor[] = {
-       AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load),
-       AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace),
-       AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove),
-       AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops),
+       AA_FS_FILE_FOPS(".load", 0666, &aa_fs_profile_load),
+       AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace),
+       AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove),
+       AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access),
+       AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops),
        AA_FS_DIR("features", aa_fs_entry_features),
        { }
 };
@@ -926,6 +1196,51 @@ void __init aa_destroy_aafs(void)
        aafs_remove_dir(&aa_fs_entry);
 }
 
+
+#define NULL_FILE_NAME ".null"
+struct path aa_null;
+
+static int aa_mk_null_file(struct dentry *parent)
+{
+       struct vfsmount *mount = NULL;
+       struct dentry *dentry;
+       struct inode *inode;
+       int count = 0;
+       int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count);
+       if (error)
+               return error;
+
+       inode_lock(d_inode(parent));
+       dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME));
+       if (IS_ERR(dentry)) {
+               error = PTR_ERR(dentry);
+               goto out;
+       }
+       inode = new_inode(parent->d_inode->i_sb);
+       if (!inode) {
+               error = -ENOMEM;
+               goto out1;
+       }
+
+       inode->i_ino = get_next_ino();
+       inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO;
+       inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+       init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO,
+                          MKDEV(MEM_MAJOR, 3));
+       d_instantiate(dentry, inode);
+       aa_null.dentry = dget(dentry);
+       aa_null.mnt = mntget(mount);
+
+       error = 0;
+
+out1:
+       dput(dentry);
+out:
+       inode_unlock(d_inode(parent));
+       simple_release_fs(&mount, &count);
+       return error;
+}
+
 /**
  * aa_create_aafs - create the apparmor security filesystem
  *
@@ -950,12 +1265,20 @@ static int __init aa_create_aafs(void)
        if (error)
                goto error;
 
-       error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry,
-                                       "policy");
+       mutex_lock(&root_ns->lock);
+       error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy");
+       mutex_unlock(&root_ns->lock);
+
+       if (error)
+               goto error;
+
+       error = aa_mk_null_file(aa_fs_entry.dentry);
        if (error)
                goto error;
 
-       /* TODO: add support for apparmorfs_null and apparmorfs_mnt */
+       if (!aa_g_unconfined_init) {
+               /* TODO: add default profile to apparmorfs */
+       }
 
        /* Report that AppArmor fs is enabled */
        aa_info_message("AppArmor Filesystem Enabled");