whiterose

linux unikernel
Log | Files | Refs | README | LICENSE | git clone https://git.ne02ptzero.me/git/whiterose

commit 505b050fdf42097883b2d37b8e796e1f11dbef50
parent 9b286efeb5eb5aaa2712873fc1f928b2f879dbde
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sat,  5 Jan 2019 13:25:58 -0800

Merge branch 'mount.part1' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull vfs mount API prep from Al Viro:
 "Mount API prereqs.

  Mostly that's LSM mount options cleanups. There are several minor
  fixes in there, but nothing earth-shattering (leaks on failure exits,
  mostly)"

* 'mount.part1' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (27 commits)
  mount_fs: suppress MAC on MS_SUBMOUNT as well as MS_KERNMOUNT
  smack: rewrite smack_sb_eat_lsm_opts()
  smack: get rid of match_token()
  smack: take the guts of smack_parse_opts_str() into a new helper
  LSM: new method: ->sb_add_mnt_opt()
  selinux: rewrite selinux_sb_eat_lsm_opts()
  selinux: regularize Opt_... names a bit
  selinux: switch away from match_token()
  selinux: new helper - selinux_add_opt()
  LSM: bury struct security_mnt_opts
  smack: switch to private smack_mnt_opts
  selinux: switch to private struct selinux_mnt_opts
  LSM: hide struct security_mnt_opts from any generic code
  selinux: kill selinux_sb_get_mnt_opts()
  LSM: turn sb_eat_lsm_opts() into a method
  nfs_remount(): don't leak, don't ignore LSM options quietly
  btrfs: sanitize security_mnt_opts use
  selinux; don't open-code a loop in sb_finish_set_opts()
  LSM: split ->sb_set_mnt_opts() out of ->sb_kern_mount()
  new helper: security_sb_eat_lsm_opts()
  ...

Diffstat:
March/arc/kernel/setup.c | 1+
March/arm/kernel/atags_parse.c | 1+
March/sh/kernel/setup.c | 1+
March/sparc/kernel/setup_32.c | 1+
March/sparc/kernel/setup_64.c | 1+
March/x86/kernel/setup.c | 1+
Mdrivers/base/devtmpfs.c | 1+
Mfs/btrfs/ctree.h | 4----
Mfs/btrfs/super.c | 82+++++++++++--------------------------------------------------------------------
Mfs/exofs/super.c | 37+++++++++++++++++++++++++++++--------
Mfs/namespace.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mfs/nfs/internal.h | 2+-
Mfs/nfs/super.c | 34++++++++++------------------------
Mfs/pnode.c | 1+
Mfs/super.c | 24+++++++++++++-----------
Minclude/linux/lsm_hooks.h | 17++++++++++-------
Minclude/linux/mount.h | 2+-
Minclude/linux/security.h | 82++++++++++++++++---------------------------------------------------------------
Minclude/uapi/linux/fs.h | 56+++++---------------------------------------------------
Ainclude/uapi/linux/mount.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minit/do_mounts.c | 1+
Minit/do_mounts_initrd.c | 1+
Msecurity/apparmor/lsm.c | 1+
Msecurity/apparmor/mount.c | 1+
Msecurity/security.c | 39++++++++++++++++++++++++++-------------
Msecurity/selinux/hooks.c | 799+++++++++++++++++++++++++++++--------------------------------------------------
Msecurity/smack/smack_lsm.c | 359++++++++++++++++++++++++++++++-------------------------------------------------
Msecurity/tomoyo/mount.c | 1+
28 files changed, 724 insertions(+), 1040 deletions(-)

