capability: handle idmapped mounts
authorChristian Brauner <christian.brauner@ubuntu.com>
Thu, 21 Jan 2021 13:19:23 +0000 (14:19 +0100)
committerChristian Brauner <christian.brauner@ubuntu.com>
Sun, 24 Jan 2021 13:27:16 +0000 (14:27 +0100)
In order to determine whether a caller holds privilege over a given
inode the capability framework exposes the two helpers
privileged_wrt_inode_uidgid() and capable_wrt_inode_uidgid(). The former
verifies that the inode has a mapping in the caller's user namespace and
the latter additionally verifies that the caller has the requested
capability in their current user namespace.
If the inode is accessed through an idmapped mount map it into the
mount's user namespace. Afterwards the checks are identical to
non-idmapped inodes. If the initial user namespace is passed all
operations are a nop so non-idmapped mounts will not see a change in
behavior.

Link: https://lore.kernel.org/r/20210121131959.646623-5-christian.brauner@ubuntu.com
Cc: Christoph Hellwig <hch@lst.de>
Cc: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: James Morris <jamorris@linux.microsoft.com>
Acked-by: Serge Hallyn <serge@hallyn.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
fs/attr.c
fs/exec.c
fs/inode.c
fs/namei.c
fs/overlayfs/super.c
fs/posix_acl.c
fs/xfs/xfs_ioctl.c
include/linux/capability.h
kernel/capability.c
security/commoncap.c

index b4bbdbd4c8ca096e86451b0443e64bc2ca9e2b1d..d270f640a192fbf3d6a05d7e1d646eb8ab98133c 100644 (file)
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -23,7 +23,7 @@ static bool chown_ok(const struct inode *inode, kuid_t uid)
        if (uid_eq(current_fsuid(), inode->i_uid) &&
            uid_eq(uid, inode->i_uid))
                return true;
-       if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+       if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
                return true;
        if (uid_eq(inode->i_uid, INVALID_UID) &&
            ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
@@ -36,7 +36,7 @@ static bool chgrp_ok(const struct inode *inode, kgid_t gid)
        if (uid_eq(current_fsuid(), inode->i_uid) &&
            (in_group_p(gid) || gid_eq(gid, inode->i_gid)))
                return true;
-       if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+       if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
                return true;
        if (gid_eq(inode->i_gid, INVALID_GID) &&
            ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
@@ -92,7 +92,7 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr)
                /* Also check the setgid bit! */
                if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
                                inode->i_gid) &&
-                   !capable_wrt_inode_uidgid(inode, CAP_FSETID))
+                   !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
                        attr->ia_mode &= ~S_ISGID;
        }
 
@@ -193,7 +193,7 @@ void setattr_copy(struct inode *inode, const struct iattr *attr)
                umode_t mode = attr->ia_mode;
 
                if (!in_group_p(inode->i_gid) &&
-                   !capable_wrt_inode_uidgid(inode, CAP_FSETID))
+                   !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
                        mode &= ~S_ISGID;
                inode->i_mode = mode;
        }
index 5d4d52039105cd3e017681c15f38324fd5681f4e..89d4780ff48f970b834c65b58e025013b3484539 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1411,7 +1411,8 @@ void would_dump(struct linux_binprm *bprm, struct file *file)
                /* Ensure mm->user_ns contains the executable */
                user_ns = old = bprm->mm->user_ns;
                while ((user_ns != &init_user_ns) &&
-                      !privileged_wrt_inode_uidgid(user_ns, inode))
+                      !privileged_wrt_inode_uidgid(user_ns, &init_user_ns,
+                                                   inode))
                        user_ns = user_ns->parent;
 
                if (old != user_ns) {
index 6442d97d9a4ab37a82f55dda5801c5067ef9bf01..cd40cbf87ce4acab1f74210dcc3773374b2e779b 100644 (file)
@@ -2146,7 +2146,8 @@ void inode_init_owner(struct inode *inode, const struct inode *dir,
                        mode |= S_ISGID;
                else if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP) &&
                         !in_group_p(inode->i_gid) &&
-                        !capable_wrt_inode_uidgid(dir, CAP_FSETID))
+                        !capable_wrt_inode_uidgid(&init_user_ns, dir,
+                                                  CAP_FSETID))
                        mode &= ~S_ISGID;
        } else
                inode->i_gid = current_fsgid();
index 78443a85480a5aea3b04b3064bb3ef594c83777b..fd4724bce4f5f11f5b3add79aed0c0c8c80899e4 100644 (file)
@@ -357,10 +357,11 @@ int generic_permission(struct inode *inode, int mask)
        if (S_ISDIR(inode->i_mode)) {
                /* DACs are overridable for directories */
                if (!(mask & MAY_WRITE))
-                       if (capable_wrt_inode_uidgid(inode,
+                       if (capable_wrt_inode_uidgid(&init_user_ns, inode,
                                                     CAP_DAC_READ_SEARCH))
                                return 0;
-               if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
+               if (capable_wrt_inode_uidgid(&init_user_ns, inode,
+                                            CAP_DAC_OVERRIDE))
                        return 0;
                return -EACCES;
        }
@@ -370,7 +371,8 @@ int generic_permission(struct inode *inode, int mask)
         */
        mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
        if (mask == MAY_READ)
-               if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
+               if (capable_wrt_inode_uidgid(&init_user_ns, inode,
+                                            CAP_DAC_READ_SEARCH))
                        return 0;
        /*
         * Read/write DACs are always overridable.
@@ -378,7 +380,8 @@ int generic_permission(struct inode *inode, int mask)
         * at least one exec bit set.
         */
        if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
-               if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
+               if (capable_wrt_inode_uidgid(&init_user_ns, inode,
+                                            CAP_DAC_OVERRIDE))
                        return 0;
 
        return -EACCES;
@@ -2659,7 +2662,7 @@ int __check_sticky(struct inode *dir, struct inode *inode)
                return 0;
        if (uid_eq(dir->i_uid, fsuid))
                return 0;
-       return !capable_wrt_inode_uidgid(inode, CAP_FOWNER);
+       return !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FOWNER);
 }
 EXPORT_SYMBOL(__check_sticky);
 
