[PATCH 33/34] overlayfs: handle idmapped merged mounts

Christian Brauner christian.brauner at ubuntu.com
Thu Oct 29 00:32:51 UTC 2020


Now that overlayfs handles idmapped lower and upper mounts enable it to support
idmapped merged directories, i.e. support idmapping overlayfs mounts themselves.

In contrast to supporting lower and upper idmapped mounts this requires very
little changes. Permission checking will continue to work as before, i.e. we
will verify that we are privileged over the overlay inode itself and we are
privileged over the lower and upper inodes.

A very special thank you to my friend Seth Forshee who has given invaluable
advice around the permission checking!

 # This is a directory where all file ownership starts with uid and gid 10000.
 root at f2-vm:/# ls -al /var/lib/lxc/f1/rootfs
 total 108
 drwxr-xr-x  20 10000 10000  4096 Oct 28 11:13 .
 drwxrwx---   4 10000 10000  4096 Oct 28 11:17 ..
 -rw-r--r--   1 10000 10000  7197 Oct 24 09:45 asdf
 drwxr-xr-x   2 10000 10000  4096 Oct 16 19:07 ASDF
 lrwxrwxrwx   1 10000 10000     7 Sep 24 07:43 bin -> usr/bin
 drwxr-xr-x   2 10000 10000  4096 Apr 15  2020 boot
 -rw-r--r--   1 10000 10000 13059 Oct  8 12:38 ccc
 drwxr-xr-x   2 11000 11000  4096 Oct 23 17:10 ddd
 drwxr-xr-x   3 10000 10000  4096 Sep 25 08:04 dev
 drwxr-xr-x  61 10000 10000  4096 Sep 25 08:04 etc

 # Create an idmapped mount on the host such that all files owned by uid and
 # gid 10000 show up as being owned by uid 0 and gid 0.
 /mount2 --idmap both:10000:0:10000 /var/lib/lxc/f1/rootfs/ /lower1/

 # Verify that the files show up as uid and gid 0 under the idmapped mount at /lower1
 root at f2-vm:/# ls -al /lower1/
 total 108
 drwxr-xr-x  20 root   root    4096 Oct 28 11:13 .
 drwxr-xr-x  29 root   root    4096 Oct 28 11:57 ..
 -rw-r--r--   1 root   root    7197 Oct 24 09:45 asdf
 drwxr-xr-x   2 root   root    4096 Oct 16 19:07 ASDF
 lrwxrwxrwx   1 root   root       7 Sep 24 07:43 bin -> usr/bin
 drwxr-xr-x   2 root   root    4096 Apr 15  2020 boot
 -rw-r--r--   1 root   root   13059 Oct  8 12:38 ccc
 drwxr-xr-x   2 ubuntu ubuntu  4096 Oct 23 17:10 ddd
 drwxr-xr-x   3 root   root    4096 Sep 25 08:04 dev
 drwxr-xr-x  61 root   root    4096 Sep 25 08:04 etc

 # Create an idmapped upper mount at /upper. Now, files created as id 0 will
 # show up as id 10000 in /upper and files created as id 1000 will show up as
 # id 11000 under /upper.
 /mount2 --idmap both:10000:0:10000 /upper /upper
 mkdir /upper/upper
 chown 10000:10000 /upper/upper
 mkdir /upper/work
 chown 10000:10000 /upper/work

 # Create an overlayfs mount.
 mount -t overlay overlay -o lowerdir=/lower1/,upperdir=/upper/upper/,workdir=/upper/work/ /merged/

 # Create a new user namespace with an id mapping of 0:20000:10000 and then
 # create an idmapped overlayfs merged mount with the same id mapping.
 ./mount2 -cb:0:20000:10000 -mb:0:20000:10000 /merged/ /merged/

 # Verify that we are in a user namespace with the expected id mapping
 root at f2-vm:/# cat /proc/self/uid_map
          0      20000      10000
 root at f2-vm:/# cat /proc/self/gid_map
          0      20000      10000

 # The merged mount should show up starting from uid and gid 0
 root at f2-vm:/# ls -al /merged/
 total 124
 drwxr-xr-x   1 root   root    4096 Oct 25 23:04 .
 drwxr-xr-x  29 root   root    4096 Oct 28 12:07 ..
 -rw-r--r--   1 root   root    7197 Oct 24 09:45 asdf
 drwxr-xr-x   2 root   root    4096 Oct 16 19:07 ASDF
 lrwxrwxrwx   1 root   root       7 Sep 24 07:43 bin -> usr/bin
 drwxr-xr-x   2 root   root    4096 Apr 15  2020 boot
 -rw-r--r--   1 root   root   13059 Oct  8 12:38 ccc
 drwxr-xr-x   2 ubuntu ubuntu  4096 Oct 23 17:10 ddd
 drwxr-xr-x   3 root   root    4096 Sep 25 08:04 dev
 drwxr-xr-x  61 root   root    4096 Sep 25 08:04 etc

 # Create a file as as root
 root at f2-vm:/merged# touch /merged/A-FILE

 root at f2-vm:/merged# ls -al /merged/A-FILE
 -rw-r--r-- 1 root root 0 Oct 28 12:16 /merged/A-FILE

 # Chown the file to a simple user
 root at f2-vm:/merged# chown 1000:1000 /merged/A-FILE

 root at f2-vm:/merged# ls -al /merged/A-FILE
 -rw-r--r-- 1 ubuntu ubuntu 0 Oct 28 12:16 /merged/A-FILE

 # Create a directory and delegate to simple user
 root at f2-vm:/merged# mkdir /merged/A-DIR

 root at f2-vm:/merged# chown 1000:1000 /merged/A-DIR/

 # Let's look at these files in our idmapped upper directory from within the
 # user namespace. All files should appear to be owned by 65534:65534 since the
 # /upper mount is idmapped to 10000:0:10000 but we are in a user namespace
 # with an idmapping of 0:20000:20000.
 root at f2-vm:/merged# ls -al /upper/upper/
 total 12
 drwxr-xr-x 3 nobody nogroup 4096 Oct 28 13:26 .
 drwxr-xr-x 4 nobody nogroup 4096 Oct 21 13:48 ..
 drwxr-xr-x 2 nobody nogroup 4096 Oct 28 13:26 A-DIR
 -rw-r--r-- 1 nobody nogroup    0 Oct 28 13:25 A-FILE

 # Let's look at this /upper from the initial user namespace (where it is
 # mounted idmapped with 10000:0:10000)
 root at f2-vm:~# ls -al /upper/upper/
 total 12
 drwxr-xr-x 3 root   root   4096 Oct 28 13:26 .
 drwxr-xr-x 4 root   root   4096 Oct 21 13:48 ..
 drwxr-xr-x 2 ubuntu ubuntu 4096 Oct 28 13:26 A-DIR
 -rw-r--r-- 1 ubuntu ubuntu    0 Oct 28 13:25 A-FILE

 # Let's remove the idmapped /upper mount (overlayfs will have it's own private mount anyway)
 umount /upper

 # Let's look at these files in our upper directory with the idmapped mount removed
 ubuntu at f2-vm:/$ ls -al /upper/upper/
 root at f2-vm:~# ls -al /upper/upper/
 total 12
 drwxr-xr-x 3 10000 10000 4096 Oct 28 13:26 .
 drwxr-xr-x 4 10000 10000 4096 Oct 21 13:48 ..
 drwxr-xr-x 2 11000 11000 4096 Oct 28 13:26 A-DIR
 -rw-r--r-- 1 11000 11000    0 Oct 28 13:25 A-FILE

 # Let's create a few acls from the /merged directory on an already existing file
 # triggering a copy-up operation
 root at f2-vm:/merged# setfacl -m u:1000:rwx /merged/asdf
 root at f2-vm:/merged# getfacl /merged/asdf
 getfacl: Removing leading '/' from absolute path names
 # file: merged/asdf
 # owner: root
 # group: root
 user::rw-
 user:ubuntu:rwx
 group::r--
 mask::rwx
 other::r--

 # Let's look at this file from our upper directory from the initial user namespace
 root at f2-vm:/merged# getfacl /upper/upper/asdf
 getfacl: Removing leading '/' from absolute path names
 # file: upper/upper/asdf
 # owner: 10000
 # group: 10000
 user::rw-
 user:11000:rwx
 group::r--
 mask::rwx
 other::r--

