Merge tag 'fs.setgid.v6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner...
[linux-2.6-microblaze.git] / fs / namei.c
index ed3ffd9..53b4bc0 100644 (file)
@@ -3023,6 +3023,65 @@ void unlock_rename(struct dentry *p1, struct dentry *p2)
 }
 EXPORT_SYMBOL(unlock_rename);
 
+/**
+ * mode_strip_umask - handle vfs umask stripping
+ * @dir:       parent directory of the new inode
+ * @mode:      mode of the new inode to be created in @dir
+ *
+ * Umask stripping depends on whether or not the filesystem supports POSIX
+ * ACLs. If the filesystem doesn't support it umask stripping is done directly
+ * in here. If the filesystem does support POSIX ACLs umask stripping is
+ * deferred until the filesystem calls posix_acl_create().
+ *
+ * Returns: mode
+ */
+static inline umode_t mode_strip_umask(const struct inode *dir, umode_t mode)
+{
+       if (!IS_POSIXACL(dir))
+               mode &= ~current_umask();
+       return mode;
+}
+
+/**
+ * vfs_prepare_mode - prepare the mode to be used for a new inode
+ * @mnt_userns:                user namespace of the mount the inode was found from
+ * @dir:       parent directory of the new inode
+ * @mode:      mode of the new inode
+ * @mask_perms:        allowed permission by the vfs
+ * @type:      type of file to be created
+ *
+ * This helper consolidates and enforces vfs restrictions on the @mode of a new
+ * object to be created.
+ *
+ * Umask stripping depends on whether the filesystem supports POSIX ACLs (see
+ * the kernel documentation for mode_strip_umask()). Moving umask stripping
+ * after setgid stripping allows the same ordering for both non-POSIX ACL and
+ * POSIX ACL supporting filesystems.
+ *
+ * Note that it's currently valid for @type to be 0 if a directory is created.
+ * Filesystems raise that flag individually and we need to check whether each
+ * filesystem can deal with receiving S_IFDIR from the vfs before we enforce a
+ * non-zero type.
+ *
+ * Returns: mode to be passed to the filesystem
+ */
+static inline umode_t vfs_prepare_mode(struct user_namespace *mnt_userns,
+                                      const struct inode *dir, umode_t mode,
+                                      umode_t mask_perms, umode_t type)
+{
+       mode = mode_strip_sgid(mnt_userns, dir, mode);
+       mode = mode_strip_umask(dir, mode);
+
+       /*
+        * Apply the vfs mandated allowed permission mask and set the type of
+        * file to be created before we call into the filesystem.
+        */
+       mode &= (mask_perms & ~S_IFMT);
+       mode |= (type & S_IFMT);
+
+       return mode;
+}
+
 /**
  * vfs_create - create new file
  * @mnt_userns:        user namespace of the mount the inode was found from
@@ -3048,8 +3107,8 @@ int vfs_create(struct user_namespace *mnt_userns, struct inode *dir,
 
        if (!dir->i_op->create)
                return -EACCES; /* shouldn't it be ENOSYS? */
-       mode &= S_IALLUGO;
-       mode |= S_IFREG;
+
+       mode = vfs_prepare_mode(mnt_userns, dir, mode, S_IALLUGO, S_IFREG);
        error = security_inode_create(dir, dentry, mode);
        if (error)
                return error;
@@ -3312,8 +3371,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
        if (open_flag & O_CREAT) {
                if (open_flag & O_EXCL)
                        open_flag &= ~O_TRUNC;
-               if (!IS_POSIXACL(dir->d_inode))
-                       mode &= ~current_umask();
+               mode = vfs_prepare_mode(mnt_userns, dir->d_inode, mode, mode, mode);
                if (likely(got_write))
                        create_error = may_o_create(mnt_userns, &nd->path,
                                                    dentry, mode);
@@ -3544,6 +3602,7 @@ struct dentry *vfs_tmpfile(struct user_namespace *mnt_userns,
        child = d_alloc(dentry, &slash_name);
        if (unlikely(!child))
                goto out_err;
+       mode = vfs_prepare_mode(mnt_userns, dir, mode, mode, mode);
        error = dir->i_op->tmpfile(mnt_userns, dir, child, mode);
        if (error)
                goto out_err;
@@ -3821,6 +3880,7 @@ int vfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
        if (!dir->i_op->mknod)
                return -EPERM;
 
+       mode = vfs_prepare_mode(mnt_userns, dir, mode, mode, mode);
        error = devcgroup_inode_mknod(mode, dev);
        if (error)
                return error;
@@ -3871,9 +3931,8 @@ retry:
        if (IS_ERR(dentry))
                goto out1;
 
-       if (!IS_POSIXACL(path.dentry->d_inode))
-               mode &= ~current_umask();
-       error = security_path_mknod(&path, dentry, mode, dev);
+       error = security_path_mknod(&path, dentry,
+                       mode_strip_umask(path.dentry->d_inode, mode), dev);
        if (error)
                goto out2;
 
@@ -3943,7 +4002,7 @@ int vfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
        if (!dir->i_op->mkdir)
                return -EPERM;
 
-       mode &= (S_IRWXUGO|S_ISVTX);
+       mode = vfs_prepare_mode(mnt_userns, dir, mode, S_IRWXUGO | S_ISVTX, 0);
        error = security_inode_mkdir(dir, dentry, mode);
        if (error)
                return error;
@@ -3971,9 +4030,8 @@ retry:
        if (IS_ERR(dentry))
                goto out_putname;
 
-       if (!IS_POSIXACL(path.dentry->d_inode))
-               mode &= ~current_umask();
-       error = security_path_mkdir(&path, dentry, mode);
+       error = security_path_mkdir(&path, dentry,
+                       mode_strip_umask(path.dentry->d_inode, mode));
        if (!error) {
                struct user_namespace *mnt_userns;
                mnt_userns = mnt_user_ns(path.mnt);