diff --git a/arch/arc/kernel/setup.c b/arch/arc/kernel/setup.c @@ -19,6 +19,7 @@ #include <linux/of_fdt.h> #include <linux/of.h> #include <linux/cache.h> +#include <uapi/linux/mount.h> #include <asm/sections.h> #include <asm/arcregs.h> #include <asm/tlb.h> diff --git a/arch/arm/kernel/atags_parse.c b/arch/arm/kernel/atags_parse.c @@ -24,6 +24,7 @@ #include <linux/root_dev.h> #include <linux/screen_info.h> #include <linux/memblock.h> +#include <uapi/linux/mount.h> #include <asm/setup.h> #include <asm/system_info.h> diff --git a/arch/sh/kernel/setup.c b/arch/sh/kernel/setup.c @@ -32,6 +32,7 @@ #include <linux/of.h> #include <linux/of_fdt.h> #include <linux/uaccess.h> +#include <uapi/linux/mount.h> #include <asm/io.h> #include <asm/page.h> #include <asm/elf.h> diff --git a/arch/sparc/kernel/setup_32.c b/arch/sparc/kernel/setup_32.c @@ -34,6 +34,7 @@ #include <linux/kdebug.h> #include <linux/export.h> #include <linux/start_kernel.h> +#include <uapi/linux/mount.h> #include <asm/io.h> #include <asm/processor.h> diff --git a/arch/sparc/kernel/setup_64.c b/arch/sparc/kernel/setup_64.c @@ -33,6 +33,7 @@ #include <linux/module.h> #include <linux/start_kernel.h> #include <linux/memblock.h> +#include <uapi/linux/mount.h> #include <asm/io.h> #include <asm/processor.h> diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c @@ -50,6 +50,7 @@ #include <linux/kvm_para.h> #include <linux/dma-contiguous.h> #include <xen/xen.h> +#include <uapi/linux/mount.h> #include <linux/errno.h> #include <linux/kernel.h> diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c @@ -25,6 +25,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/kthread.h> +#include <uapi/linux/mount.h> #include "base.h" static struct task_struct *thread; diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h @@ -1144,9 +1144,6 @@ struct btrfs_fs_info { struct mutex unused_bg_unpin_mutex; struct mutex delete_unused_bgs_mutex; - /* For btrfs to record security options */ - struct security_mnt_opts security_opts; - /* * Chunks that can't be freed yet (under a trim/discard operation) * and will be latter freed. Protected by fs_info->chunk_mutex. @@ -3021,7 +3018,6 @@ static inline void free_fs_info(struct btrfs_fs_info *fs_info) kfree(fs_info->free_space_root); kfree(fs_info->super_copy); kfree(fs_info->super_for_commit); - security_free_mnt_opts(&fs_info->security_opts); kvfree(fs_info); } diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c @@ -1458,56 +1458,6 @@ out: return root; } -static int parse_security_options(char *orig_opts, - struct security_mnt_opts *sec_opts) -{ - char *secdata = NULL; - int ret = 0; - - secdata = alloc_secdata(); - if (!secdata) - return -ENOMEM; - ret = security_sb_copy_data(orig_opts, secdata); - if (ret) { - free_secdata(secdata); - return ret; - } - ret = security_sb_parse_opts_str(secdata, sec_opts); - free_secdata(secdata); - return ret; -} - -static int setup_security_options(struct btrfs_fs_info *fs_info, - struct super_block *sb, - struct security_mnt_opts *sec_opts) -{ - int ret = 0; - - /* - * Call security_sb_set_mnt_opts() to check whether new sec_opts - * is valid. - */ - ret = security_sb_set_mnt_opts(sb, sec_opts, 0, NULL); - if (ret) - return ret; - -#ifdef CONFIG_SECURITY - if (!fs_info->security_opts.num_mnt_opts) { - /* first time security setup, copy sec_opts to fs_info */ - memcpy(&fs_info->security_opts, sec_opts, sizeof(*sec_opts)); - } else { - /* - * Since SELinux (the only one supporting security_mnt_opts) - * does NOT support changing context during remount/mount of - * the same sb, this must be the same or part of the same - * security options, just free it. - */ - security_free_mnt_opts(sec_opts); - } -#endif - return ret; -} - /* * Find a superblock for the given device / mount point. * @@ -1522,16 +1472,15 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, struct btrfs_device *device = NULL; struct btrfs_fs_devices *fs_devices = NULL; struct btrfs_fs_info *fs_info = NULL; - struct security_mnt_opts new_sec_opts; + void *new_sec_opts = NULL; fmode_t mode = FMODE_READ; int error = 0; if (!(flags & SB_RDONLY)) mode |= FMODE_WRITE; - security_init_mnt_opts(&new_sec_opts); if (data) { - error = parse_security_options(data, &new_sec_opts); + error = security_sb_eat_lsm_opts(data, &new_sec_opts); if (error) return ERR_PTR(error); } @@ -1550,7 +1499,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, fs_info->super_copy = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); fs_info->super_for_commit = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); - security_init_mnt_opts(&fs_info->security_opts); if (!fs_info->super_copy || !fs_info->super_for_commit) { error = -ENOMEM; goto error_fs_info; @@ -1601,16 +1549,12 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, btrfs_sb(s)->bdev_holder = fs_type; error = btrfs_fill_super(s, fs_devices, data); } + if (!error) + error = security_sb_set_mnt_opts(s, new_sec_opts, 0, NULL); + security_free_mnt_opts(&new_sec_opts); if (error) { deactivate_locked_super(s); - goto error_sec_opts; - } - - fs_info = btrfs_sb(s); - error = setup_security_options(fs_info, s, &new_sec_opts); - if (error) { - deactivate_locked_super(s); - goto error_sec_opts; + return ERR_PTR(error); } return dget(s->s_root); @@ -1779,18 +1723,14 @@ static int btrfs_remount(struct super_block *sb, int *flags, char *data) btrfs_remount_prepare(fs_info); if (data) { - struct security_mnt_opts new_sec_opts; + void *new_sec_opts = NULL; - security_init_mnt_opts(&new_sec_opts); - ret = parse_security_options(data, &new_sec_opts); + ret = security_sb_eat_lsm_opts(data, &new_sec_opts); + if (!ret) + ret = security_sb_remount(sb, new_sec_opts); + security_free_mnt_opts(&new_sec_opts); if (ret) goto restore; - ret = setup_security_options(fs_info, sb, - &new_sec_opts); - if (ret) { - security_free_mnt_opts(&new_sec_opts); - goto restore; - } } ret = btrfs_parse_options(fs_info, data, *flags); diff --git a/fs/exofs/super.c b/fs/exofs/super.c @@ -705,21 +705,18 @@ out: /* * Read the superblock from the OSD and fill in the fields */ -static int exofs_fill_super(struct super_block *sb, void *data, int silent) +static int exofs_fill_super(struct super_block *sb, + struct exofs_mountopt *opts, + struct exofs_sb_info *sbi, + int silent) { struct inode *root; - struct exofs_mountopt *opts = data; - struct exofs_sb_info *sbi; /*extended info */ struct osd_dev *od; /* Master device */ struct exofs_fscb fscb; /*on-disk superblock info */ struct ore_comp comp; unsigned table_count; int ret; - sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); - if (!sbi) - return -ENOMEM; - /* use mount options to fill superblock */ if (opts->is_osdname) { struct osd_dev_info odi = {.systemid_len = 0}; @@ -863,7 +860,9 @@ static struct dentry *exofs_mount(struct file_system_type *type, int flags, const char *dev_name, void *data) { + struct super_block *s; struct exofs_mountopt opts; + struct exofs_sb_info *sbi; int ret; ret = parse_options(data, &opts); @@ -872,9 +871,31 @@ static struct dentry *exofs_mount(struct file_system_type *type, return ERR_PTR(ret); } + sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); + if (!sbi) { + kfree(opts.dev_name); + return ERR_PTR(-ENOMEM); + } + + s = sget(type, NULL, set_anon_super, flags, NULL); + + if (IS_ERR(s)) { + kfree(opts.dev_name); + kfree(sbi); + return ERR_CAST(s); + } + if (!opts.dev_name) opts.dev_name = dev_name; - return mount_nodev(type, flags, &opts, exofs_fill_super); + + + ret = exofs_fill_super(s, &opts, sbi, flags & SB_SILENT ? 1 : 0); + if (ret) { + deactivate_locked_super(s); + return ERR_PTR(ret); + } + s->s_flags |= SB_ACTIVE; + return dget(s->s_root); } /* diff --git a/fs/namespace.c b/fs/namespace.c @@ -26,6 +26,7 @@ #include <linux/memblock.h> #include <linux/task_work.h> #include <linux/sched/task.h> +#include <uapi/linux/mount.h> #include "pnode.h" #include "internal.h" @@ -245,13 +246,9 @@ out_free_cache: * mnt_want/drop_write() will _keep_ the filesystem * r/w. */ -int __mnt_is_readonly(struct vfsmount *mnt) +bool __mnt_is_readonly(struct vfsmount *mnt) { - if (mnt->mnt_flags & MNT_READONLY) - return 1; - if (sb_rdonly(mnt->mnt_sb)) - return 1; - return 0; + return (mnt->mnt_flags & MNT_READONLY) || sb_rdonly(mnt->mnt_sb); } EXPORT_SYMBOL_GPL(__mnt_is_readonly); @@ -507,11 +504,12 @@ static int mnt_make_readonly(struct mount *mnt) return ret; } -static void __mnt_unmake_readonly(struct mount *mnt) +static int __mnt_unmake_readonly(struct mount *mnt) { lock_mount_hash(); mnt->mnt.mnt_flags &= ~MNT_READONLY; unlock_mount_hash(); + return 0; } int sb_prepare_remount_readonly(struct super_block *sb) @@ -2215,21 +2213,91 @@ out: return err; } -static int change_mount_flags(struct vfsmount *mnt, int ms_flags) +/* + * Don't allow locked mount flags to be cleared. + * + * No locks need to be held here while testing the various MNT_LOCK + * flags because those flags can never be cleared once they are set. + */ +static bool can_change_locked_flags(struct mount *mnt, unsigned int mnt_flags) { - int error = 0; - int readonly_request = 0; + unsigned int fl = mnt->mnt.mnt_flags; + + if ((fl & MNT_LOCK_READONLY) && + !(mnt_flags & MNT_READONLY)) + return false; - if (ms_flags & MS_RDONLY) - readonly_request = 1; - if (readonly_request == __mnt_is_readonly(mnt)) + if ((fl & MNT_LOCK_NODEV) && + !(mnt_flags & MNT_NODEV)) + return false; + + if ((fl & MNT_LOCK_NOSUID) && + !(mnt_flags & MNT_NOSUID)) + return false; + + if ((fl & MNT_LOCK_NOEXEC) && + !(mnt_flags & MNT_NOEXEC)) + return false; + + if ((fl & MNT_LOCK_ATIME) && + ((fl & MNT_ATIME_MASK) != (mnt_flags & MNT_ATIME_MASK))) + return false; + + return true; +} + +static int change_mount_ro_state(struct mount *mnt, unsigned int mnt_flags) +{ + bool readonly_request = (mnt_flags & MNT_READONLY); + + if (readonly_request == __mnt_is_readonly(&mnt->mnt)) return 0; if (readonly_request) - error = mnt_make_readonly(real_mount(mnt)); - else - __mnt_unmake_readonly(real_mount(mnt)); - return error; + return mnt_make_readonly(mnt); + + return __mnt_unmake_readonly(mnt); +} + +/* + * Update the user-settable attributes on a mount. The caller must hold + * sb->s_umount for writing. + */ +static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags) +{ + lock_mount_hash(); + mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK; + mnt->mnt.mnt_flags = mnt_flags; + touch_mnt_namespace(mnt->mnt_ns); + unlock_mount_hash(); +} + +/* + * Handle reconfiguration of the mountpoint only without alteration of the + * superblock it refers to. This is triggered by specifying MS_REMOUNT|MS_BIND + * to mount(2). + */ +static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags) +{ + struct super_block *sb = path->mnt->mnt_sb; + struct mount *mnt = real_mount(path->mnt); + int ret; + + if (!check_mnt(mnt)) + return -EINVAL; + + if (path->dentry != mnt->mnt.mnt_root) + return -EINVAL; + + if (!can_change_locked_flags(mnt, mnt_flags)) + return -EPERM; + + down_write(&sb->s_umount); + ret = change_mount_ro_state(mnt, mnt_flags); + if (ret == 0) + set_mount_attributes(mnt, mnt_flags); + up_write(&sb->s_umount); + return ret; } /* @@ -2243,6 +2311,7 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags, int err; struct super_block *sb = path->mnt->mnt_sb; struct mount *mnt = real_mount(path->mnt); + void *sec_opts = NULL; if (!check_mnt(mnt)) return -EINVAL; @@ -2250,50 +2319,25 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags, if (path->dentry != path->mnt->mnt_root) return -EINVAL; - /* Don't allow changing of locked mnt flags. - * - * No locks need to be held here while testing the various - * MNT_LOCK flags because those flags can never be cleared - * once they are set. - */ - if ((mnt->mnt.mnt_flags & MNT_LOCK_READONLY) && - !(mnt_flags & MNT_READONLY)) { - return -EPERM; - } - if ((mnt->mnt.mnt_flags & MNT_LOCK_NODEV) && - !(mnt_flags & MNT_NODEV)) { - return -EPERM; - } - if ((mnt->mnt.mnt_flags & MNT_LOCK_NOSUID) && - !(mnt_flags & MNT_NOSUID)) { - return -EPERM; - } - if ((mnt->mnt.mnt_flags & MNT_LOCK_NOEXEC) && - !(mnt_flags & MNT_NOEXEC)) { - return -EPERM; - } - if ((mnt->mnt.mnt_flags & MNT_LOCK_ATIME) && - ((mnt->mnt.mnt_flags & MNT_ATIME_MASK) != (mnt_flags & MNT_ATIME_MASK))) { + if (!can_change_locked_flags(mnt, mnt_flags)) return -EPERM; - } - err = security_sb_remount(sb, data); + if (data && !(sb->s_type->fs_flags & FS_BINARY_MOUNTDATA)) { + err = security_sb_eat_lsm_opts(data, &sec_opts); + if (err) + return err; + } + err = security_sb_remount(sb, sec_opts); + security_free_mnt_opts(&sec_opts); if (err) return err; down_write(&sb->s_umount); - if (ms_flags & MS_BIND) - err = change_mount_flags(path->mnt, ms_flags); - else if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) - err = -EPERM; - else + err = -EPERM; + if (ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) { err = do_remount_sb(sb, sb_flags, data, 0); - if (!err) { - lock_mount_hash(); - mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK; - mnt->mnt.mnt_flags = mnt_flags; - touch_mnt_namespace(mnt->mnt_ns); - unlock_mount_hash(); + if (!err) + set_mount_attributes(mnt, mnt_flags); } up_write(&sb->s_umount); return err; @@ -2788,7 +2832,9 @@ long do_mount(const char *dev_name, const char __user *dir_name, SB_LAZYTIME | SB_I_VERSION); - if (flags & MS_REMOUNT) + if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND)) + retval = do_reconfigure_mnt(&path, mnt_flags); + else if (flags & MS_REMOUNT) retval = do_remount(&path, flags, sb_flags, mnt_flags, data_page); else if (flags & MS_BIND) diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h @@ -123,7 +123,7 @@ struct nfs_parsed_mount_data { unsigned short protocol; } nfs_server; - struct security_mnt_opts lsm_opts; + void *lsm_opts; struct net *net; }; diff --git a/fs/nfs/super.c b/fs/nfs/super.c @@ -929,7 +929,7 @@ static struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void) data->minorversion = 0; data->need_mount = true; data->net = current->nsproxy->net_ns; - security_init_mnt_opts(&data->lsm_opts); + data->lsm_opts = NULL; } return data; } @@ -1206,7 +1206,7 @@ static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option, static int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt) { - char *p, *string, *secdata; + char *p, *string; int rc, sloppy = 0, invalid_option = 0; unsigned short protofamily = AF_UNSPEC; unsigned short mountfamily = AF_UNSPEC; @@ -1217,20 +1217,10 @@ static int nfs_parse_mount_options(char *raw, } dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw); - secdata = alloc_secdata(); - if (!secdata) - goto out_nomem; - - rc = security_sb_copy_data(raw, secdata); + rc = security_sb_eat_lsm_opts(raw, &mnt->lsm_opts); if (rc) goto out_security_failure; - rc = security_sb_parse_opts_str(secdata, &mnt->lsm_opts); - if (rc) - goto out_security_failure; - - free_secdata(secdata); - while ((p = strsep(&raw, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; unsigned long option; @@ -1682,7 +1672,6 @@ out_nomem: printk(KERN_INFO "NFS: not enough memory to parse option\n"); return 0; out_security_failure: - free_secdata(secdata); printk(KERN_INFO "NFS: security options invalid: %d\n", rc); return 0; } @@ -2081,14 +2070,9 @@ static int nfs23_validate_mount_data(void *options, if (data->context[0]){ #ifdef CONFIG_SECURITY_SELINUX int rc; - char *opts_str = kmalloc(sizeof(data->context) + 8, GFP_KERNEL); - if (!opts_str) - return -ENOMEM; - strcpy(opts_str, "context="); data->context[NFS_MAX_CONTEXT_LEN] = '\0'; - strcat(opts_str, &data->context[0]); - rc = security_sb_parse_opts_str(opts_str, &args->lsm_opts); - kfree(opts_str); + rc = security_add_mnt_opt("context", data->context, + strlen(data->context), &args->lsm_opts); if (rc) return rc; #else @@ -2271,7 +2255,7 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data) options->version <= 6)))) return 0; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = nfs_alloc_parsed_mount_data(); if (data == NULL) return -ENOMEM; @@ -2310,8 +2294,10 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data) /* compare new mount options with old ones */ error = nfs_compare_remount_data(nfss, data); + if (!error) + error = security_sb_remount(sb, data->lsm_opts); out: - kfree(data); + nfs_free_parsed_mount_data(data); return error; } EXPORT_SYMBOL_GPL(nfs_remount); @@ -2548,7 +2534,7 @@ int nfs_set_sb_security(struct super_block *s, struct dentry *mntroot, if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL) kflags |= SECURITY_LSM_NATIVE_LABELS; - error = security_sb_set_mnt_opts(s, &mount_info->parsed->lsm_opts, + error = security_sb_set_mnt_opts(s, mount_info->parsed->lsm_opts, kflags, &kflags_out); if (error) goto err; diff --git a/fs/pnode.c b/fs/pnode.c @@ -10,6 +10,7 @@ #include <linux/mount.h> #include <linux/fs.h> #include <linux/nsproxy.h> +#include <uapi/linux/mount.h> #include "internal.h" #include "pnode.h" diff --git a/fs/super.c b/fs/super.c @@ -35,6 +35,7 @@ #include <linux/fsnotify.h> #include <linux/lockdep.h> #include <linux/user_namespace.h> +#include <uapi/linux/mount.h> #include "internal.h" static int thaw_super_locked(struct super_block *sb); @@ -1245,17 +1246,13 @@ mount_fs(struct file_system_type *type, int flags, const char *name, void *data) { struct dentry *root; struct super_block *sb; - char *secdata = NULL; int error = -ENOMEM; + void *sec_opts = NULL; if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) { - secdata = alloc_secdata(); - if (!secdata) - goto out; - - error = security_sb_copy_data(data, secdata); + error = security_sb_eat_lsm_opts(data, &sec_opts); if (error) - goto out_free_secdata; + return ERR_PTR(error); } root = type->mount(type, flags, name, data); @@ -1276,10 +1273,16 @@ mount_fs(struct file_system_type *type, int flags, const char *name, void *data) smp_wmb(); sb->s_flags |= SB_BORN; - error = security_sb_kern_mount(sb, flags, secdata); + error = security_sb_set_mnt_opts(sb, sec_opts, 0, NULL); if (error) goto out_sb; + if (!(flags & (MS_KERNMOUNT|MS_SUBMOUNT))) { + error = security_sb_kern_mount(sb); + if (error) + goto out_sb; + } + /* * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE * but s_maxbytes was an unsigned long long for many releases. Throw @@ -1290,14 +1293,13 @@ mount_fs(struct file_system_type *type, int flags, const char *name, void *data) "negative value (%lld)\n", type->name, sb->s_maxbytes); up_write(&sb->s_umount); - free_secdata(secdata); + security_free_mnt_opts(&sec_opts); return root; out_sb: dput(root); deactivate_locked_super(sb); out_free_secdata: - free_secdata(secdata); -out: + security_free_mnt_opts(&sec_opts); return ERR_PTR(error); } diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h @@ -1461,9 +1461,10 @@ union security_list_options { int (*sb_alloc_security)(struct super_block *sb); void (*sb_free_security)(struct super_block *sb); - int (*sb_copy_data)(char *orig, char *copy); - int (*sb_remount)(struct super_block *sb, void *data); - int (*sb_kern_mount)(struct super_block *sb, int flags, void *data); + void (*sb_free_mnt_opts)(void *mnt_opts); + int (*sb_eat_lsm_opts)(char *orig, void **mnt_opts); + int (*sb_remount)(struct super_block *sb, void *mnt_opts); + int (*sb_kern_mount)(struct super_block *sb); int (*sb_show_options)(struct seq_file *m, struct super_block *sb); int (*sb_statfs)(struct dentry *dentry); int (*sb_mount)(const char *dev_name, const struct path *path, @@ -1471,14 +1472,15 @@ union security_list_options { int (*sb_umount)(struct vfsmount *mnt, int flags); int (*sb_pivotroot)(const struct path *old_path, const struct path *new_path); int (*sb_set_mnt_opts)(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags); int (*sb_clone_mnt_opts)(const struct super_block *oldsb, struct super_block *newsb, unsigned long kern_flags, unsigned long *set_kern_flags); - int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts); + int (*sb_add_mnt_opt)(const char *option, const char *val, int len, + void **mnt_opts); int (*dentry_init_security)(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, u32 *ctxlen); @@ -1800,7 +1802,8 @@ struct security_hook_heads { struct hlist_head bprm_committed_creds; struct hlist_head sb_alloc_security; struct hlist_head sb_free_security; - struct hlist_head sb_copy_data; + struct hlist_head sb_free_mnt_opts; + struct hlist_head sb_eat_lsm_opts; struct hlist_head sb_remount; struct hlist_head sb_kern_mount; struct hlist_head sb_show_options; @@ -1810,7 +1813,7 @@ struct security_hook_heads { struct hlist_head sb_pivotroot; struct hlist_head sb_set_mnt_opts; struct hlist_head sb_clone_mnt_opts; - struct hlist_head sb_parse_opts_str; + struct hlist_head sb_add_mnt_opt; struct hlist_head dentry_init_security; struct hlist_head dentry_create_files_as; #ifdef CONFIG_SECURITY_PATH diff --git a/include/linux/mount.h b/include/linux/mount.h @@ -81,7 +81,7 @@ extern void mnt_drop_write_file(struct file *file); extern void mntput(struct vfsmount *mnt); extern struct vfsmount *mntget(struct vfsmount *mnt); extern struct vfsmount *mnt_clone_internal(const struct path *path); -extern int __mnt_is_readonly(struct vfsmount *mnt); +extern bool __mnt_is_readonly(struct vfsmount *mnt); extern bool mnt_may_suid(struct vfsmount *mnt); struct path; diff --git a/include/linux/security.h b/include/linux/security.h @@ -182,36 +182,10 @@ static inline const char *kernel_load_data_id_str(enum kernel_load_data_id id) #ifdef CONFIG_SECURITY -struct security_mnt_opts { - char **mnt_opts; - int *mnt_opts_flags; - int num_mnt_opts; -}; - int call_lsm_notifier(enum lsm_event event, void *data); int register_lsm_notifier(struct notifier_block *nb); int unregister_lsm_notifier(struct notifier_block *nb); -static inline void security_init_mnt_opts(struct security_mnt_opts *opts) -{ - opts->mnt_opts = NULL; - opts->mnt_opts_flags = NULL; - opts->num_mnt_opts = 0; -} - -static inline void security_free_mnt_opts(struct security_mnt_opts *opts) -{ - int i; - if (opts->mnt_opts) - for (i = 0; i < opts->num_mnt_opts; i++) - kfree(opts->mnt_opts[i]); - kfree(opts->mnt_opts); - opts->mnt_opts = NULL; - kfree(opts->mnt_opts_flags); - opts->mnt_opts_flags = NULL; - opts->num_mnt_opts = 0; -} - /* prototypes */ extern int security_init(void); @@ -248,9 +222,10 @@ void security_bprm_committing_creds(struct linux_binprm *bprm); void security_bprm_committed_creds(struct linux_binprm *bprm); int security_sb_alloc(struct super_block *sb); void security_sb_free(struct super_block *sb); -int security_sb_copy_data(char *orig, char *copy); -int security_sb_remount(struct super_block *sb, void *data); -int security_sb_kern_mount(struct super_block *sb, int flags, void *data); +void security_free_mnt_opts(void **mnt_opts); +int security_sb_eat_lsm_opts(char *options, void **mnt_opts); +int security_sb_remount(struct super_block *sb, void *mnt_opts); +int security_sb_kern_mount(struct super_block *sb); int security_sb_show_options(struct seq_file *m, struct super_block *sb); int security_sb_statfs(struct dentry *dentry); int security_sb_mount(const char *dev_name, const struct path *path, @@ -258,14 +233,15 @@ int security_sb_mount(const char *dev_name, const struct path *path, int security_sb_umount(struct vfsmount *mnt, int flags); int security_sb_pivotroot(const struct path *old_path, const struct path *new_path); int security_sb_set_mnt_opts(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags); int security_sb_clone_mnt_opts(const struct super_block *oldsb, struct super_block *newsb, unsigned long kern_flags, unsigned long *set_kern_flags); -int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); +int security_add_mnt_opt(const char *option, const char *val, + int len, void **mnt_opts); int security_dentry_init_security(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, u32 *ctxlen); @@ -403,8 +379,6 @@ int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen); int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen); int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen); #else /* CONFIG_SECURITY */ -struct security_mnt_opts { -}; static inline int call_lsm_notifier(enum lsm_event event, void *data) { @@ -421,11 +395,7 @@ static inline int unregister_lsm_notifier(struct notifier_block *nb) return 0; } -static inline void security_init_mnt_opts(struct security_mnt_opts *opts) -{ -} - -static inline void security_free_mnt_opts(struct security_mnt_opts *opts) +static inline void security_free_mnt_opts(void **mnt_opts) { } @@ -555,17 +525,19 @@ static inline int security_sb_alloc(struct super_block *sb) static inline void security_sb_free(struct super_block *sb) { } -static inline int security_sb_copy_data(char *orig, char *copy) +static inline int security_sb_eat_lsm_opts(char *options, + void **mnt_opts) { return 0; } -static inline int security_sb_remount(struct super_block *sb, void *data) +static inline int security_sb_remount(struct super_block *sb, + void *mnt_opts) { return 0; } -static inline int security_sb_kern_mount(struct super_block *sb, int flags, void *data) +static inline int security_sb_kern_mount(struct super_block *sb) { return 0; } @@ -600,7 +572,7 @@ static inline int security_sb_pivotroot(const struct path *old_path, } static inline int security_sb_set_mnt_opts(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags) { @@ -615,7 +587,8 @@ static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, return 0; } -static inline int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) +static inline int security_add_mnt_opt(const char *option, const char *val, + int len, void **mnt_opts) { return 0; } @@ -1820,28 +1793,5 @@ static inline void security_bpf_prog_free(struct bpf_prog_aux *aux) #endif /* CONFIG_SECURITY */ #endif /* CONFIG_BPF_SYSCALL */ -#ifdef CONFIG_SECURITY - -static inline char *alloc_secdata(void) -{ - return (char *)get_zeroed_page(GFP_KERNEL); -} - -static inline void free_secdata(void *secdata) -{ - free_page((unsigned long)secdata); -} - -#else - -static inline char *alloc_secdata(void) -{ - return (char *)1; -} - -static inline void free_secdata(void *secdata) -{ } -#endif /* CONFIG_SECURITY */ - #endif /* ! __LINUX_SECURITY_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h @@ -14,6 +14,11 @@ #include <linux/ioctl.h> #include <linux/types.h> +/* Use of MS_* flags within the kernel is restricted to core mount(2) code. */ +#if !defined(__KERNEL__) +#include <linux/mount.h> +#endif + /* * It's silly to have NR_OPEN bigger than NR_FILE, but you can change * the file limit at runtime and only root can increase the per-process @@ -101,57 +106,6 @@ struct inodes_stat_t { #define NR_FILE 8192 /* this can well be larger on a larger system */ - -/* - * These are the fs-independent mount-flags: up to 32 flags are supported - */ -#define MS_RDONLY 1 /* Mount read-only */ -#define MS_NOSUID 2 /* Ignore suid and sgid bits */ -#define MS_NODEV 4 /* Disallow access to device special files */ -#define MS_NOEXEC 8 /* Disallow program execution */ -#define MS_SYNCHRONOUS 16 /* Writes are synced at once */ -#define MS_REMOUNT 32 /* Alter flags of a mounted FS */ -#define MS_MANDLOCK 64 /* Allow mandatory locks on an FS */ -#define MS_DIRSYNC 128 /* Directory modifications are synchronous */ -#define MS_NOATIME 1024 /* Do not update access times. */ -#define MS_NODIRATIME 2048 /* Do not update directory access times */ -#define MS_BIND 4096 -#define MS_MOVE 8192 -#define MS_REC 16384 -#define MS_VERBOSE 32768 /* War is peace. Verbosity is silence. - MS_VERBOSE is deprecated. */ -#define MS_SILENT 32768 -#define MS_POSIXACL (1<<16) /* VFS does not apply the umask */ -#define MS_UNBINDABLE (1<<17) /* change to unbindable */ -#define MS_PRIVATE (1<<18) /* change to private */ -#define MS_SLAVE (1<<19) /* change to slave */ -#define MS_SHARED (1<<20) /* change to shared */ -#define MS_RELATIME (1<<21) /* Update atime relative to mtime/ctime. */ -#define MS_KERNMOUNT (1<<22) /* this is a kern_mount call */ -#define MS_I_VERSION (1<<23) /* Update inode I_version field */ -#define MS_STRICTATIME (1<<24) /* Always perform atime updates */ -#define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */ - -/* These sb flags are internal to the kernel */ -#define MS_SUBMOUNT (1<<26) -#define MS_NOREMOTELOCK (1<<27) -#define MS_NOSEC (1<<28) -#define MS_BORN (1<<29) -#define MS_ACTIVE (1<<30) -#define MS_NOUSER (1<<31) - -/* - * Superblock flags that can be altered by MS_REMOUNT - */ -#define MS_RMT_MASK (MS_RDONLY|MS_SYNCHRONOUS|MS_MANDLOCK|MS_I_VERSION|\ - MS_LAZYTIME) - -/* - * Old magic mount flag and mask - */ -#define MS_MGC_VAL 0xC0ED0000 -#define MS_MGC_MSK 0xffff0000 - /* * Structure for FS_IOC_FSGETXATTR[A] and FS_IOC_FSSETXATTR. */ diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h @@ -0,0 +1,58 @@ +#ifndef _UAPI_LINUX_MOUNT_H +#define _UAPI_LINUX_MOUNT_H + +/* + * These are the fs-independent mount-flags: up to 32 flags are supported + * + * Usage of these is restricted within the kernel to core mount(2) code and + * callers of sys_mount() only. Filesystems should be using the SB_* + * equivalent instead. + */ +#define MS_RDONLY 1 /* Mount read-only */ +#define MS_NOSUID 2 /* Ignore suid and sgid bits */ +#define MS_NODEV 4 /* Disallow access to device special files */ +#define MS_NOEXEC 8 /* Disallow program execution */ +#define MS_SYNCHRONOUS 16 /* Writes are synced at once */ +#define MS_REMOUNT 32 /* Alter flags of a mounted FS */ +#define MS_MANDLOCK 64 /* Allow mandatory locks on an FS */ +#define MS_DIRSYNC 128 /* Directory modifications are synchronous */ +#define MS_NOATIME 1024 /* Do not update access times. */ +#define MS_NODIRATIME 2048 /* Do not update directory access times */ +#define MS_BIND 4096 +#define MS_MOVE 8192 +#define MS_REC 16384 +#define MS_VERBOSE 32768 /* War is peace. Verbosity is silence. + MS_VERBOSE is deprecated. */ +#define MS_SILENT 32768 +#define MS_POSIXACL (1<<16) /* VFS does not apply the umask */ +#define MS_UNBINDABLE (1<<17) /* change to unbindable */ +#define MS_PRIVATE (1<<18) /* change to private */ +#define MS_SLAVE (1<<19) /* change to slave */ +#define MS_SHARED (1<<20) /* change to shared */ +#define MS_RELATIME (1<<21) /* Update atime relative to mtime/ctime. */ +#define MS_KERNMOUNT (1<<22) /* this is a kern_mount call */ +#define MS_I_VERSION (1<<23) /* Update inode I_version field */ +#define MS_STRICTATIME (1<<24) /* Always perform atime updates */ +#define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */ + +/* These sb flags are internal to the kernel */ +#define MS_SUBMOUNT (1<<26) +#define MS_NOREMOTELOCK (1<<27) +#define MS_NOSEC (1<<28) +#define MS_BORN (1<<29) +#define MS_ACTIVE (1<<30) +#define MS_NOUSER (1<<31) + +/* + * Superblock flags that can be altered by MS_REMOUNT + */ +#define MS_RMT_MASK (MS_RDONLY|MS_SYNCHRONOUS|MS_MANDLOCK|MS_I_VERSION|\ + MS_LAZYTIME) + +/* + * Old magic mount flag and mask + */ +#define MS_MGC_VAL 0xC0ED0000 +#define MS_MGC_MSK 0xffff0000 + +#endif /* _UAPI_LINUX_MOUNT_H */ diff --git a/init/do_mounts.c b/init/do_mounts.c @@ -22,6 +22,7 @@ #include <linux/nfs_fs.h> #include <linux/nfs_fs_sb.h> #include <linux/nfs_mount.h> +#include <uapi/linux/mount.h> #include "do_mounts.h" diff --git a/init/do_mounts_initrd.c b/init/do_mounts_initrd.c @@ -8,6 +8,7 @@ #include <linux/sched.h> #include <linux/freezer.h> #include <linux/kmod.h> +#include <uapi/linux/mount.h> #include "do_mounts.h" diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c @@ -26,6 +26,7 @@ #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv6.h> #include <net/sock.h> +#include <uapi/linux/mount.h> #include "include/apparmor.h" #include "include/apparmorfs.h" diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c @@ -15,6 +15,7 @@ #include <linux/fs.h> #include <linux/mount.h> #include <linux/namei.h> +#include <uapi/linux/mount.h> #include "include/apparmor.h" #include "include/audit.h" diff --git a/security/security.c b/security/security.c @@ -384,20 +384,31 @@ void security_sb_free(struct super_block *sb) call_void_hook(sb_free_security, sb); } -int security_sb_copy_data(char *orig, char *copy) +void security_free_mnt_opts(void **mnt_opts) { - return call_int_hook(sb_copy_data, 0, orig, copy); + if (!*mnt_opts) + return; + call_void_hook(sb_free_mnt_opts, *mnt_opts); + *mnt_opts = NULL; +} +EXPORT_SYMBOL(security_free_mnt_opts); + +int security_sb_eat_lsm_opts(char *options, void **mnt_opts) +{ + return call_int_hook(sb_eat_lsm_opts, 0, options, mnt_opts); } -EXPORT_SYMBOL(security_sb_copy_data); +EXPORT_SYMBOL(security_sb_eat_lsm_opts); -int security_sb_remount(struct super_block *sb, void *data) +int security_sb_remount(struct super_block *sb, + void *mnt_opts) { - return call_int_hook(sb_remount, 0, sb, data); + return call_int_hook(sb_remount, 0, sb, mnt_opts); } +EXPORT_SYMBOL(security_sb_remount); -int security_sb_kern_mount(struct super_block *sb, int flags, void *data) +int security_sb_kern_mount(struct super_block *sb) { - return call_int_hook(sb_kern_mount, 0, sb, flags, data); + return call_int_hook(sb_kern_mount, 0, sb); } int security_sb_show_options(struct seq_file *m, struct super_block *sb) @@ -427,13 +438,13 @@ int security_sb_pivotroot(const struct path *old_path, const struct path *new_pa } int security_sb_set_mnt_opts(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags) { return call_int_hook(sb_set_mnt_opts, - opts->num_mnt_opts ? -EOPNOTSUPP : 0, sb, - opts, kern_flags, set_kern_flags); + mnt_opts ? -EOPNOTSUPP : 0, sb, + mnt_opts, kern_flags, set_kern_flags); } EXPORT_SYMBOL(security_sb_set_mnt_opts); @@ -447,11 +458,13 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb, } EXPORT_SYMBOL(security_sb_clone_mnt_opts); -int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) +int security_add_mnt_opt(const char *option, const char *val, int len, + void **mnt_opts) { - return call_int_hook(sb_parse_opts_str, 0, options, opts); + return call_int_hook(sb_add_mnt_opt, -EINVAL, + option, val, len, mnt_opts); } -EXPORT_SYMBOL(security_sb_parse_opts_str); +EXPORT_SYMBOL(security_add_mnt_opt); int security_inode_alloc(struct inode *inode) { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c @@ -88,6 +88,7 @@ #include <linux/msg.h> #include <linux/shm.h> #include <linux/bpf.h> +#include <uapi/linux/mount.h> #include "avc.h" #include "objsec.h" @@ -432,6 +433,20 @@ static void superblock_free_security(struct super_block *sb) kfree(sbsec); } +struct selinux_mnt_opts { + const char *fscontext, *context, *rootcontext, *defcontext; +}; + +static void selinux_free_mnt_opts(void *mnt_opts) +{ + struct selinux_mnt_opts *opts = mnt_opts; + kfree(opts->fscontext); + kfree(opts->context); + kfree(opts->rootcontext); + kfree(opts->defcontext); + kfree(opts); +} + static inline int inode_doinit(struct inode *inode) { return inode_doinit_with_dentry(inode, NULL); @@ -443,20 +458,42 @@ enum { Opt_fscontext = 2, Opt_defcontext = 3, Opt_rootcontext = 4, - Opt_labelsupport = 5, - Opt_nextmntopt = 6, + Opt_seclabel = 5, }; -#define NUM_SEL_MNT_OPTS (Opt_nextmntopt - 1) - -static const match_table_t tokens = { - {Opt_context, CONTEXT_STR "%s"}, - {Opt_fscontext, FSCONTEXT_STR "%s"}, - {Opt_defcontext, DEFCONTEXT_STR "%s"}, - {Opt_rootcontext, ROOTCONTEXT_STR "%s"}, - {Opt_labelsupport, LABELSUPP_STR}, - {Opt_error, NULL}, +#define A(s, has_arg) {#s, sizeof(#s) - 1, Opt_##s, has_arg} +static struct { + const char *name; + int len; + int opt; + bool has_arg; +} tokens[] = { + A(context, true), + A(fscontext, true), + A(defcontext, true), + A(rootcontext, true), + A(seclabel, false), }; +#undef A + +static int match_opt_prefix(char *s, int l, char **arg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tokens); i++) { + size_t len = tokens[i].len; + if (len > l || memcmp(s, tokens[i].name, len)) + continue; + if (tokens[i].has_arg) { + if (len == l || s[len] != '=') + continue; + *arg = s + len + 1; + } else if (len != l) + continue; + return tokens[i].opt; + } + return Opt_error; +} #define SEL_MOUNT_FAIL_MSG "SELinux: duplicate or incompatible mount options\n" @@ -570,10 +607,9 @@ static int sb_finish_set_opts(struct super_block *sb) during get_sb by a pseudo filesystem that directly populates itself. */ spin_lock(&sbsec->isec_lock); -next_inode: - if (!list_empty(&sbsec->isec_head)) { + while (!list_empty(&sbsec->isec_head)) { struct inode_security_struct *isec = - list_entry(sbsec->isec_head.next, + list_first_entry(&sbsec->isec_head, struct inode_security_struct, list); struct inode *inode = isec->inode; list_del_init(&isec->list); @@ -585,112 +621,12 @@ next_inode: iput(inode); } spin_lock(&sbsec->isec_lock); - goto next_inode; } spin_unlock(&sbsec->isec_lock); out: return rc; } -/* - * This function should allow an FS to ask what it's mount security - * options were so it can use those later for submounts, displaying - * mount options, or whatever. - */ -static int selinux_get_mnt_opts(const struct super_block *sb, - struct security_mnt_opts *opts) -{ - int rc = 0, i; - struct superblock_security_struct *sbsec = sb->s_security; - char *context = NULL; - u32 len; - char tmp; - - security_init_mnt_opts(opts); - - if (!(sbsec->flags & SE_SBINITIALIZED)) - return -EINVAL; - - if (!selinux_state.initialized) - return -EINVAL; - - /* make sure we always check enough bits to cover the mask */ - BUILD_BUG_ON(SE_MNTMASK >= (1 << NUM_SEL_MNT_OPTS)); - - tmp = sbsec->flags & SE_MNTMASK; - /* count the number of mount options for this sb */ - for (i = 0; i < NUM_SEL_MNT_OPTS; i++) { - if (tmp & 0x01) - opts->num_mnt_opts++; - tmp >>= 1; - } - /* Check if the Label support flag is set */ - if (sbsec->flags & SBLABEL_MNT) - opts->num_mnt_opts++; - - opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); - if (!opts->mnt_opts) { - rc = -ENOMEM; - goto out_free; - } - - opts->mnt_opts_flags = kcalloc(opts->num_mnt_opts, sizeof(int), GFP_ATOMIC); - if (!opts->mnt_opts_flags) { - rc = -ENOMEM; - goto out_free; - } - - i = 0; - if (sbsec->flags & FSCONTEXT_MNT) { - rc = security_sid_to_context(&selinux_state, sbsec->sid, - &context, &len); - if (rc) - goto out_free; - opts->mnt_opts[i] = context; - opts->mnt_opts_flags[i++] = FSCONTEXT_MNT; - } - if (sbsec->flags & CONTEXT_MNT) { - rc = security_sid_to_context(&selinux_state, - sbsec->mntpoint_sid, - &context, &len); - if (rc) - goto out_free; - opts->mnt_opts[i] = context; - opts->mnt_opts_flags[i++] = CONTEXT_MNT; - } - if (sbsec->flags & DEFCONTEXT_MNT) { - rc = security_sid_to_context(&selinux_state, sbsec->def_sid, - &context, &len); - if (rc) - goto out_free; - opts->mnt_opts[i] = context; - opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT; - } - if (sbsec->flags & ROOTCONTEXT_MNT) { - struct dentry *root = sbsec->sb->s_root; - struct inode_security_struct *isec = backing_inode_security(root); - - rc = security_sid_to_context(&selinux_state, isec->sid, - &context, &len); - if (rc) - goto out_free; - opts->mnt_opts[i] = context; - opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; - } - if (sbsec->flags & SBLABEL_MNT) { - opts->mnt_opts[i] = NULL; - opts->mnt_opts_flags[i++] = SBLABEL_MNT; - } - - BUG_ON(i != opts->num_mnt_opts); - - return 0; - -out_free: - security_free_mnt_opts(opts); - return rc; -} - static int bad_option(struct superblock_security_struct *sbsec, char flag, u32 old_sid, u32 new_sid) { @@ -711,31 +647,39 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, return 0; } +static int parse_sid(struct super_block *sb, const char *s, u32 *sid) +{ + int rc = security_context_str_to_sid(&selinux_state, s, + sid, GFP_KERNEL); + if (rc) + pr_warn("SELinux: security_context_str_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + s, sb->s_id, sb->s_type->name, rc); + return rc; +} + /* * Allow filesystems with binary mount data to explicitly set mount point * labeling information. */ static int selinux_set_mnt_opts(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags) { const struct cred *cred = current_cred(); - int rc = 0, i; struct superblock_security_struct *sbsec = sb->s_security; - const char *name = sb->s_type->name; struct dentry *root = sbsec->sb->s_root; + struct selinux_mnt_opts *opts = mnt_opts; struct inode_security_struct *root_isec; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; u32 defcontext_sid = 0; - char **mount_options = opts->mnt_opts; - int *flags = opts->mnt_opts_flags; - int num_opts = opts->num_mnt_opts; + int rc = 0; mutex_lock(&sbsec->lock); if (!selinux_state.initialized) { - if (!num_opts) { + if (!opts) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security server is ready to handle calls. */ @@ -765,7 +709,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, * will be used for both mounts) */ if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - && (num_opts == 0)) + && !opts) goto out; root_isec = backing_inode_security_novalidate(root); @@ -775,68 +719,48 @@ static int selinux_set_mnt_opts(struct super_block *sb, * also check if someone is trying to mount the same sb more * than once with different security options. */ - for (i = 0; i < num_opts; i++) { - u32 sid; - - if (flags[i] == SBLABEL_MNT) - continue; - rc = security_context_str_to_sid(&selinux_state, - mount_options[i], &sid, - GFP_KERNEL); - if (rc) { - pr_warn("SELinux: security_context_str_to_sid" - "(%s) failed for (dev %s, type %s) errno=%d\n", - mount_options[i], sb->s_id, name, rc); - goto out; - } - switch (flags[i]) { - case FSCONTEXT_MNT: - fscontext_sid = sid; - + if (opts) { + if (opts->fscontext) { + rc = parse_sid(sb, opts->fscontext, &fscontext_sid); + if (rc) + goto out; if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, fscontext_sid)) goto out_double_mount; - sbsec->flags |= FSCONTEXT_MNT; - break; - case CONTEXT_MNT: - context_sid = sid; - + } + if (opts->context) { + rc = parse_sid(sb, opts->context, &context_sid); + if (rc) + goto out; if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, context_sid)) goto out_double_mount; - sbsec->flags |= CONTEXT_MNT; - break; - case ROOTCONTEXT_MNT: - rootcontext_sid = sid; - + } + if (opts->rootcontext) { + rc = parse_sid(sb, opts->rootcontext, &rootcontext_sid); + if (rc) + goto out; if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, rootcontext_sid)) goto out_double_mount; - sbsec->flags |= ROOTCONTEXT_MNT; - - break; - case DEFCONTEXT_MNT: - defcontext_sid = sid; - + } + if (opts->defcontext) { + rc = parse_sid(sb, opts->defcontext, &defcontext_sid); + if (rc) + goto out; if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, defcontext_sid)) goto out_double_mount; - sbsec->flags |= DEFCONTEXT_MNT; - - break; - default: - rc = -EINVAL; - goto out; } } if (sbsec->flags & SE_SBINITIALIZED) { /* previously mounted with options, but not on this attempt? */ - if ((sbsec->flags & SE_MNTMASK) && !num_opts) + if ((sbsec->flags & SE_MNTMASK) && !opts) goto out_double_mount; rc = 0; goto out; @@ -969,7 +893,8 @@ out: out_double_mount: rc = -EINVAL; pr_warn("SELinux: mount invalid. Same superblock, different " - "security settings for (dev %s, type %s)\n", sb->s_id, name); + "security settings for (dev %s, type %s)\n", sb->s_id, + sb->s_type->name); goto out; } @@ -1081,218 +1006,145 @@ out: return rc; } -static int selinux_parse_opts_str(char *options, - struct security_mnt_opts *opts) +static int selinux_add_opt(int token, const char *s, void **mnt_opts) { - char *p; - char *context = NULL, *defcontext = NULL; - char *fscontext = NULL, *rootcontext = NULL; - int rc, num_mnt_opts = 0; - - opts->num_mnt_opts = 0; + struct selinux_mnt_opts *opts = *mnt_opts; - /* Standard string-based options. */ - while ((p = strsep(&options, "|")) != NULL) { - int token; - substring_t args[MAX_OPT_ARGS]; - - if (!*p) - continue; - - token = match_token(p, tokens, args); - - switch (token) { - case Opt_context: - if (context || defcontext) { - rc = -EINVAL; - pr_warn(SEL_MOUNT_FAIL_MSG); - goto out_err; - } - context = match_strdup(&args[0]); - if (!context) { - rc = -ENOMEM; - goto out_err; - } - break; - - case Opt_fscontext: - if (fscontext) { - rc = -EINVAL; - pr_warn(SEL_MOUNT_FAIL_MSG); - goto out_err; - } - fscontext = match_strdup(&args[0]); - if (!fscontext) { - rc = -ENOMEM; - goto out_err; - } - break; - - case Opt_rootcontext: - if (rootcontext) { - rc = -EINVAL; - pr_warn(SEL_MOUNT_FAIL_MSG); - goto out_err; - } - rootcontext = match_strdup(&args[0]); - if (!rootcontext) { - rc = -ENOMEM; - goto out_err; - } - break; - - case Opt_defcontext: - if (context || defcontext) { - rc = -EINVAL; - pr_warn(SEL_MOUNT_FAIL_MSG); - goto out_err; - } - defcontext = match_strdup(&args[0]); - if (!defcontext) { - rc = -ENOMEM; - goto out_err; - } - break; - case Opt_labelsupport: - break; - default: - rc = -EINVAL; - pr_warn("SELinux: unknown mount option\n"); - goto out_err; - - } - } - - rc = -ENOMEM; - opts->mnt_opts = kcalloc(NUM_SEL_MNT_OPTS, sizeof(char *), GFP_KERNEL); - if (!opts->mnt_opts) - goto out_err; - - opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int), - GFP_KERNEL); - if (!opts->mnt_opts_flags) - goto out_err; + if (token == Opt_seclabel) /* eaten and completely ignored */ + return 0; - if (fscontext) { - opts->mnt_opts[num_mnt_opts] = fscontext; - opts->mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; - } - if (context) { - opts->mnt_opts[num_mnt_opts] = context; - opts->mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; - } - if (rootcontext) { - opts->mnt_opts[num_mnt_opts] = rootcontext; - opts->mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; + if (!opts) { + opts = kzalloc(sizeof(struct selinux_mnt_opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + *mnt_opts = opts; } - if (defcontext) { - opts->mnt_opts[num_mnt_opts] = defcontext; - opts->mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; + if (!s) + return -ENOMEM; + switch (token) { + case Opt_context: + if (opts->context || opts->defcontext) + goto Einval; + opts->context = s; + break; + case Opt_fscontext: + if (opts->fscontext) + goto Einval; + opts->fscontext = s; + break; + case Opt_rootcontext: + if (opts->rootcontext) + goto Einval; + opts->rootcontext = s; + break; + case Opt_defcontext: + if (opts->context || opts->defcontext) + goto Einval; + opts->defcontext = s; + break; } - - opts->num_mnt_opts = num_mnt_opts; return 0; - -out_err: - security_free_mnt_opts(opts); - kfree(context); - kfree(defcontext); - kfree(fscontext); - kfree(rootcontext); - return rc; +Einval: + pr_warn(SEL_MOUNT_FAIL_MSG); + return -EINVAL; } -/* - * string mount options parsing and call set the sbsec - */ -static int superblock_doinit(struct super_block *sb, void *data) -{ - int rc = 0; - char *options = data; - struct security_mnt_opts opts; - - security_init_mnt_opts(&opts); - - if (!data) - goto out; - BUG_ON(sb->s_type->fs_flags & FS_BINARY_MOUNTDATA); +static int selinux_add_mnt_opt(const char *option, const char *val, int len, + void **mnt_opts) +{ + int token = Opt_error; + int rc, i; - rc = selinux_parse_opts_str(options, &opts); - if (rc) - goto out_err; + for (i = 0; i < ARRAY_SIZE(tokens); i++) { + if (strcmp(option, tokens[i].name) == 0) { + token = tokens[i].opt; + break; + } + } -out: - rc = selinux_set_mnt_opts(sb, &opts, 0, NULL); + if (token == Opt_error) + return -EINVAL; -out_err: - security_free_mnt_opts(&opts); + if (token != Opt_seclabel) + val = kmemdup_nul(val, len, GFP_KERNEL); + rc = selinux_add_opt(token, val, mnt_opts); + if (unlikely(rc)) { + kfree(val); + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + } + } return rc; } -static void selinux_write_opts(struct seq_file *m, - struct security_mnt_opts *opts) +static int show_sid(struct seq_file *m, u32 sid) { - int i; - char *prefix; - - for (i = 0; i < opts->num_mnt_opts; i++) { - char *has_comma; + char *context = NULL; + u32 len; + int rc; - if (opts->mnt_opts[i]) - has_comma = strchr(opts->mnt_opts[i], ','); - else - has_comma = NULL; + rc = security_sid_to_context(&selinux_state, sid, + &context, &len); + if (!rc) { + bool has_comma = context && strchr(context, ','); - switch (opts->mnt_opts_flags[i]) { - case CONTEXT_MNT: - prefix = CONTEXT_STR; - break; - case FSCONTEXT_MNT: - prefix = FSCONTEXT_STR; - break; - case ROOTCONTEXT_MNT: - prefix = ROOTCONTEXT_STR; - break; - case DEFCONTEXT_MNT: - prefix = DEFCONTEXT_STR; - break; - case SBLABEL_MNT: - seq_putc(m, ','); - seq_puts(m, LABELSUPP_STR); - continue; - default: - BUG(); - return; - }; - /* we need a comma before each option */ - seq_putc(m, ','); - seq_puts(m, prefix); if (has_comma) seq_putc(m, '\"'); - seq_escape(m, opts->mnt_opts[i], "\"\n\\"); + seq_escape(m, context, "\"\n\\"); if (has_comma) seq_putc(m, '\"'); } + kfree(context); + return rc; } static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) { - struct security_mnt_opts opts; + struct superblock_security_struct *sbsec = sb->s_security; int rc; - rc = selinux_get_mnt_opts(sb, &opts); - if (rc) { - /* before policy load we may get EINVAL, don't show anything */ - if (rc == -EINVAL) - rc = 0; - return rc; - } - - selinux_write_opts(m, &opts); + if (!(sbsec->flags & SE_SBINITIALIZED)) + return 0; - security_free_mnt_opts(&opts); + if (!selinux_state.initialized) + return 0; - return rc; + if (sbsec->flags & FSCONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, FSCONTEXT_STR); + rc = show_sid(m, sbsec->sid); + if (rc) + return rc; + } + if (sbsec->flags & CONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, CONTEXT_STR); + rc = show_sid(m, sbsec->mntpoint_sid); + if (rc) + return rc; + } + if (sbsec->flags & DEFCONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, DEFCONTEXT_STR); + rc = show_sid(m, sbsec->def_sid); + if (rc) + return rc; + } + if (sbsec->flags & ROOTCONTEXT_MNT) { + struct dentry *root = sbsec->sb->s_root; + struct inode_security_struct *isec = backing_inode_security(root); + seq_putc(m, ','); + seq_puts(m, ROOTCONTEXT_STR); + rc = show_sid(m, isec->sid); + if (rc) + return rc; + } + if (sbsec->flags & SBLABEL_MNT) { + seq_putc(m, ','); + seq_puts(m, LABELSUPP_STR); + } + return 0; } static inline u16 inode_mode_to_security_class(umode_t mode) @@ -2747,195 +2599,129 @@ static void selinux_sb_free_security(struct super_block *sb) superblock_free_security(sb); } -static inline int match_prefix(char *prefix, int plen, char *option, int olen) +static inline int opt_len(const char *s) { - if (plen > olen) - return 0; - - return !memcmp(prefix, option, plen); -} - -static inline int selinux_option(char *option, int len) -{ - return (match_prefix(CONTEXT_STR, sizeof(CONTEXT_STR)-1, option, len) || - match_prefix(FSCONTEXT_STR, sizeof(FSCONTEXT_STR)-1, option, len) || - match_prefix(DEFCONTEXT_STR, sizeof(DEFCONTEXT_STR)-1, option, len) || - match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len) || - match_prefix(LABELSUPP_STR, sizeof(LABELSUPP_STR)-1, option, len)); -} + bool open_quote = false; + int len; + char c; -static inline void take_option(char **to, char *from, int *first, int len) -{ - if (!*first) { - **to = ','; - *to += 1; - } else - *first = 0; - memcpy(*to, from, len); - *to += len; -} - -static inline void take_selinux_option(char **to, char *from, int *first, - int len) -{ - int current_size = 0; - - if (!*first) { - **to = '|'; - *to += 1; - } else - *first = 0; - - while (current_size < len) { - if (*from != '"') { - **to = *from; - *to += 1; - } - from += 1; - current_size += 1; + for (len = 0; (c = s[len]) != '\0'; len++) { + if (c == '"') + open_quote = !open_quote; + if (c == ',' && !open_quote) + break; } + return len; } -static int selinux_sb_copy_data(char *orig, char *copy) +static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) { - int fnosec, fsec, rc = 0; - char *in_save, *in_curr, *in_end; - char *sec_curr, *nosec_save, *nosec; - int open_quote = 0; - - in_curr = orig; - sec_curr = copy; + char *from = options; + char *to = options; + bool first = true; - nosec = (char *)get_zeroed_page(GFP_KERNEL); - if (!nosec) { - rc = -ENOMEM; - goto out; - } + while (1) { + int len = opt_len(from); + int token, rc; + char *arg = NULL; - nosec_save = nosec; - fnosec = fsec = 1; - in_save = in_end = orig; + token = match_opt_prefix(from, len, &arg); - do { - if (*in_end == '"') - open_quote = !open_quote; - if ((*in_end == ',' && open_quote == 0) || - *in_end == '\0') { - int len = in_end - in_curr; - - if (selinux_option(in_curr, len)) - take_selinux_option(&sec_curr, in_curr, &fsec, len); - else - take_option(&nosec, in_curr, &fnosec, len); + if (token != Opt_error) { + char *p, *q; - in_curr = in_end + 1; + /* strip quotes */ + if (arg) { + for (p = q = arg; p < from + len; p++) { + char c = *p; + if (c != '"') + *q++ = c; + } + arg = kmemdup_nul(arg, q - arg, GFP_KERNEL); + } + rc = selinux_add_opt(token, arg, mnt_opts); + if (unlikely(rc)) { + kfree(arg); + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + } + return rc; + } + } else { + if (!first) { // copy with preceding comma + from--; + len++; + } + if (to != from) + memmove(to, from, len); + to += len; + first = false; } - } while (*in_end++); - - strcpy(in_save, nosec_save); - free_page((unsigned long)nosec_save); -out: - return rc; + if (!from[len]) + break; + from += len + 1; + } + *to = '\0'; + return 0; } -static int selinux_sb_remount(struct super_block *sb, void *data) +static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) { - int rc, i, *flags; - struct security_mnt_opts opts; - char *secdata, **mount_options; + struct selinux_mnt_opts *opts = mnt_opts; struct superblock_security_struct *sbsec = sb->s_security; + u32 sid; + int rc; if (!(sbsec->flags & SE_SBINITIALIZED)) return 0; - if (!data) + if (!opts) return 0; - if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - return 0; - - security_init_mnt_opts(&opts); - secdata = alloc_secdata(); - if (!secdata) - return -ENOMEM; - rc = selinux_sb_copy_data(data, secdata); - if (rc) - goto out_free_secdata; - - rc = selinux_parse_opts_str(secdata, &opts); - if (rc) - goto out_free_secdata; - - mount_options = opts.mnt_opts; - flags = opts.mnt_opts_flags; - - for (i = 0; i < opts.num_mnt_opts; i++) { - u32 sid; - - if (flags[i] == SBLABEL_MNT) - continue; - rc = security_context_str_to_sid(&selinux_state, - mount_options[i], &sid, - GFP_KERNEL); - if (rc) { - pr_warn("SELinux: security_context_str_to_sid" - "(%s) failed for (dev %s, type %s) errno=%d\n", - mount_options[i], sb->s_id, sb->s_type->name, rc); - goto out_free_opts; - } - rc = -EINVAL; - switch (flags[i]) { - case FSCONTEXT_MNT: - if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid)) - goto out_bad_option; - break; - case CONTEXT_MNT: - if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid)) - goto out_bad_option; - break; - case ROOTCONTEXT_MNT: { - struct inode_security_struct *root_isec; - root_isec = backing_inode_security(sb->s_root); - - if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) - goto out_bad_option; - break; - } - case DEFCONTEXT_MNT: - if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid)) - goto out_bad_option; - break; - default: - goto out_free_opts; - } + if (opts->fscontext) { + rc = parse_sid(sb, opts->fscontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid)) + goto out_bad_option; } + if (opts->context) { + rc = parse_sid(sb, opts->context, &sid); + if (rc) + return rc; + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid)) + goto out_bad_option; + } + if (opts->rootcontext) { + struct inode_security_struct *root_isec; + root_isec = backing_inode_security(sb->s_root); + rc = parse_sid(sb, opts->rootcontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) + goto out_bad_option; + } + if (opts->defcontext) { + rc = parse_sid(sb, opts->defcontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid)) + goto out_bad_option; + } + return 0; - rc = 0; -out_free_opts: - security_free_mnt_opts(&opts); -out_free_secdata: - free_secdata(secdata); - return rc; out_bad_option: pr_warn("SELinux: unable to change security options " "during remount (dev %s, type=%s)\n", sb->s_id, sb->s_type->name); - goto out_free_opts; + return -EINVAL; } -static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) +static int selinux_sb_kern_mount(struct super_block *sb) { const struct cred *cred = current_cred(); struct common_audit_data ad; - int rc; - - rc = superblock_doinit(sb, data); - if (rc) - return rc; - - /* Allow all mounts performed by the kernel */ - if (flags & (MS_KERNMOUNT | MS_SUBMOUNT)) - return 0; ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = sb->s_root; @@ -6926,7 +6712,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security), LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), - LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data), + LSM_HOOK_INIT(sb_eat_lsm_opts, selinux_sb_eat_lsm_opts), + LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), LSM_HOOK_INIT(sb_remount, selinux_sb_remount), LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount), LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options), @@ -6935,7 +6722,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(sb_umount, selinux_umount), LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts), LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts), - LSM_HOOK_INIT(sb_parse_opts_str, selinux_parse_opts_str), + LSM_HOOK_INIT(sb_add_mnt_opt, selinux_add_mnt_opt), LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security), LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as), @@ -7196,7 +6983,7 @@ static __init int selinux_init(void) static void delayed_superblock_init(struct super_block *sb, void *unused) { - superblock_doinit(sb, NULL); + selinux_set_mnt_opts(sb, NULL, 0, NULL); } void selinux_complete_init(void) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c @@ -59,14 +59,31 @@ static LIST_HEAD(smk_ipv6_port_list); static struct kmem_cache *smack_inode_cache; int smack_enabled; -static const match_table_t smk_mount_tokens = { - {Opt_fsdefault, SMK_FSDEFAULT "%s"}, - {Opt_fsfloor, SMK_FSFLOOR "%s"}, - {Opt_fshat, SMK_FSHAT "%s"}, - {Opt_fsroot, SMK_FSROOT "%s"}, - {Opt_fstransmute, SMK_FSTRANS "%s"}, - {Opt_error, NULL}, +#define A(s) {"smack"#s, sizeof("smack"#s) - 1, Opt_##s} +static struct { + const char *name; + int len; + int opt; +} smk_mount_opts[] = { + A(fsdefault), A(fsfloor), A(fshat), A(fsroot), A(fstransmute) }; +#undef A + +static int match_opt_prefix(char *s, int l, char **arg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smk_mount_opts); i++) { + size_t len = smk_mount_opts[i].len; + if (len > l || memcmp(s, smk_mount_opts[i].name, len)) + continue; + if (len == l || s[len] != '=') + continue; + *arg = s + len + 1; + return smk_mount_opts[i].opt; + } + return Opt_error; +} #ifdef CONFIG_SECURITY_SMACK_BRINGUP static char *smk_bu_mess[] = { @@ -567,175 +584,110 @@ static void smack_sb_free_security(struct super_block *sb) sb->s_security = NULL; } -/** - * smack_sb_copy_data - copy mount options data for processing - * @orig: where to start - * @smackopts: mount options string - * - * Returns 0 on success or -ENOMEM on error. - * - * Copy the Smack specific mount options out of the mount - * options list. - */ -static int smack_sb_copy_data(char *orig, char *smackopts) -{ - char *cp, *commap, *otheropts, *dp; - - otheropts = (char *)get_zeroed_page(GFP_KERNEL); - if (otheropts == NULL) - return -ENOMEM; +struct smack_mnt_opts { + const char *fsdefault, *fsfloor, *fshat, *fsroot, *fstransmute; +}; - for (cp = orig, commap = orig; commap != NULL; cp = commap + 1) { - if (strstr(cp, SMK_FSDEFAULT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSFLOOR) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSHAT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSROOT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSTRANS) == cp) - dp = smackopts; - else - dp = otheropts; +static void smack_free_mnt_opts(void *mnt_opts) +{ + struct smack_mnt_opts *opts = mnt_opts; + kfree(opts->fsdefault); + kfree(opts->fsfloor); + kfree(opts->fshat); + kfree(opts->fsroot); + kfree(opts->fstransmute); + kfree(opts); +} - commap = strchr(cp, ','); - if (commap != NULL) - *commap = '\0'; +static int smack_add_opt(int token, const char *s, void **mnt_opts) +{ + struct smack_mnt_opts *opts = *mnt_opts; - if (*dp != '\0') - strcat(dp, ","); - strcat(dp, cp); + if (!opts) { + opts = kzalloc(sizeof(struct smack_mnt_opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + *mnt_opts = opts; } + if (!s) + return -ENOMEM; - strcpy(orig, otheropts); - free_page((unsigned long)otheropts); - + switch (token) { + case Opt_fsdefault: + if (opts->fsdefault) + goto out_opt_err; + opts->fsdefault = s; + break; + case Opt_fsfloor: + if (opts->fsfloor) + goto out_opt_err; + opts->fsfloor = s; + break; + case Opt_fshat: + if (opts->fshat) + goto out_opt_err; + opts->fshat = s; + break; + case Opt_fsroot: + if (opts->fsroot) + goto out_opt_err; + opts->fsroot = s; + break; + case Opt_fstransmute: + if (opts->fstransmute) + goto out_opt_err; + opts->fstransmute = s; + break; + } return 0; + +out_opt_err: + pr_warn("Smack: duplicate mount options\n"); + return -EINVAL; } -/** - * smack_parse_opts_str - parse Smack specific mount options - * @options: mount options string - * @opts: where to store converted mount opts - * - * Returns 0 on success or -ENOMEM on error. - * - * converts Smack specific mount options to generic security option format - */ -static int smack_parse_opts_str(char *options, - struct security_mnt_opts *opts) +static int smack_sb_eat_lsm_opts(char *options, void **mnt_opts) { - char *p; - char *fsdefault = NULL; - char *fsfloor = NULL; - char *fshat = NULL; - char *fsroot = NULL; - char *fstransmute = NULL; - int rc = -ENOMEM; - int num_mnt_opts = 0; - int token; - - opts->num_mnt_opts = 0; - - if (!options) - return 0; - - while ((p = strsep(&options, ",")) != NULL) { - substring_t args[MAX_OPT_ARGS]; + char *from = options, *to = options; + bool first = true; - if (!*p) - continue; - - token = match_token(p, smk_mount_tokens, args); + while (1) { + char *next = strchr(from, ','); + int token, len, rc; + char *arg = NULL; - switch (token) { - case Opt_fsdefault: - if (fsdefault) - goto out_opt_err; - fsdefault = match_strdup(&args[0]); - if (!fsdefault) - goto out_err; - break; - case Opt_fsfloor: - if (fsfloor) - goto out_opt_err; - fsfloor = match_strdup(&args[0]); - if (!fsfloor) - goto out_err; - break; - case Opt_fshat: - if (fshat) - goto out_opt_err; - fshat = match_strdup(&args[0]); - if (!fshat) - goto out_err; - break; - case Opt_fsroot: - if (fsroot) - goto out_opt_err; - fsroot = match_strdup(&args[0]); - if (!fsroot) - goto out_err; - break; - case Opt_fstransmute: - if (fstransmute) - goto out_opt_err; - fstransmute = match_strdup(&args[0]); - if (!fstransmute) - goto out_err; - break; - default: - rc = -EINVAL; - pr_warn("Smack: unknown mount option\n"); - goto out_err; + if (next) + len = next - from; + else + len = strlen(from); + + token = match_opt_prefix(from, len, &arg); + if (token != Opt_error) { + arg = kmemdup_nul(arg, from + len - arg, GFP_KERNEL); + rc = smack_add_opt(token, arg, mnt_opts); + if (unlikely(rc)) { + kfree(arg); + if (*mnt_opts) + smack_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + return rc; + } + } else { + if (!first) { // copy with preceding comma + from--; + len++; + } + if (to != from) + memmove(to, from, len); + to += len; + first = false; } + if (!from[len]) + break; + from += len + 1; } - - opts->mnt_opts = kcalloc(NUM_SMK_MNT_OPTS, sizeof(char *), GFP_KERNEL); - if (!opts->mnt_opts) - goto out_err; - - opts->mnt_opts_flags = kcalloc(NUM_SMK_MNT_OPTS, sizeof(int), - GFP_KERNEL); - if (!opts->mnt_opts_flags) - goto out_err; - - if (fsdefault) { - opts->mnt_opts[num_mnt_opts] = fsdefault; - opts->mnt_opts_flags[num_mnt_opts++] = FSDEFAULT_MNT; - } - if (fsfloor) { - opts->mnt_opts[num_mnt_opts] = fsfloor; - opts->mnt_opts_flags[num_mnt_opts++] = FSFLOOR_MNT; - } - if (fshat) { - opts->mnt_opts[num_mnt_opts] = fshat; - opts->mnt_opts_flags[num_mnt_opts++] = FSHAT_MNT; - } - if (fsroot) { - opts->mnt_opts[num_mnt_opts] = fsroot; - opts->mnt_opts_flags[num_mnt_opts++] = FSROOT_MNT; - } - if (fstransmute) { - opts->mnt_opts[num_mnt_opts] = fstransmute; - opts->mnt_opts_flags[num_mnt_opts++] = FSTRANS_MNT; - } - - opts->num_mnt_opts = num_mnt_opts; + *to = '\0'; return 0; - -out_opt_err: - rc = -EINVAL; - pr_warn("Smack: duplicate mount options\n"); - -out_err: - kfree(fsdefault); - kfree(fsfloor); - kfree(fshat); - kfree(fsroot); - kfree(fstransmute); - return rc; } /** @@ -751,7 +703,7 @@ out_err: * labels. */ static int smack_set_mnt_opts(struct super_block *sb, - struct security_mnt_opts *opts, + void *mnt_opts, unsigned long kern_flags, unsigned long *set_kern_flags) { @@ -760,9 +712,8 @@ static int smack_set_mnt_opts(struct super_block *sb, struct superblock_smack *sp = sb->s_security; struct inode_smack *isp; struct smack_known *skp; - int i; - int num_opts = opts->num_mnt_opts; - int transmute = 0; + struct smack_mnt_opts *opts = mnt_opts; + bool transmute = false; if (sp->smk_flags & SMK_SB_INITIALIZED) return 0; @@ -771,7 +722,7 @@ static int smack_set_mnt_opts(struct super_block *sb, /* * Unprivileged mounts don't get to specify Smack values. */ - if (num_opts) + if (opts) return -EPERM; /* * Unprivileged mounts get root and default from the caller. @@ -787,48 +738,44 @@ static int smack_set_mnt_opts(struct super_block *sb, if (sb->s_user_ns != &init_user_ns && sb->s_magic != SYSFS_MAGIC && sb->s_magic != TMPFS_MAGIC && sb->s_magic != RAMFS_MAGIC) { - transmute = 1; + transmute = true; sp->smk_flags |= SMK_SB_UNTRUSTED; } } sp->smk_flags |= SMK_SB_INITIALIZED; - for (i = 0; i < num_opts; i++) { - switch (opts->mnt_opts_flags[i]) { - case FSDEFAULT_MNT: - skp = smk_import_entry(opts->mnt_opts[i], 0); + if (opts) { + if (opts->fsdefault) { + skp = smk_import_entry(opts->fsdefault, 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_default = skp; - break; - case FSFLOOR_MNT: - skp = smk_import_entry(opts->mnt_opts[i], 0); + } + if (opts->fsfloor) { + skp = smk_import_entry(opts->fsfloor, 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_floor = skp; - break; - case FSHAT_MNT: - skp = smk_import_entry(opts->mnt_opts[i], 0); + } + if (opts->fshat) { + skp = smk_import_entry(opts->fshat, 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_hat = skp; - break; - case FSROOT_MNT: - skp = smk_import_entry(opts->mnt_opts[i], 0); + } + if (opts->fsroot) { + skp = smk_import_entry(opts->fsroot, 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_root = skp; - break; - case FSTRANS_MNT: - skp = smk_import_entry(opts->mnt_opts[i], 0); + } + if (opts->fstransmute) { + skp = smk_import_entry(opts->fstransmute, 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_root = skp; - transmute = 1; - break; - default: - break; + transmute = true; } } @@ -851,37 +798,6 @@ static int smack_set_mnt_opts(struct super_block *sb, } /** - * smack_sb_kern_mount - Smack specific mount processing - * @sb: the file system superblock - * @flags: the mount flags - * @data: the smack mount options - * - * Returns 0 on success, an error code on failure - */ -static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) -{ - int rc = 0; - char *options = data; - struct security_mnt_opts opts; - - security_init_mnt_opts(&opts); - - if (!options) - goto out; - - rc = smack_parse_opts_str(options, &opts); - if (rc) - goto out_err; - -out: - rc = smack_set_mnt_opts(sb, &opts, 0, NULL); - -out_err: - security_free_mnt_opts(&opts); - return rc; -} - -/** * smack_sb_statfs - Smack check on statfs * @dentry: identifies the file system in question * @@ -4673,11 +4589,10 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), LSM_HOOK_INIT(sb_free_security, smack_sb_free_security), - LSM_HOOK_INIT(sb_copy_data, smack_sb_copy_data), - LSM_HOOK_INIT(sb_kern_mount, smack_sb_kern_mount), + LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), + LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), LSM_HOOK_INIT(sb_set_mnt_opts, smack_set_mnt_opts), - LSM_HOOK_INIT(sb_parse_opts_str, smack_parse_opts_str), LSM_HOOK_INIT(bprm_set_creds, smack_bprm_set_creds), diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c @@ -6,6 +6,7 @@ */ #include <linux/slab.h> +#include <uapi/linux/mount.h> #include "common.h" /* String table for special mount operations. */