]> www.infradead.org Git - users/willy/linux.git/commitdiff
apparmor: Implement security hooks for the new mount API
authorDavid Howells <dhowells@redhat.com>
Tue, 11 Sep 2018 21:20:55 +0000 (22:20 +0100)
committerDavid Howells <dhowells@redhat.com>
Tue, 23 Oct 2018 16:38:57 +0000 (17:38 +0100)
Implement hooks to check the creation of new mountpoints for AppArmor.

Unfortunately, the DFA evaluation puts the option data in last, after the
details of the mountpoint, so we have to cache the mount options in the
fs_context using those hooks till we get to the new mountpoint hook.

Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: John Johansen <john.johansen@canonical.com>
cc: apparmor@lists.ubuntu.com
cc: linux-security-module@vger.kernel.org

security/apparmor/include/mount.h
security/apparmor/lsm.c
security/apparmor/mount.c

index 25d6067fa6efef03c1257045756a3cd85576403a..0441bfae30fac54ae12d8889761f3bdcd8efa80f 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <linux/fs.h>
 #include <linux/path.h>
+#include <linux/fs_context.h>
 
 #include "domain.h"
 #include "policy.h"
 #define AA_AUDIT_DATA          0x40
 #define AA_MNT_CONT_MATCH      0x40
 
-#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
+#define AA_SB_IGNORE_MASK (SB_KERNMOUNT | SB_NOSEC | SB_ACTIVE | SB_BORN)
+
+struct apparmor_fs_context {
+       struct fs_context       fc;
+       char                    *saved_options;
+       size_t                  saved_size;
+};
 
 int aa_remount(struct aa_label *label, const struct path *path,
               unsigned long flags, void *data);
@@ -45,6 +52,8 @@ int aa_move_mount(struct aa_label *label, const struct path *path,
 int aa_new_mount(struct aa_label *label, const char *dev_name,
                 const struct path *path, const char *type, unsigned long flags,
                 void *data);
+int aa_new_mount_fc(struct aa_label *label, struct fs_context *fc,
+                   const struct path *mountpoint);
 
 int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags);
 
index 3d98ace5b898ff0b4c3fb4aa7b1c5fb64a023820..416204ea713d6fef075904d8d2c180fd88e4a31e 100644 (file)
@@ -520,6 +520,105 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
                           !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
 }
 