index 2bd570cbe8a4591f58610f9ede56e713b3eb2aa8..88d8777877700a60008d3200578f281547bcf30b 100644 (file)
@@ -1017,7 +1017,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler,
        if (unlikely(inode->i_mode & S_ISGID) &&
            handler->flags == ACL_TYPE_ACCESS &&
            !in_group_p(inode->i_gid) &&
-           !capable_wrt_inode_uidgid(inode, CAP_FSETID)) {
+           !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) {
                struct iattr iattr = { .ia_valid = ATTR_KILL_SGID };
 
                err = ovl_setattr(dentry, &iattr);
index 95882b3f5f62f07380d8dbe242b0f89210e19f84..4ca6d53c6f0af0ac6ce300fdce252ae7d93ba225 100644 (file)
@@ -656,7 +656,7 @@ int posix_acl_update_mode(struct inode *inode, umode_t *mode_p,
        if (error == 0)
                *acl = NULL;
        if (!in_group_p(inode->i_gid) &&
-           !capable_wrt_inode_uidgid(inode, CAP_FSETID))
+           !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
                mode &= ~S_ISGID;
        *mode_p = mode;
        return 0;
index 3fbd98f61ea5c02ce200f6c38cc5e1c8f781f4f3..97bd29fc8c43fbc2189ec2dc5ae8cf8d75cad2ba 100644 (file)
@@ -1502,7 +1502,7 @@ xfs_ioctl_setattr(
         */
 
        if ((VFS_I(ip)->i_mode & (S_ISUID|S_ISGID)) &&
-           !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID))
+           !capable_wrt_inode_uidgid(&init_user_ns, VFS_I(ip), CAP_FSETID))
                VFS_I(ip)->i_mode &= ~(S_ISUID|S_ISGID);
 
        /* Change the ownerships and register project quota modifications */
index b2f698915c0f3f41fbc13c49d0eeedc1c95c0af8..62bfa3ed84d78246cf2cc08b3d57d9226b8fb7df 100644 (file)
@@ -247,8 +247,11 @@ static inline bool ns_capable_setid(struct user_namespace *ns, int cap)
        return true;
 }
 #endif /* CONFIG_MULTIUSER */
-extern bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode);
-extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap);
+bool privileged_wrt_inode_uidgid(struct user_namespace *ns,
+                                struct user_namespace *mnt_userns,
+                                const struct inode *inode);
+bool capable_wrt_inode_uidgid(struct user_namespace *mnt_userns,
+                             const struct inode *inode, int cap);
 extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap);
 extern bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns);
 static inline bool perfmon_capable(void)
index de7eac903a2a5f6e6af42158b753f711dff9093c..46a361dde0421a29e7fdd25386c9e0f1f6e6775a 100644 (file)
@@ -484,10 +484,12 @@ EXPORT_SYMBOL(file_ns_capable);
  *
  * Return true if the inode uid and gid are within the namespace.
  */
-bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode)
+bool privileged_wrt_inode_uidgid(struct user_namespace *ns,
+                                struct user_namespace *mnt_userns,
+                                const struct inode *inode)
 {
-       return kuid_has_mapping(ns, inode->i_uid) &&
-               kgid_has_mapping(ns, inode->i_gid);
+       return kuid_has_mapping(ns, i_uid_into_mnt(mnt_userns, inode)) &&
+              kgid_has_mapping(ns, i_gid_into_mnt(mnt_userns, inode));
 }
 
 /**
@@ -499,11 +501,13 @@ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *
  * its own user namespace and that the given inode's uid and gid are
  * mapped into the current user namespace.
  */
-bool capable_wrt_inode_uidgid(const struct inode *inode, int cap)
+bool capable_wrt_inode_uidgid(struct user_namespace *mnt_userns,
+                             const struct inode *inode, int cap)
 {
        struct user_namespace *ns = current_user_ns();
 
-       return ns_capable(ns, cap) && privileged_wrt_inode_uidgid(ns, inode);
+       return ns_capable(ns, cap) &&
+              privileged_wrt_inode_uidgid(ns, mnt_userns, inode);
 }
 EXPORT_SYMBOL(capable_wrt_inode_uidgid);
 
index bacc1111d871bc257c5f86bbf206430f64cb8d79..88ee345f7bfa4e49176c4837e3a03ac9e7002168 100644 (file)
@@ -489,7 +489,7 @@ int cap_convert_nscap(struct dentry *dentry, const void **ivalue, size_t size)
                return -EINVAL;
        if (!validheader(size, cap))
                return -EINVAL;
-       if (!capable_wrt_inode_uidgid(inode, CAP_SETFCAP))
+       if (!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_SETFCAP))
                return -EPERM;
        if (size == XATTR_CAPS_SZ_2)
                if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP))
@@ -956,7 +956,8 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name)
                struct inode *inode = d_backing_inode(dentry);
                if (!inode)
                        return -EINVAL;
-               if (!capable_wrt_inode_uidgid(inode, CAP_SETFCAP))
+               if (!capable_wrt_inode_uidgid(&init_user_ns, inode,
+                                             CAP_SETFCAP))
                        return -EPERM;
                return 0;
        }