Cc: Seth Forshee <seth.forshee at canonical.com>
Cc: Amir Goldstein <amir73il at gmail.com>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 fs/overlayfs/copy_up.c   |  4 +--
 fs/overlayfs/dir.c       | 68 ++++++++++++++++++++++++++++++++--------
 fs/overlayfs/inode.c     | 40 +++++++++++++++++------
 fs/overlayfs/overlayfs.h |  9 +++++-
 fs/overlayfs/super.c     |  3 ++
 5 files changed, 99 insertions(+), 25 deletions(-)

diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 1b8721796fd4..91134203c511 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -268,8 +268,8 @@ int ovl_set_attr(struct user_namespace *user_ns, struct dentry *upperdentry,
 	if (!err) {
 		struct iattr attr = {
 			.ia_valid = ATTR_UID | ATTR_GID,
-			.ia_uid = stat->uid,
-			.ia_gid = stat->gid,
+			.ia_uid = kuid_from_mnt(user_ns, stat->uid),
+			.ia_gid = kgid_from_mnt(user_ns, stat->gid),
 		};
 		err = notify_mapped_change(user_ns, upperdentry, &attr, NULL);
 	}
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 23d09de00957..1c0153b1ad6d 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -625,7 +625,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
 	return err;
 }
 
-static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
+static int ovl_create_object(struct user_namespace *user_ns,
+			     struct dentry *dentry, int mode, dev_t rdev,
 			     const char *link)
 {
 	int err;
@@ -649,7 +650,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
 	inode->i_state |= I_CREATING;
 	spin_unlock(&inode->i_lock);
 
-	inode_init_owner(inode, dentry->d_parent->d_inode, mode);
+	mapped_inode_init_owner(inode, user_ns, dentry->d_parent->d_inode, mode);
 	attr.mode = inode->i_mode;
 
 	err = ovl_create_or_link(dentry, inode, &attr, false);
@@ -663,31 +664,55 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
 	return err;
 }
 
