]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/commitdiff
UBUNTU: SAUCE: (namespace) ext4: Add support for unprivileged mounts from user namespaces
authorSeth Forshee <seth.forshee@canonical.com>
Sat, 18 Oct 2014 11:02:09 +0000 (13:02 +0200)
committerTim Gardner <tim.gardner@canonical.com>
Mon, 20 Feb 2017 03:57:58 +0000 (20:57 -0700)
Support unprivileged mounting of ext4 volumes from user
namespaces. This requires the following changes:

 - Perform all uid, gid, and projid conversions to/from disk
   relative to s_user_ns. In many cases this will already be
   handled by the vfs helper functions. This also requires
   updates to handle cases where ids may not map into s_user_ns.
   A new helper, projid_valid_eq(), is added to help with this.

 - Update most capability checks to check for capabilities in
   s_user_ns rather than init_user_ns. These mostly reflect
   changes to the filesystem that a user in s_user_ns could
   already make externally by virtue of having write access to
   the backing device.

 - Restrict unsafe options in either the mount options or the
   ext4 superblock. Currently the only concerning option is
   errors=panic, and this is made to require CAP_SYS_ADMIN in
   init_user_ns.

 - Verify that unprivileged users have the required access to the
   journal device at the path passed via the journal_path mount
   option.

   Note that for the journal_path and the journal_dev mount
   options, and for external journal devices specified in the
   ext4 superblock, devcgroup restrictions will be enforced by
   __blkdev_get(), (via blkdev_get_by_dev()), ensuring that the
   user has been granted appropriate access to the block device.

 - Set the FS_USERNS_MOUNT flag on the filesystem types supported
   by ext4.

sysfs attributes for ext4 mounts remain writable only by real
root.

Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
fs/ext4/acl.c
fs/ext4/balloc.c
fs/ext4/ialloc.c
fs/ext4/inode.c
fs/ext4/ioctl.c
fs/ext4/namei.c
fs/ext4/resize.c
fs/ext4/super.c
include/linux/projid.h

index fd389935ecd1629f402128518f63fd7d851e2a84..03cee8a39ff070d3dacb09869853bfa880f3eb56 100644 (file)
@@ -13,7 +13,7 @@
  * Convert from filesystem to in-memory representation.
  */
 static struct posix_acl *