+static int apparmor_fs_context_alloc(struct fs_context *fc, struct dentry *reference)
+{
+       struct apparmor_fs_context *afc;
+
+       afc = kzalloc(sizeof(*afc), GFP_KERNEL);
+       if (!afc)
+               return -ENOMEM;
+
+       fc->security = afc;
+       return 0;
+}
+
+static int apparmor_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc)
+{
+       fc->security = NULL;
+       return 0;
+}
+
+static void apparmor_fs_context_free(struct fs_context *fc)
+{
+       struct apparmor_fs_context *afc = fc->security;
+
+       if (afc) {
+               kfree(afc->saved_options);
+               kfree(afc);
+       }
+}
+
+/*
+ * As a temporary hack, we buffer all the options.  The problem is that we need
+ * to pass them to the DFA evaluator *after* mount point parameters, which
+ * means deferring the entire check to the sb_mountpoint hook.
+ */
+static int apparmor_fs_context_parse_param(struct fs_context *fc,
+                                          struct fs_parameter *param)
+{
+       struct apparmor_fs_context *afc = fc->security;
+       const char *value;
+       size_t space = 0, k_len = strlen(param->key), len = k_len, v_len;
+       char *p, *q;
+
+       if (afc->saved_size > 0)
+               space = 1;
+
+       switch (param->type) {
+       case fs_value_is_string:
+               value = param->string;
+               v_len = param->size;
+               len += 1 + v_len;
+               break;
+       case fs_value_is_filename:
+       case fs_value_is_filename_empty: {
+               value = param->name->name;
+               v_len = param->size;
+               len += 1 + v_len;
+               break;
+       }
+       default:
+               value = NULL;
+               v_len = 0;
+               break;
+       }
+
+       p = krealloc(afc->saved_options, afc->saved_size + space + len + 1,
+                    GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+
+       q = p + afc->saved_size;
+       if (q != p)
+               *q++ = ' ';
+       memcpy(q, param->key, k_len);
+       q += k_len;
+       if (value) {
+               *q++ = '=';
+               memcpy(q, value, v_len);
+               q += v_len;
+       }
+       *q = 0;
+
+       afc->saved_options = p;
+       afc->saved_size += 1 + len;
+       return -ENOPARAM;
+}
+
+static int apparmor_sb_mountpoint(struct fs_context *fc, struct path *mountpoint,
+                                 unsigned int mnt_flags)
+{
+       struct aa_label *label;
+       int error = 0;
+
+       label = __begin_current_label_crit_section();
+       if (!unconfined(label))
+               error = aa_new_mount_fc(label, fc, mountpoint);
+       __end_current_label_crit_section(label);
+
+       return error;
+}
+
 static int apparmor_sb_mount(const char *dev_name, const struct path *path,
                             const char *type, unsigned long flags,
                             void *data, size_t data_size)
@@ -531,7 +630,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,
        if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
                flags &= ~MS_MGC_MSK;
 
-       flags &= ~AA_MS_IGNORE_MASK;
+       flags &= ~AA_SB_IGNORE_MASK;
 
        label = __begin_current_label_crit_section();
        if (!unconfined(label)) {
@@ -1134,6 +1233,12 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
        LSM_HOOK_INIT(capget, apparmor_capget),
        LSM_HOOK_INIT(capable, apparmor_capable),
 
+       LSM_HOOK_INIT(fs_context_alloc, apparmor_fs_context_alloc),
+       LSM_HOOK_INIT(fs_context_dup, apparmor_fs_context_dup),
+       LSM_HOOK_INIT(fs_context_free, apparmor_fs_context_free),
+       LSM_HOOK_INIT(fs_context_parse_param, apparmor_fs_context_parse_param),
+       LSM_HOOK_INIT(sb_mountpoint, apparmor_sb_mountpoint),
+
        LSM_HOOK_INIT(sb_mount, apparmor_sb_mount),
        LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
        LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot),
index 8c3787399356b940889711f9e33f376ce02605d0..3c95fffb76ace60dcc28dbb27d70ec8f553d5b12 100644 (file)
@@ -554,6 +554,52 @@ int aa_new_mount(struct aa_label *label, const char *dev_name,
        return error;
 }
 
+int aa_new_mount_fc(struct aa_label *label, struct fs_context *fc,
+                   const struct path *mountpoint)
+{
+       struct apparmor_fs_context *afc = fc->security;
+       struct aa_profile *profile;
+       char *buffer = NULL, *dev_buffer = NULL;
+       bool binary;
+       int error;
+       struct path tmp_path, *dev_path = NULL;
+
+       AA_BUG(!label);
+       AA_BUG(!mountpoint);
+
+       binary = fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA;
+
+       if (fc->fs_type->fs_flags & FS_REQUIRES_DEV) {
+               if (!fc->source)
+                       return -ENOENT;
+
+               error = kern_path(fc->source, LOOKUP_FOLLOW, &tmp_path);
+               if (error)
+                       return error;
+               dev_path = &tmp_path;
+       }
+
+       get_buffers(buffer, dev_buffer);
+       if (dev_path) {
+               error = fn_for_each_confined(label, profile,
+                       match_mnt(profile, mountpoint, buffer, dev_path, dev_buffer,
+                                 fc->fs_type->name,
+                                 fc->sb_flags & ~AA_SB_IGNORE_MASK,
+                                 afc->saved_options, binary));
+       } else {
+               error = fn_for_each_confined(label, profile,
+                       match_mnt_path_str(profile, mountpoint, buffer,
+                                          fc->source, fc->fs_type->name,
+                                          fc->sb_flags & ~AA_SB_IGNORE_MASK,
+                                          afc->saved_options, binary, NULL));
+       }
+       put_buffers(buffer, dev_buffer);
+       if (dev_path)
+               path_put(dev_path);
+
+       return error;
+}
+
 static int profile_umount(struct aa_profile *profile, struct path *path,
                          char *buffer)
 {