-static int ovl_create(struct inode *dir, struct dentry *dentry, umode_t mode,
-		      bool excl)
+static int ovl_create_mapped(struct user_namespace *user_ns, struct inode *dir,
+			     struct dentry *dentry, umode_t mode, bool excl)
 {
-	return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL);
+	return ovl_create_object(user_ns, dentry, (mode & 07777) | S_IFREG, 0, NULL);
+}
+
+static int ovl_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl)
+{
+	return ovl_create_mapped(&init_user_ns, dir, dentry, mode, excl);
+}
+
+static int ovl_mkdir_mapped(struct user_namespace *user_ns, struct inode *dir,
+			    struct dentry *dentry, umode_t mode)
+{
+	return ovl_create_object(user_ns, dentry,
+				 (mode & 07777) | S_IFDIR, 0, NULL);
 }
 
 static int ovl_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 {
-	return ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL);
+	return ovl_mkdir_mapped(&init_user_ns, dir, dentry, mode);
 }
 
-static int ovl_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
-		     dev_t rdev)
+static int ovl_mknod_mapped(struct user_namespace *user_ns, struct inode *dir,
+			    struct dentry *dentry, umode_t mode, dev_t rdev)
 {
 	/* Don't allow creation of "whiteout" on overlay */
 	if (S_ISCHR(mode) && rdev == WHITEOUT_DEV)
 		return -EPERM;
 
-	return ovl_create_object(dentry, mode, rdev, NULL);
+	return ovl_create_object(user_ns, dentry, mode, rdev, NULL);
+}
+
+static int ovl_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
+		     dev_t rdev)
+{
+	return ovl_mknod_mapped(&init_user_ns, dir, dentry, mode, rdev);
+}
+
+static int ovl_symlink_mapped(struct user_namespace *user_ns, struct inode *dir,
+			      struct dentry *dentry, const char *link)
+{
+	return ovl_create_object(user_ns, dentry, S_IFLNK, 0, link);
 }
 
 static int ovl_symlink(struct inode *dir, struct dentry *dentry,
 		       const char *link)
 {
-	return ovl_create_object(dentry, S_IFLNK, 0, link);
+	return ovl_symlink_mapped(&init_user_ns, dir, dentry, link);
 }
 
 static int ovl_set_link_redirect(struct dentry *dentry)