-ext4_acl_from_disk(const void *value, size_t size)
+ext4_acl_from_disk(struct super_block *sb, const void *value, size_t size)
 {
        const char *end = (char *)value + size;
        int n, count;
@@ -57,16 +57,20 @@ ext4_acl_from_disk(const void *value, size_t size)
                        if ((char *)value > end)
                                goto fail;
                        acl->a_entries[n].e_uid =
-                               make_kuid(&init_user_ns,
+                               make_kuid(sb->s_user_ns,
                                          le32_to_cpu(entry->e_id));
+                       if (!uid_valid(acl->a_entries[n].e_uid))
+                               goto fail;
                        break;
                case ACL_GROUP:
                        value = (char *)value + sizeof(ext4_acl_entry);
                        if ((char *)value > end)
                                goto fail;
                        acl->a_entries[n].e_gid =
-                               make_kgid(&init_user_ns,
+                               make_kgid(sb->s_user_ns,
                                          le32_to_cpu(entry->e_id));
+                       if (!gid_valid(acl->a_entries[n].e_gid))
+                               goto fail;
                        break;
 
                default:
@@ -86,11 +90,14 @@ fail:
  * Convert from in-memory to filesystem representation.
  */
 static void *
-ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
+ext4_acl_to_disk(struct super_block *sb, const struct posix_acl *acl,
+                size_t *size)
 {
        ext4_acl_header *ext_acl;
        char *e;
        size_t n;
+       uid_t uid;
+       gid_t gid;
 
        *size = ext4_acl_size(acl->a_count);
        ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count *
@@ -106,13 +113,17 @@ ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
                entry->e_perm = cpu_to_le16(acl_e->e_perm);
                switch (acl_e->e_tag) {
                case ACL_USER:
-                       entry->e_id = cpu_to_le32(
-                               from_kuid(&init_user_ns, acl_e->e_uid));
+                       uid = from_kuid(sb->s_user_ns, acl_e->e_uid);
+                       if (uid == (uid_t)-1)
+                               goto fail;
+                       entry->e_id = cpu_to_le32(uid);
                        e += sizeof(ext4_acl_entry);
                        break;
                case ACL_GROUP:
-                       entry->e_id = cpu_to_le32(
-                               from_kgid(&init_user_ns, acl_e->e_gid));
+                       gid = from_kgid(sb->s_user_ns, acl_e->e_gid);
+                       if (gid == (gid_t)-1)
+                               goto fail;
+                       entry->e_id = cpu_to_le32(gid);
                        e += sizeof(ext4_acl_entry);
                        break;
 
@@ -165,7 +176,7 @@ ext4_get_acl(struct inode *inode, int type)
                retval = ext4_xattr_get(inode, name_index, "", value, retval);
        }
        if (retval > 0)
-               acl = ext4_acl_from_disk(value, retval);
+               acl = ext4_acl_from_disk(inode->i_sb, value, retval);
        else if (retval == -ENODATA || retval == -ENOSYS)
                acl = NULL;
        else
@@ -211,7 +222,7 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
                return -EINVAL;
        }
        if (acl) {
-               value = ext4_acl_to_disk(acl, &size);
+               value = ext4_acl_to_disk(inode->i_sb, acl, &size);
                if (IS_ERR(value))
                        return (int)PTR_ERR(value);
        }
index e04ec868e37e71d3e82529eeb68f449c284b47c6..f22fdd4e27ba3f490aa4de8b2c56546108869528 100644 (file)
@@ -565,8 +565,8 @@ static int ext4_has_free_clusters(struct ext4_sb_info *sbi,
 
        /* Hm, nope.  Are (enough) root reserved clusters available? */
        if (uid_eq(sbi->s_resuid, current_fsuid()) ||
-           (!gid_eq(sbi->s_resgid, GLOBAL_ROOT_GID) && in_group_p(sbi->s_resgid)) ||
-           capable(CAP_SYS_RESOURCE) ||
+           (!gid_eq(sbi->s_resgid, make_kgid(sbi->s_sb->s_user_ns, 0)) && in_group_p(sbi->s_resgid)) ||
+           ns_capable(sbi->s_sb->s_user_ns, CAP_SYS_RESOURCE) ||
            (flags & EXT4_MB_USE_ROOT_BLOCKS)) {
 
                if (free_clusters >= (nclusters + dirty_clusters +
index e57e8d90ea54a71745cf6257a7f1ef6437a0191b..7e855a662ef478bded5bbddea2f82e62a0817b0c 100644 (file)
@@ -764,6 +764,10 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
        if (!dir || !dir->i_nlink)
                return ERR_PTR(-EPERM);
 
+       /* Supplied owner must be valid */
+       if (owner && (owner[0] == (uid_t)-1 || owner[1] == (uid_t)-1))
+               return ERR_PTR(-EOVERFLOW);
+
        if ((ext4_encrypted_inode(dir) ||
             DUMMY_ENCRYPTION_ENABLED(EXT4_SB(dir->i_sb))) &&
            (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) {
@@ -806,7 +810,7 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
            ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT))
                ei->i_projid = EXT4_I(dir)->i_projid;
        else
-               ei->i_projid = make_kprojid(&init_user_ns, EXT4_DEF_PROJID);
+               ei->i_projid = make_kprojid(sb->s_user_ns, EXT4_DEF_PROJID);
 
        err = dquot_initialize(inode);
        if (err)
index 88d57af1b516c5bbfd7b2f1bc471a9d76382c3bc..55005185ea6c2d848e05ca18d2a259f8e15ab7c1 100644 (file)
@@ -4613,7 +4613,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
        }
        i_uid_write(inode, i_uid);
        i_gid_write(inode, i_gid);
-       ei->i_projid = make_kprojid(&init_user_ns, i_projid);
+       ei->i_projid = make_kprojid(sb->s_user_ns, i_projid);
        set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
 
        ext4_clear_state_flags(ei);     /* Only relevant on 32-bit archs */
@@ -4931,7 +4931,7 @@ static int ext4_do_update_inode(handle_t *handle,
        raw_inode->i_mode = cpu_to_le16(inode->i_mode);
        i_uid = i_uid_read(inode);
        i_gid = i_gid_read(inode);
-       i_projid = from_kprojid(&init_user_ns, ei->i_projid);
+       i_projid = from_kprojid(sb->s_user_ns, ei->i_projid);
        if (!(test_opt(inode->i_sb, NO_UID32))) {
                raw_inode->i_uid_low = cpu_to_le16(low_16_bits(i_uid));
                raw_inode->i_gid_low = cpu_to_le16(low_16_bits(i_gid));
@@ -5010,12 +5010,14 @@ static int ext4_do_update_inode(handle_t *handle,
                }
        }
 
-       BUG_ON(!ext4_has_feature_project(inode->i_sb) &&
-              i_projid != EXT4_DEF_PROJID);
+       if (i_projid != (projid_t)-1) {
+               BUG_ON(!ext4_has_feature_project(inode->i_sb) &&
+                      i_projid != EXT4_DEF_PROJID);
 
-       if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
-           EXT4_FITS_IN_INODE(raw_inode, ei, i_projid))
-               raw_inode->i_projid = cpu_to_le32(i_projid);
+               if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
+                   EXT4_FITS_IN_INODE(raw_inode, ei, i_projid))
+                       raw_inode->i_projid = cpu_to_le32(i_projid);
+       }
 
        ext4_inode_csum_set(inode, raw_inode, ei);
        spin_unlock(&ei->i_raw_lock);
index d534399cf60785ed42cabe77b5ab41aefcb6ae9b..d2ba39b9920404121c7998390bf17a1796f6ada7 100644 (file)
@@ -238,7 +238,7 @@ static int ext4_ioctl_setflags(struct inode *inode,
         * the relevant capability.
         */
        if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL)) {
-               if (!capable(CAP_SYS_RESOURCE))
+               if (!ns_capable(inode->i_sb->s_user_ns, CAP_SYS_RESOURCE))
                        goto flags_out;
        }
        if ((flags ^ oldflags) & EXT4_EXTENTS_FL)
@@ -326,8 +326,10 @@ static int ext4_ioctl_setproject(struct file *filp, __u32 projid)
        if (EXT4_INODE_SIZE(sb) <= EXT4_GOOD_OLD_INODE_SIZE)
                return -EOPNOTSUPP;
 
-       kprojid = make_kprojid(&init_user_ns, (projid_t)projid);
+       kprojid = make_kprojid(sb->s_user_ns, (projid_t)projid);
 
+       if (!projid_valid(kprojid))
+               return -EOVERFLOW;
        if (projid_eq(kprojid, EXT4_I(inode)->i_projid))
                return 0;
 
@@ -763,7 +765,7 @@ resizefs_out:
                struct fstrim_range range;
                int ret = 0;
 
-               if (!capable(CAP_SYS_ADMIN))
+               if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
                        return -EPERM;
 
                if (!blk_queue_discard(q))
@@ -845,7 +847,7 @@ resizefs_out:
                fa.fsx_xflags = ext4_iflags_to_xflags(ei->i_flags & EXT4_FL_USER_VISIBLE);
 
                if (ext4_has_feature_project(inode->i_sb)) {
-                       fa.fsx_projid = (__u32)from_kprojid(&init_user_ns,
+                       fa.fsx_projid = (__u32)from_kprojid_munged(sb->s_user_ns,
                                EXT4_I(inode)->i_projid);
                }
 
index eadba919f26b1484c10125739a0e684fe49b7273..71bfb48e94395f265d6ad2835f5e1c6a42739187 100644 (file)
@@ -3236,8 +3236,8 @@ static int ext4_link(struct dentry *old_dentry,
                return -EPERM;
 
        if ((ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) &&
-          (!projid_eq(EXT4_I(dir)->i_projid,
-                      EXT4_I(old_dentry->d_inode)->i_projid)))
+          (!projid_valid_eq(EXT4_I(dir)->i_projid,
+                            EXT4_I(old_dentry->d_inode)->i_projid)))
                return -EXDEV;
 
        err = dquot_initialize(dir);
@@ -3521,8 +3521,8 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
        u8 old_file_type;
 
        if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT)) &&
-           (!projid_eq(EXT4_I(new_dir)->i_projid,
-                       EXT4_I(old_dentry->d_inode)->i_projid)))
+           (!projid_valid_eq(EXT4_I(new_dir)->i_projid,
+                             EXT4_I(old_dentry->d_inode)->i_projid)))
                return -EXDEV;
 
        retval = dquot_initialize(old.dir);
@@ -3733,11 +3733,11 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
                return -EPERM;
 
        if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT) &&
