SELinux: don't BUG if fs reuses a superblock
[sfrench/cifs-2.6.git] / security / selinux / hooks.c
index 75c2e99bfb813a0653350bf18572d1359878dde5..d39b59cf8a08c12f69ff257553a2f3bebd18a685 100644 (file)
@@ -180,7 +180,7 @@ static int inode_alloc_security(struct inode *inode)
        struct task_security_struct *tsec = current->security;
        struct inode_security_struct *isec;
 
-       isec = kmem_cache_zalloc(sel_inode_cache, GFP_KERNEL);
+       isec = kmem_cache_zalloc(sel_inode_cache, GFP_NOFS);
        if (!isec)
                return -ENOMEM;
 
@@ -443,8 +443,7 @@ out:
  * mount options, or whatever.
  */
 static int selinux_get_mnt_opts(const struct super_block *sb,
-                               char ***mount_options, int **mnt_opts_flags,
-                               int *num_opts)
+                               struct security_mnt_opts *opts)
 {
        int rc = 0, i;
        struct superblock_security_struct *sbsec = sb->s_security;
@@ -452,9 +451,7 @@ static int selinux_get_mnt_opts(const struct super_block *sb,
        u32 len;
        char tmp;
 
-       *num_opts = 0;
-       *mount_options = NULL;
-       *mnt_opts_flags = NULL;
+       security_init_mnt_opts(opts);
 
        if (!sbsec->initialized)
                return -EINVAL;
@@ -470,18 +467,18 @@ static int selinux_get_mnt_opts(const struct super_block *sb,
        /* count the number of mount options for this sb */
        for (i = 0; i < 8; i++) {
                if (tmp & 0x01)
-                       (*num_opts)++;
+                       opts->num_mnt_opts++;
                tmp >>= 1;
        }
 
-       *mount_options = kcalloc(*num_opts, sizeof(char *), GFP_ATOMIC);
-       if (!*mount_options) {
+       opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC);
+       if (!opts->mnt_opts) {
                rc = -ENOMEM;
                goto out_free;
        }
 
-       *mnt_opts_flags = kcalloc(*num_opts, sizeof(int), GFP_ATOMIC);
-       if (!*mnt_opts_flags) {
+       opts->mnt_opts_flags = kcalloc(opts->num_mnt_opts, sizeof(int), GFP_ATOMIC);
+       if (!opts->mnt_opts_flags) {
                rc = -ENOMEM;
                goto out_free;
        }
@@ -491,22 +488,22 @@ static int selinux_get_mnt_opts(const struct super_block *sb,
                rc = security_sid_to_context(sbsec->sid, &context, &len);
                if (rc)
                        goto out_free;
-               (*mount_options)[i] = context;
-               (*mnt_opts_flags)[i++] = FSCONTEXT_MNT;
+               opts->mnt_opts[i] = context;
+               opts->mnt_opts_flags[i++] = FSCONTEXT_MNT;
        }
        if (sbsec->flags & CONTEXT_MNT) {
                rc = security_sid_to_context(sbsec->mntpoint_sid, &context, &len);
                if (rc)
                        goto out_free;
-               (*mount_options)[i] = context;
-               (*mnt_opts_flags)[i++] = CONTEXT_MNT;
+               opts->mnt_opts[i] = context;
+               opts->mnt_opts_flags[i++] = CONTEXT_MNT;
        }
        if (sbsec->flags & DEFCONTEXT_MNT) {
                rc = security_sid_to_context(sbsec->def_sid, &context, &len);
                if (rc)
                        goto out_free;
-               (*mount_options)[i] = context;
-               (*mnt_opts_flags)[i++] = DEFCONTEXT_MNT;
+               opts->mnt_opts[i] = context;
+               opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT;
        }
        if (sbsec->flags & ROOTCONTEXT_MNT) {
                struct inode *root = sbsec->sb->s_root->d_inode;
@@ -515,24 +512,16 @@ static int selinux_get_mnt_opts(const struct super_block *sb,
                rc = security_sid_to_context(isec->sid, &context, &len);
                if (rc)
                        goto out_free;
-               (*mount_options)[i] = context;
-               (*mnt_opts_flags)[i++] = ROOTCONTEXT_MNT;
+               opts->mnt_opts[i] = context;
+               opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT;
        }
 
-       BUG_ON(i != *num_opts);
+       BUG_ON(i != opts->num_mnt_opts);
 
        return 0;
 
 out_free:
-       /* don't leak context string if security_sid_to_context had an error */
-       if (*mount_options && i)
-               for (; i > 0; i--)
-                       kfree((*mount_options)[i-1]);
-       kfree(*mount_options);
-       *mount_options = NULL;
-       kfree(*mnt_opts_flags);
-       *mnt_opts_flags = NULL;
-       *num_opts = 0;
+       security_free_mnt_opts(opts);
        return rc;
 }
 
@@ -553,12 +542,13 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag,
                        return 1;
        return 0;
 }
+
 /*
  * Allow filesystems with binary mount data to explicitly set mount point
  * labeling information.
  */
-static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options,
-                               int *flags, int num_opts)
+static int selinux_set_mnt_opts(struct super_block *sb,
+                               struct security_mnt_opts *opts)
 {
        int rc = 0, i;
        struct task_security_struct *tsec = current->security;
@@ -568,6 +558,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options,
        struct inode_security_struct *root_isec = inode->i_security;
        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;
 
        mutex_lock(&sbsec->lock);
 
@@ -588,6 +581,21 @@ static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options,
                goto out;
        }
 
+       /*
+        * Binary mount data FS will come through this function twice.  Once
+        * from an explicit call and once from the generic calls from the vfs.
+        * Since the generic VFS calls will not contain any security mount data
+        * we need to skip the double mount verification.
+        *
+        * This does open a hole in which we will not notice if the first
+        * mount using this sb set explict options and a second mount using
+        * this sb does not set any security options.  (The first options
+        * will be used for both mounts)
+        */
+       if (sbsec->initialized && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA)
+           && (num_opts == 0))
+               goto out;
+
        /*
         * parse the mount options, check if they are valid sids.
         * also check if someone is trying to mount the same sb more
@@ -752,13 +760,13 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
         * this early in the boot process. */
        BUG_ON(!ss_initialized);
 
-       /* this might go away sometime down the line if there is a new user
-        * of clone, but for now, nfs better not get here... */
-       BUG_ON(newsbsec->initialized);
-
        /* how can we clone if the old one wasn't set up?? */
        BUG_ON(!oldsbsec->initialized);
 
+       /* if fs is reusing a sb, just let its options stand... */
+       if (newsbsec->initialized)
+               return;
+
        mutex_lock(&newsbsec->lock);
 
        newsbsec->flags = oldsbsec->flags;
@@ -792,43 +800,15 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
        mutex_unlock(&newsbsec->lock);
 }
 