@@ -1085,9 +1110,10 @@ static int ovl_set_redirect(struct dentry *dentry, bool samedir)
 	return err;
 }
 
-static int ovl_rename(struct inode *olddir, struct dentry *old,
-		      struct inode *newdir, struct dentry *new,
-		      unsigned int flags)
+static int ovl_rename_mapped(struct user_namespace *user_ns,
+			     struct inode *olddir, struct dentry *old,
+			     struct inode *newdir, struct dentry *new,
+			     unsigned int flags)
 {
 	int err;
 	struct dentry *old_upperdir;
@@ -1303,6 +1329,13 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
 	return err;
 }
 
+static int ovl_rename(struct inode *olddir, struct dentry *old,
+		      struct inode *newdir, struct dentry *new,
+		      unsigned int flags)
+{
+	return ovl_rename_mapped(&init_user_ns, olddir, old, newdir, new, flags);
+}
+
 const struct inode_operations ovl_dir_inode_operations = {
 	.lookup		= ovl_lookup,
 	.mkdir		= ovl_mkdir,
@@ -1319,4 +1352,13 @@ const struct inode_operations ovl_dir_inode_operations = {
 	.listxattr	= ovl_listxattr,
 	.get_acl	= ovl_get_acl,
 	.update_time	= ovl_update_time,
+#ifdef CONFIG_IDMAP_MOUNTS
+	.permission_mapped	= ovl_permission_mapped,
+	.mkdir_mapped		= ovl_mkdir_mapped,
+	.create_mapped		= ovl_create_mapped,
+	.mknod_mapped		= ovl_mknod_mapped,
+	.symlink_mapped		= ovl_symlink_mapped,
+	.setattr_mapped		= ovl_setattr_mapped,
+	.rename_mapped		= ovl_rename_mapped,
+#endif
 };
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index b6c8b904f0e7..3831ddbe57f8 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -14,15 +14,15 @@
 #include <linux/fiemap.h>
 #include "overlayfs.h"
 
-
-int ovl_setattr(struct dentry *dentry, struct iattr *attr)
+int ovl_setattr_mapped(struct user_namespace *user_ns, struct dentry *dentry,
+		       struct iattr *attr)
 {
 	int err;
 	bool full_copy_up = false;
 	struct dentry *upperdentry;
 	const struct cred *old_cred;
 
-	err = setattr_prepare(dentry, attr);
+	err = setattr_mapped_prepare(user_ns, dentry, attr);
 	if (err)
 		return err;
 
@@ -104,6 +104,11 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr)
 	return err;
 }
 
+int ovl_setattr(struct dentry *dentry, struct iattr *attr)
+{
+	return ovl_setattr_mapped(&init_user_ns, dentry, attr);
+}
+
 static int ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid)
 {
 	bool samefs = ovl_same_fs(dentry->d_sb);
@@ -167,6 +172,7 @@ static int ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid)
 int ovl_getattr(const struct path *path, struct kstat *stat,
 		u32 request_mask, unsigned int flags)
 {
+	struct user_namespace *user_ns;
 	struct dentry *dentry = path->dentry;
 	enum ovl_path_type type;
 	struct path realpath;
@@ -284,14 +290,19 @@ int ovl_getattr(const struct path *path, struct kstat *stat,
 out:
 	revert_creds(old_cred);
 
+	user_ns = mnt_user_ns(path->mnt);
+	stat->uid = kuid_into_mnt(user_ns, stat->uid);
+	stat->gid = kgid_into_mnt(user_ns, stat->gid);
+
 	return err;
 }
 
-int ovl_permission(struct inode *inode, int mask)
+int ovl_permission_mapped(struct user_namespace *user_ns,
+			  struct inode *inode, int mask)
 {
 	struct inode *upperinode = ovl_inode_upper(inode);
 	struct inode *realinode = upperinode ?: ovl_inode_lower(inode);
-	struct user_namespace *user_ns;
+	struct user_namespace *real_user_ns;
 	const struct cred *old_cred;
 	int err;
 
@@ -302,15 +313,15 @@ int ovl_permission(struct inode *inode, int mask)
 	}
 
 	if (upperinode)
-		user_ns = ovl_upper_mnt_user_ns(OVL_FS(inode->i_sb));
+		real_user_ns = ovl_upper_mnt_user_ns(OVL_FS(inode->i_sb));
 	else
-		user_ns = OVL_I(inode)->lower_user_ns;
+		real_user_ns = OVL_I(inode)->lower_user_ns;
 
 	/*
 	 * Check overlay inode with the creds of task and underlying inode
 	 * with creds of mounter
 	 */
-	err = generic_permission(inode, mask);
+	err = mapped_generic_permission(user_ns, inode, mask);
 	if (err)
 		return err;
 
@@ -322,7 +333,7 @@ int ovl_permission(struct inode *inode, int mask)
 		/* Make sure mounter can read file for copy up later */
 		mask |= MAY_READ;
 	}