-            !projid_eq(EXT4_I(new_dir)->i_projid,
-                       EXT4_I(old_dentry->d_inode)->i_projid)) ||
+            !projid_valid_eq(EXT4_I(new_dir)->i_projid,
+                             EXT4_I(old_dentry->d_inode)->i_projid)) ||
            (ext4_test_inode_flag(old_dir, EXT4_INODE_PROJINHERIT) &&
-            !projid_eq(EXT4_I(old_dir)->i_projid,
-                       EXT4_I(new_dentry->d_inode)->i_projid)))
+            !projid_valid_eq(EXT4_I(old_dir)->i_projid,
+                             EXT4_I(new_dentry->d_inode)->i_projid)))
                return -EXDEV;
 
        retval = dquot_initialize(old.dir);
index cf681004b1965fba00be6e97ee9a4cbe57eb3654..a5e2ed23d495156f64f511f154575cb243b4aed2 100644 (file)
@@ -20,7 +20,7 @@ int ext4_resize_begin(struct super_block *sb)
 {
        int ret = 0;
 
-       if (!capable(CAP_SYS_RESOURCE))
+       if (!ns_capable(sb->s_user_ns, CAP_SYS_RESOURCE))
                return -EPERM;
 
        /*
index 66845a08a87a42bc8a97c7fcab88644c19d6b0f9..ff956f1127acda9a8fae21e3f247012ac82082f6 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/crc16.h>
 #include <linux/cleancache.h>
 #include <linux/uaccess.h>
+#include <linux/user_namespace.h>
 
 #include <linux/kthread.h>
 #include <linux/freezer.h>
@@ -117,7 +118,7 @@ static struct file_system_type ext2_fs_type = {
        .name           = "ext2",
        .mount          = ext4_mount,
        .kill_sb        = kill_block_super,
-       .fs_flags       = FS_REQUIRES_DEV,
+       .fs_flags       = FS_REQUIRES_DEV | FS_USERNS_MOUNT,
 };
 MODULE_ALIAS_FS("ext2");
 MODULE_ALIAS("ext2");
@@ -132,7 +133,7 @@ static struct file_system_type ext3_fs_type = {
        .name           = "ext3",
        .mount          = ext4_mount,
        .kill_sb        = kill_block_super,
-       .fs_flags       = FS_REQUIRES_DEV,
+       .fs_flags       = FS_REQUIRES_DEV | FS_USERNS_MOUNT,
 };
 MODULE_ALIAS_FS("ext3");
 MODULE_ALIAS("ext3");
@@ -1650,6 +1651,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
                return -1;
        }
 
+       if (token == Opt_err_panic && !capable(CAP_SYS_ADMIN)) {
+               ext4_msg(sb, KERN_ERR,
+                        "Mount option \"%s\" not allowed for unprivileged mounts",
+                        opt);
+               return -1;
+       }
+
        if (args->from && !(m->flags & MOPT_STRING) && match_int(args, &arg))
                return -1;
        if (args->from && (m->flags & MOPT_GTE0) && (arg < 0))
@@ -1698,14 +1706,14 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
        } else if (token == Opt_stripe) {
                sbi->s_stripe = arg;
        } else if (token == Opt_resuid) {
-               uid = make_kuid(current_user_ns(), arg);
+               uid = make_kuid(sb->s_user_ns, arg);
                if (!uid_valid(uid)) {
                        ext4_msg(sb, KERN_ERR, "Invalid uid value %d", arg);
                        return -1;
                }
                sbi->s_resuid = uid;
        } else if (token == Opt_resgid) {
-               gid = make_kgid(current_user_ns(), arg);
+               gid = make_kgid(sb->s_user_ns, arg);
                if (!gid_valid(gid)) {
                        ext4_msg(sb, KERN_ERR, "Invalid gid value %d", arg);
                        return -1;
@@ -1744,6 +1752,19 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
                        return -1;
                }
 
+               /*
+                * Refuse access for unprivileged mounts if the user does
+                * not have rw access to the journal device via the supplied
+                * path.
+                */
+               if (!capable(CAP_SYS_ADMIN) &&
+                   inode_permission(d_inode(path.dentry), MAY_READ|MAY_WRITE)) {
+                       ext4_msg(sb, KERN_ERR,
+                                "error: Insufficient access to journal path %s",
+                                journal_path);
+                       return -1;
+               }
+
                journal_inode = d_inode(path.dentry);
                if (!S_ISBLK(journal_inode->i_mode)) {
                        ext4_msg(sb, KERN_ERR, "error: journal path %s "
@@ -1979,14 +2000,14 @@ static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
                SEQ_OPTS_PRINT("%s", token2str(m->token));
        }
 
-       if (nodefs || !uid_eq(sbi->s_resuid, make_kuid(&init_user_ns, EXT4_DEF_RESUID)) ||
+       if (nodefs || !uid_eq(sbi->s_resuid, make_kuid(sb->s_user_ns, EXT4_DEF_RESUID)) ||
            le16_to_cpu(es->s_def_resuid) != EXT4_DEF_RESUID)
                SEQ_OPTS_PRINT("resuid=%u",
-                               from_kuid_munged(&init_user_ns, sbi->s_resuid));
-       if (nodefs || !gid_eq(sbi->s_resgid, make_kgid(&init_user_ns, EXT4_DEF_RESGID)) ||
+                               from_kuid_munged(sb->s_user_ns, sbi->s_resuid));
+       if (nodefs || !gid_eq(sbi->s_resgid, make_kgid(sb->s_user_ns, EXT4_DEF_RESGID)) ||
            le16_to_cpu(es->s_def_resgid) != EXT4_DEF_RESGID)
                SEQ_OPTS_PRINT("resgid=%u",
-                               from_kgid_munged(&init_user_ns, sbi->s_resgid));
+                               from_kgid_munged(sb->s_user_ns, sbi->s_resgid));
        def_errors = nodefs ? -1 : le16_to_cpu(es->s_errors);
        if (test_opt(sb, ERRORS_RO) && def_errors != EXT4_ERRORS_RO)
                SEQ_OPTS_PUTS("errors=remount-ro");
@@ -3458,19 +3479,26 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
        else if ((def_mount_opts & EXT4_DEFM_JMODE) == EXT4_DEFM_JMODE_WBACK)
                set_opt(sb, WRITEBACK_DATA);
 
-       if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_PANIC)
+       if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_PANIC) {
+               if (!capable(CAP_SYS_ADMIN))
+                       goto failed_mount;
                set_opt(sb, ERRORS_PANIC);
-       else if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_CONTINUE)
+       } else if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_CONTINUE) {
                set_opt(sb, ERRORS_CONT);
-       else
+       } else {
                set_opt(sb, ERRORS_RO);
+       }
        /* block_validity enabled by default; disable with noblock_validity */
        set_opt(sb, BLOCK_VALIDITY);
        if (def_mount_opts & EXT4_DEFM_DISCARD)
                set_opt(sb, DISCARD);
 