-/*
- * string mount options parsing and call set the sbsec
- */
-static int superblock_doinit(struct super_block *sb, void *data)
+static int selinux_parse_opts_str(char *options,
+                                 struct security_mnt_opts *opts)
 {
+       char *p;
        char *context = NULL, *defcontext = NULL;
        char *fscontext = NULL, *rootcontext = NULL;
-       int rc = 0;
-       char *p, *options = data;
-       /* selinux only know about a fixed number of mount options */
-       char *mnt_opts[NUM_SEL_MNT_OPTS];
-       int mnt_opts_flags[NUM_SEL_MNT_OPTS], num_mnt_opts = 0;
-
-       if (!data)
-               goto out;
-
-       /* with the nfs patch this will become a goto out; */
-       if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) {
-               const char *name = sb->s_type->name;
-               /* NFS we understand. */
-               if (!strcmp(name, "nfs")) {
-                       struct nfs_mount_data *d = data;
-
-                       if (d->version !=  NFS_MOUNT_VERSION)
-                               goto out;
+       int rc, num_mnt_opts = 0;
 
-                       if (d->context[0]) {
-                               context = kstrdup(d->context, GFP_KERNEL);
-                               if (!context) {
-                                       rc = -ENOMEM;
-                                       goto out;
-                               }
-                       }
-                       goto build_flags;
-               } else
-                       goto out;
-       }
+       opts->num_mnt_opts = 0;
 
        /* Standard string-based options. */
        while ((p = strsep(&options, "|")) != NULL) {
@@ -901,26 +881,37 @@ static int superblock_doinit(struct super_block *sb, void *data)
                }
        }
 