-	err = mapped_inode_permission(user_ns, realinode, mask);
+	err = mapped_inode_permission(real_user_ns, realinode, mask);
 	revert_creds(old_cred);
 
 	return err;
@@ -547,6 +558,10 @@ static const struct inode_operations ovl_file_inode_operations = {
 	.get_acl	= ovl_get_acl,
 	.update_time	= ovl_update_time,
 	.fiemap		= ovl_fiemap,
+#ifdef CONFIG_IDMAP_MOUNTS
+	.permission_mapped	= ovl_permission_mapped,
+	.setattr_mapped		= ovl_setattr_mapped,
+#endif
 };
 
 static const struct inode_operations ovl_symlink_inode_operations = {
@@ -555,6 +570,9 @@ static const struct inode_operations ovl_symlink_inode_operations = {
 	.getattr	= ovl_getattr,
 	.listxattr	= ovl_listxattr,
 	.update_time	= ovl_update_time,
+#ifdef CONFIG_IDMAP_MOUNTS
+	.setattr_mapped	= ovl_setattr_mapped,
+#endif
 };
 
 static const struct inode_operations ovl_special_inode_operations = {
@@ -564,6 +582,10 @@ static const struct inode_operations ovl_special_inode_operations = {
 	.listxattr	= ovl_listxattr,
 	.get_acl	= ovl_get_acl,
 	.update_time	= ovl_update_time,
+#ifdef CONFIG_IDMAP_MOUNTS
+	.permission_mapped	= ovl_permission_mapped,
+	.setattr_mapped		= ovl_setattr_mapped,
+#endif
 };
 
 static const struct address_space_operations ovl_aops = {
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 63c257c3bfa8..97c8c34cd6d0 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -470,9 +470,16 @@ unsigned int ovl_get_nlink(struct ovl_fs *ofs, struct dentry *lowerdentry,
 			   struct dentry *upperdentry,
 			   unsigned int fallback);
 int ovl_setattr(struct dentry *dentry, struct iattr *attr);
+int ovl_setattr_mapped(struct user_namespace *user_ns, struct dentry *dentry,
+		       struct iattr *attr);
 int ovl_getattr(const struct path *path, struct kstat *stat,
 		u32 request_mask, unsigned int flags);
-int ovl_permission(struct inode *inode, int mask);
+int ovl_permission_mapped(struct user_namespace *user_ns,
+			  struct inode *inode, int mask);
+static inline int ovl_permission(struct inode *inode, int mask)
+{
+	return ovl_permission_mapped(&init_user_ns, inode, mask);
+}
 int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char *name,
 		  const void *value, size_t size, int flags);
 int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char *name,
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 755f651587a5..8db8ccd70b6f 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -2082,6 +2082,9 @@ static struct file_system_type ovl_fs_type = {
 	.name		= "overlay",
 	.mount		= ovl_mount,
 	.kill_sb	= kill_anon_super,
+#ifdef CONFIG_IDMAP_MOUNTS
+	.fs_flags	= FS_ALLOW_IDMAP,
+#endif
 };
 MODULE_ALIAS_FS("overlay");
 
-- 
2.29.0



More information about the Linux-security-module-archive mailing list