-       sbi->s_resuid = make_kuid(&init_user_ns, le16_to_cpu(es->s_def_resuid));
-       sbi->s_resgid = make_kgid(&init_user_ns, le16_to_cpu(es->s_def_resgid));
+       sbi->s_resuid = make_kuid(sb->s_user_ns, le16_to_cpu(es->s_def_resuid));
+       if (!uid_valid(sbi->s_resuid))
+               sbi->s_resuid = make_kuid(sb->s_user_ns, EXT4_DEF_RESUID);
+       sbi->s_resgid = make_kgid(sb->s_user_ns, le16_to_cpu(es->s_def_resgid));
+       if (!gid_valid(sbi->s_resgid))
+               sbi->s_resgid = make_kgid(sb->s_user_ns, EXT4_DEF_RESGID);
        sbi->s_commit_interval = JBD2_DEFAULT_MAX_COMMIT_AGE * HZ;
        sbi->s_min_batch_time = EXT4_DEF_MIN_BATCH_TIME;
        sbi->s_max_batch_time = EXT4_DEF_MAX_BATCH_TIME;
@@ -4283,6 +4311,7 @@ failed_mount:
        ext4_blkdev_remove(sbi);
        brelse(bh);
 out_fail:
+       /* sb->s_user_ns will be put when sb is destroyed */
        sb->s_fs_info = NULL;
        kfree(sbi->s_blockgroup_lock);
 out_free_base:
@@ -5607,7 +5636,7 @@ static struct file_system_type ext4_fs_type = {
        .name           = "ext4",
        .mount          = ext4_mount,
        .kill_sb        = kill_block_super,
-       .fs_flags       = FS_REQUIRES_DEV,
+       .fs_flags       = FS_REQUIRES_DEV | FS_USERNS_MOUNT,
 };
 MODULE_ALIAS_FS("ext4");
 
index 8c1f2c55226de3dc577ac052f8e459766ea47823..344e807c61d708f09f945a11fd500dc2b3d8b979 100644 (file)
@@ -47,6 +47,11 @@ static inline bool projid_valid(kprojid_t projid)
        return !projid_eq(projid, INVALID_PROJID);
 }
 
+static inline bool projid_valid_eq(kprojid_t left, kprojid_t right)
+{
+       return projid_eq(left, right) && projid_valid(left);
+}
+
 #ifdef CONFIG_USER_NS
 
 extern kprojid_t make_kprojid(struct user_namespace *from, projid_t projid);