-build_flags:
+       rc = -ENOMEM;
+       opts->mnt_opts = kcalloc(NUM_SEL_MNT_OPTS, sizeof(char *), GFP_ATOMIC);
+       if (!opts->mnt_opts)
+               goto out_err;
+
+       opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int), GFP_ATOMIC);
+       if (!opts->mnt_opts_flags) {
+               kfree(opts->mnt_opts);
+               goto out_err;
+       }
+
        if (fscontext) {
-               mnt_opts[num_mnt_opts] = fscontext;
-               mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT;
+               opts->mnt_opts[num_mnt_opts] = fscontext;
+               opts->mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT;
        }
        if (context) {
-               mnt_opts[num_mnt_opts] = context;
-               mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT;
+               opts->mnt_opts[num_mnt_opts] = context;
+               opts->mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT;
        }
        if (rootcontext) {
-               mnt_opts[num_mnt_opts] = rootcontext;
-               mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT;
+               opts->mnt_opts[num_mnt_opts] = rootcontext;
+               opts->mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT;
        }
        if (defcontext) {
-               mnt_opts[num_mnt_opts] = defcontext;
-               mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT;
+               opts->mnt_opts[num_mnt_opts] = defcontext;
+               opts->mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT;
        }
 
-out:
-       rc = selinux_set_mnt_opts(sb, mnt_opts, mnt_opts_flags, num_mnt_opts);
+       opts->num_mnt_opts = num_mnt_opts;
+       return 0;
+
 out_err:
        kfree(context);
        kfree(defcontext);
@@ -928,6 +919,33 @@ out_err:
        kfree(rootcontext);
        return rc;
 }
+/*
+ * 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);
+
+       rc = selinux_parse_opts_str(options, &opts);
+       if (rc)
+               goto out_err;
+
+out:
+       rc = selinux_set_mnt_opts(sb, &opts);
+
+out_err:
+       security_free_mnt_opts(&opts);
+       return rc;
+}
 
 static inline u16 inode_mode_to_security_class(umode_t mode)
 {
@@ -1125,7 +1143,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent
                }
 
                len = INITCONTEXTLEN;
-               context = kmalloc(len, GFP_KERNEL);
+               context = kmalloc(len, GFP_NOFS);
                if (!context) {
                        rc = -ENOMEM;
                        dput(dentry);
@@ -1143,7 +1161,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent
                        }
                        kfree(context);
                        len = rc;
-                       context = kmalloc(len, GFP_KERNEL);
+                       context = kmalloc(len, GFP_NOFS);
                        if (!context) {
                                rc = -ENOMEM;
                                dput(dentry);
@@ -1167,7 +1185,8 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent
                        rc = 0;
                } else {
                        rc = security_context_to_sid_default(context, rc, &sid,
-                                                            sbsec->def_sid);
+                                                            sbsec->def_sid,
+                                                            GFP_NOFS);
                        if (rc) {
                                printk(KERN_WARNING "%s:  context_to_sid(%s) "
                                       "returned %d for dev=%s ino=%ld\n",
@@ -1612,6 +1631,12 @@ static inline u32 file_to_av(struct file *file)
                else
                        av |= FILE__WRITE;
        }
+       if (!av) {
+               /*
+                * Special file opened with flags 3 for ioctl-only use.
+                */
+               av = FILE__IOCTL;
+       }
 
        return av;
 }
@@ -2253,7 +2278,7 @@ static inline void take_selinux_option(char **to, char *from, int *first,
        }
 }
 
-static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void *copy)
+static int selinux_sb_copy_data(char *orig, char *copy)
 {
        int fnosec, fsec, rc = 0;
        char *in_save, *in_curr, *in_end;
@@ -2263,12 +2288,6 @@ static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void
        in_curr = orig;
        sec_curr = copy;
 
-       /* Binary mount data: just copy */
-       if (type->fs_flags & FS_BINARY_MOUNTDATA) {
-               copy_page(sec_curr, in_curr);
-               goto out;
-       }
-
        nosec = (char *)get_zeroed_page(GFP_KERNEL);
        if (!nosec) {
                rc = -ENOMEM;
@@ -2411,7 +2430,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
                return -EOPNOTSUPP;
 
        if (name) {
-               namep = kstrdup(XATTR_SELINUX_SUFFIX, GFP_KERNEL);
+               namep = kstrdup(XATTR_SELINUX_SUFFIX, GFP_NOFS);
                if (!namep)
                        return -ENOMEM;
                *name = namep;
@@ -5251,6 +5270,8 @@ static struct security_operations selinux_ops = {
        .sb_get_mnt_opts =              selinux_get_mnt_opts,
        .sb_set_mnt_opts =              selinux_set_mnt_opts,
        .sb_clone_mnt_opts =            selinux_sb_clone_mnt_opts,
+       .sb_parse_opts_str =            selinux_parse_opts_str,
+
 
        .inode_alloc_security =         selinux_inode_alloc_security,
        .inode_free_security =          selinux_inode_free_security,