summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/filesystems/overlayfs.rst16
-rw-r--r--fs/overlayfs/dir.c17
-rw-r--r--fs/overlayfs/inode.c26
-rw-r--r--fs/overlayfs/overlayfs.h1
-rw-r--r--fs/overlayfs/super.c2
-rw-r--r--fs/overlayfs/xattrs.c1
-rw-r--r--tools/testing/selftests/filesystems/overlayfs/.gitignore1
-rw-r--r--tools/testing/selftests/filesystems/overlayfs/Makefile2
-rw-r--r--tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c501
-rw-r--r--tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c16
10 files changed, 560 insertions, 23 deletions
diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst
index eb846518e6ac..1a29a5afabd7 100644
--- a/Documentation/filesystems/overlayfs.rst
+++ b/Documentation/filesystems/overlayfs.rst
@@ -347,6 +347,22 @@ The resulting access permissions should be the same. The difference is in
the time of copy (on-demand vs. up-front).
+Idmapped mounts
+---------------
+
+The overlay mount itself can be turned into an idmapped mount by applying an
+idmapping to it with mount_setattr(2) and MOUNT_ATTR_IDMAP, just like for
+other filesystems that support idmapped mounts.
+
+The mount idmapping only changes how ownership and permissions of the overlay
+inodes are presented to and interpreted for the caller. It does not change
+how overlayfs accesses the underlying layers: those are still accessed with
+the stashed mounter's credentials through their own mounts, which may
+themselves be idmapped. The overlay mount idmapping and any layer idmapping
+compose, an underlying id is first mapped according to the relevant layer
+idmapping and then according to the overlay mount idmapping.
+
+
Multiple lower layers
---------------------
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 88bcf98d287d..7beb0af26498 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -188,7 +188,6 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
newdentry);
- end_creating(newdentry);
err = -EINVAL;
}
break;
@@ -689,8 +688,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,
- const char *link)
+static int ovl_create_object(struct mnt_idmap *idmap, struct dentry *dentry,
+ int mode, dev_t rdev, const char *link)
{
int err;
struct inode *inode;
@@ -717,7 +716,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
inode_state_set(inode, I_CREATING);
spin_unlock(&inode->i_lock);
- inode_init_owner(&nop_mnt_idmap, inode, dentry->d_parent->d_inode, mode);
+ inode_init_owner(idmap, inode, dentry->d_parent->d_inode, mode);
attr.mode = inode->i_mode;
err = ovl_create_or_link(dentry, inode, &attr, false);
@@ -734,13 +733,13 @@ out:
static int ovl_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
- return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL);
+ return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL);
}
static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
- return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL));
+ return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL));
}
static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir,
@@ -750,13 +749,13 @@ static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir,
if (S_ISCHR(mode) && rdev == WHITEOUT_DEV)
return -EPERM;
- return ovl_create_object(dentry, mode, rdev, NULL);
+ return ovl_create_object(idmap, dentry, mode, rdev, NULL);
}
static int ovl_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *link)
{
- return ovl_create_object(dentry, S_IFLNK, 0, link);
+ return ovl_create_object(idmap, dentry, S_IFLNK, 0, link);
}
static int ovl_set_link_redirect(struct dentry *dentry)
@@ -1444,7 +1443,7 @@ static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
if (!inode)
goto drop_write;
- inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
+ inode_init_owner(idmap, inode, dir, mode);
err = ovl_create_tmpfile(file, dentry, inode, inode->i_mode);
if (err)
goto put_inode;
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index bc71231cad53..401cb8c75520 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -26,10 +26,18 @@ int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
bool full_copy_up = false;
struct dentry *upperdentry;
- err = setattr_prepare(&nop_mnt_idmap, dentry, attr);
+ err = setattr_prepare(idmap, dentry, attr);
if (err)
return err;
+ /* Rebase ownership from the mount idmap into overlay id space. */
+ if (attr->ia_valid & ATTR_UID)
+ attr->ia_vfsuid = VFSUIDT_INIT(from_vfsuid(idmap,
+ i_user_ns(d_inode(dentry)), attr->ia_vfsuid));
+ if (attr->ia_valid & ATTR_GID)
+ attr->ia_vfsgid = VFSGIDT_INIT(from_vfsgid(idmap,
+ i_user_ns(d_inode(dentry)), attr->ia_vfsgid));
+
if (attr->ia_valid & ATTR_SIZE) {
/* Truncate should trigger data copy up as well */
full_copy_up = true;
@@ -172,6 +180,8 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path,
int fsid = 0;
int err;
bool metacopy_blocks = false;
+ vfsuid_t vfsuid;
+ vfsgid_t vfsgid;
metacopy_blocks = ovl_is_metacopy_dentry(dentry);
@@ -284,6 +294,12 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path,
if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry)))
stat->nlink = dentry->d_inode->i_nlink;
+ /* Map ownership of the real inode through the overlay mount idmap. */
+ vfsuid = make_vfsuid(idmap, i_user_ns(inode), stat->uid);
+ vfsgid = make_vfsgid(idmap, i_user_ns(inode), stat->gid);
+ stat->uid = vfsuid_into_kuid(vfsuid);
+ stat->gid = vfsgid_into_kgid(vfsgid);
+
return err;
}
@@ -306,7 +322,7 @@ int ovl_permission(struct mnt_idmap *idmap,
* Check overlay inode with the creds of task and underlying inode
* with creds of mounter
*/
- err = generic_permission(&nop_mnt_idmap, inode, mask);
+ err = generic_permission(idmap, inode, mask);
if (err)
return err;
@@ -534,7 +550,7 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
return -EOPNOTSUPP;
if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode))
return acl ? -EACCES : 0;
- if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
+ if (!inode_owner_or_capable(idmap, inode))
return -EPERM;
/*
@@ -542,8 +558,8 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
* be done with mounter's capabilities and so that won't do it for us).
*/
if (unlikely(inode->i_mode & S_ISGID) && type == ACL_TYPE_ACCESS &&
- !in_group_p(inode->i_gid) &&
- !capable_wrt_inode_uidgid(&nop_mnt_idmap, inode, CAP_FSETID)) {
+ !in_group_or_capable(idmap, inode,
+ i_gid_into_vfsgid(idmap, inode))) {
struct iattr iattr = { .ia_valid = ATTR_KILL_SGID };
err = ovl_setattr(&nop_mnt_idmap, dentry, &iattr);
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index b75df37f70ac..e0d8c6152e9f 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -320,6 +320,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry,
const char *name, const void *value,
size_t size, int flags)
{
+ /* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */
int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), dentry, name,
value, size, flags);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 60b808b85fc4..e487597337e8 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1574,7 +1574,7 @@ struct file_system_type ovl_fs_type = {
.name = "overlay",
.init_fs_context = ovl_init_fs_context,
.parameters = ovl_parameter_spec,
- .fs_flags = FS_USERNS_MOUNT,
+ .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
.kill_sb = kill_anon_super,
};
MODULE_ALIAS_FS("overlay");
diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c
index 859e80ae6f40..5ae44b9c8790 100644
--- a/fs/overlayfs/xattrs.c
+++ b/fs/overlayfs/xattrs.c
@@ -84,6 +84,7 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char
struct path realpath;
ovl_i_path_real(inode, &realpath);
+ /* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */
with_ovl_creds(dentry->d_sb)
return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size);
}
diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore
index e23a18c8b37f..077f7a128168 100644
--- a/tools/testing/selftests/filesystems/overlayfs/.gitignore
+++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
dev_in_maps
set_layers_via_fds
+idmapped_mounts
diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile
index d3ad4a77db9b..b3185f684add 100644
--- a/tools/testing/selftests/filesystems/overlayfs/Makefile
+++ b/tools/testing/selftests/filesystems/overlayfs/Makefile
@@ -8,7 +8,9 @@ LOCAL_HDRS += ../wrappers.h log.h
TEST_GEN_PROGS := dev_in_maps
TEST_GEN_PROGS += set_layers_via_fds
+TEST_GEN_PROGS += idmapped_mounts
include ../../lib.mk
$(OUTPUT)/set_layers_via_fds: ../utils.c
+$(OUTPUT)/idmapped_mounts: ../utils.c
diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
new file mode 100644
index 000000000000..44a75839f4ed
--- /dev/null
+++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#include <linux/mount.h>
+#include <linux/types.h>
+
+#include "kselftest_harness.h"
+#include "../wrappers.h"
+#include "../utils.h"
+
+/*
+ * An idmapping that maps the mount-visible id range [0, ID_RANGE) onto the
+ * host/overlay-final id range [ID_HOST, ID_HOST + ID_RANGE). Through such an
+ * idmapped overlay mount, an overlay-final id of ID_HOST + n is reported as n,
+ * and an id of n requested through the mount is stored as ID_HOST + n.
+ */
+#define ID_NS 0
+#define ID_HOST 10000
+#define ID_RANGE 10000
+
+/*
+ * For the composition test the lower layer's on-disk ids live in a
+ * separate range and are mapped by an idmapped lower layer onto the
+ * overlay-final range [ID_HOST, ID_HOST + ID_RANGE).
+ */
+#define LAYER_HOST 20000
+
+#ifndef MOUNT_ATTR_IDMAP
+#define MOUNT_ATTR_IDMAP 0x00100000
+#endif
+
+#ifndef __NR_mount_setattr
+#define __NR_mount_setattr 442
+#endif
+
+static inline int sys_mount_setattr(int dfd, const char *path,
+ unsigned int flags,
+ struct mount_attr *attr, size_t size)
+{
+ return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
+}
+
+static bool ovl_supported(void)
+{
+ int fd = sys_fsopen("overlay", 0);
+
+ if (fd < 0)
+ return false;
+ close(fd);
+ return true;
+}
+
+/* base/{l,u,w} owned by ID_HOST so they map to ID_NS through the idmap. */
+static int setup_layers(const char *base)
+{
+ static const char *sub[] = { "", "/l", "/u", "/w" };
+ char path[PATH_MAX];
+
+ for (size_t i = 0; i < ARRAY_SIZE(sub); i++) {
+ snprintf(path, sizeof(path), "%s%s", base, sub[i]);
+ if (mkdir(path, 0755) && errno != EEXIST)
+ return -1;
+ if (i && chown(path, ID_HOST, ID_HOST))
+ return -1;
+ }
+ return 0;
+}
+
+static int ovl_mount(const char *base, bool nfs_export)
+{
+ char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX];
+ int fsfd, ovl;
+
+ snprintf(lower, sizeof(lower), "%s/l", base);
+ snprintf(upper, sizeof(upper), "%s/u", base);
+ snprintf(work, sizeof(work), "%s/w", base);
+
+ fsfd = sys_fsopen("overlay", 0);
+ if (fsfd < 0)
+ return -1;
+
+ if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", lower, 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0))
+ goto err;
+ if (nfs_export &&
+ (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", "on", 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export", "on", 0)))
+ goto err;
+ if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
+ goto err;
+
+ ovl = sys_fsmount(fsfd, 0, 0);
+ close(fsfd);
+ return ovl;
+err:
+ close(fsfd);
+ return -1;
+}
+
+/* Idmap the (still detached, not yet visible) overlay mount @mfd. */
+static int ovl_idmap(int mfd)
+{
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ int ret, userns_fd;
+
+ /*
+ * get_userns_fd(fs_id, mount_id, range): a file whose filesystem id
+ * is fs_id + n is shown through the idmapped mount as mount_id + n.
+ * Here the overlay-final (fs side) range is [ID_HOST, ..) and the
+ * caller-visible (mount side) range is [ID_NS, ..).
+ */
+ userns_fd = get_userns_fd(ID_HOST, ID_NS, ID_RANGE);
+ if (userns_fd < 0)
+ return -1;
+
+ attr.userns_fd = userns_fd;
+ ret = sys_mount_setattr(mfd, "", AT_EMPTY_PATH, &attr, sizeof(attr));
+ close(userns_fd);
+ return ret;
+}
+
+/* Clone @path into a detached, idmapped mount usable as an overlay layer. */
+static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range)
+{
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ int fd_tree, userns_fd;
+
+ fd_tree = sys_open_tree(AT_FDCWD, path,
+ OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ if (fd_tree < 0)
+ return -1;
+ userns_fd = get_userns_fd(nsid, hostid, range);
+ if (userns_fd < 0) {
+ close(fd_tree);
+ return -1;
+ }
+ attr.userns_fd = userns_fd;
+ if (sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr,
+ sizeof(attr))) {
+ close(userns_fd);
+ close(fd_tree);
+ return -1;
+ }
+ close(userns_fd);
+ return fd_tree;
+}
+
+/* Overlay with a layer passed by fd (idmapped) plus a plain upper/work. */
+static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower)
+{
+ int fsfd, ovl;
+
+ fsfd = sys_fsopen("overlay", 0);
+ if (fsfd < 0)
+ return -1;
+
+ if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0) ||
+ sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower) ||
+ sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
+ goto err;
+
+ ovl = sys_fsmount(fsfd, 0, 0);
+ close(fsfd);
+ return ovl;
+err:
+ close(fsfd);
+ return -1;
+}
+
+/*
+ * Mount an overlay inside user namespace @u1 (so the overlay sb's s_user_ns is
+ * not the initial namespace) and idmap that overlay mount with @u2. Runs in a
+ * child that joins @u1; returns 0 on success.
+ */
+static int userns_overlay_child(int u1)
+{
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ struct stat st;
+ int ovl, u2;
+
+ /* Become root in the overlay sb's user namespace u1. */
+ if (!switch_userns(u1, 0, 0, false))
+ return fprintf(stderr, "userns: switch_userns: %m\n"), -1;
+ if (unshare(CLONE_NEWNS) ||
+ sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL))
+ return fprintf(stderr, "userns: unshare/slave: %m\n"), -1;
+ if (sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL))
+ return fprintf(stderr, "userns: mount tmpfs: %m\n"), -1;
+ if (setup_layers("/tmp/ovl"))
+ return fprintf(stderr, "userns: setup_layers: %m\n"), -1;
+ if (mknod("/tmp/ovl/l/file", S_IFREG | 0644, 0) ||
+ chown("/tmp/ovl/l/file", ID_HOST + 5, ID_HOST + 5))
+ return fprintf(stderr, "userns: lower file: %m\n"), -1;
+
+ ovl = ovl_mount("/tmp/ovl", false);
+ if (ovl < 0)
+ return fprintf(stderr, "userns: ovl_mount: %m\n"), -1;
+
+ /*
+ * mount_setattr() requires CAP_SYS_ADMIN over the idmap user
+ * namespace, so it must be a child of u1. Create it now, from
+ * inside u1.
+ */
+ u2 = get_userns_fd(ID_HOST, ID_NS, ID_RANGE);
+ if (u2 < 0)
+ return fprintf(stderr, "userns: get_userns_fd: %m\n"), -1;
+ attr.userns_fd = u2;
+ if (sys_mount_setattr(ovl, "", AT_EMPTY_PATH, &attr, sizeof(attr)))
+ return fprintf(stderr, "userns: mount_setattr: %m\n"), -1;
+ close(u2);
+
+ if (fstatat(ovl, "file", &st, 0))
+ return fprintf(stderr, "userns: fstatat: %m\n"), -1;
+ if (st.st_uid != ID_NS + 5 || st.st_gid != ID_NS + 5) {
+ fprintf(stderr, "userns: got %u:%u expected %u:%u\n",
+ st.st_uid, st.st_gid, ID_NS + 5, ID_NS + 5);
+ return -1;
+ }
+ return 0;
+}
+
+FIXTURE(idmapped_overlay) {
+ char base[64];
+};
+
+FIXTURE_SETUP(idmapped_overlay)
+{
+ /* Private mount namespace so test mounts need no cleanup. */
+ ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+ ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0);
+
+ /* tmpfs for the layers so we can chown them to arbitrary ids. */
+ ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0);
+
+ snprintf(self->base, sizeof(self->base), "/tmp/ovl");
+ ASSERT_EQ(setup_layers(self->base), 0);
+}
+
+FIXTURE_TEARDOWN(idmapped_overlay)
+{
+}
+
+/* A file owned by ID_HOST + 5 is reported as ID_NS + 5 through the idmap. */
+TEST_F(idmapped_overlay, getattr)
+{
+ char path[PATH_MAX];
+ struct stat st;
+ int ovl;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ snprintf(path, sizeof(path), "%s/l/file", self->base);
+ ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
+ ASSERT_EQ(chown(path, ID_HOST + 5, ID_HOST + 5), 0);
+
+ ovl = ovl_mount(self->base, false);
+ ASSERT_GE(ovl, 0);
+ ASSERT_EQ(ovl_idmap(ovl), 0);
+
+ ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0);
+ EXPECT_EQ(st.st_uid, ID_NS + 5);
+ EXPECT_EQ(st.st_gid, ID_NS + 5);
+
+ EXPECT_EQ(close(ovl), 0);
+}
+
+/*
+ * Every creation path initializes the new owner through the mount idmap:
+ * created as caller id ID_NS, stored on the upper layer as overlay-final
+ * ID_HOST. Covers ovl_create() (regular file), ovl_mkdir(), ovl_mknod()
+ * and ovl_symlink() (which share ovl_create_object()), plus the separate
+ * ovl_tmpfile() path.
+ */
+TEST_F(idmapped_overlay, create)
+{
+ static const char *names[] = { "reg", "dir", "fifo", "lnk" };
+ char path[PATH_MAX];
+ struct stat st;
+ int ovl, fd;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ ovl = ovl_mount(self->base, false);
+ ASSERT_GE(ovl, 0);
+ ASSERT_EQ(ovl_idmap(ovl), 0);
+
+ /* One object per creation operation, all as caller id ID_NS. */
+ fd = openat(ovl, "reg", O_CREAT | O_WRONLY | O_EXCL, 0644);
+ ASSERT_GE(fd, 0);
+ EXPECT_EQ(close(fd), 0);
+ ASSERT_EQ(mkdirat(ovl, "dir", 0755), 0);
+ ASSERT_EQ(mknodat(ovl, "fifo", S_IFIFO | 0644, 0), 0);
+ ASSERT_EQ(symlinkat("target", ovl, "lnk"), 0);
+
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ /* Reported as ID_NS through the idmapped mount ... */
+ ASSERT_EQ(fstatat(ovl, names[i], &st, AT_SYMLINK_NOFOLLOW), 0);
+ EXPECT_EQ(st.st_uid, ID_NS);
+ EXPECT_EQ(st.st_gid, ID_NS);
+ /* ... and stored as ID_HOST on the upper layer. */
+ snprintf(path, sizeof(path), "%s/u/%s", self->base, names[i]);
+ ASSERT_EQ(lstat(path, &st), 0);
+ EXPECT_EQ(st.st_uid, ID_HOST);
+ EXPECT_EQ(st.st_gid, ID_HOST);
+ }
+
+ /* O_TMPFILE goes through the separate ovl_tmpfile() path. */
+ fd = openat(ovl, ".", O_TMPFILE | O_WRONLY, 0644);
+ ASSERT_GE(fd, 0);
+ /* Inside the mount: caller id ID_NS. */
+ ASSERT_EQ(fstat(fd, &st), 0);
+ EXPECT_EQ(st.st_uid, ID_NS);
+ EXPECT_EQ(st.st_gid, ID_NS);
+ /* Link it in so the upper backing file can be inspected too. */
+ ASSERT_EQ(linkat(fd, "", ovl, "tmp", AT_EMPTY_PATH), 0);
+ EXPECT_EQ(close(fd), 0);
+ snprintf(path, sizeof(path), "%s/u/tmp", self->base);
+ ASSERT_EQ(lstat(path, &st), 0);
+ EXPECT_EQ(st.st_uid, ID_HOST);
+ EXPECT_EQ(st.st_gid, ID_HOST);
+
+ EXPECT_EQ(close(ovl), 0);
+}
+
+/* chown through the idmapped mount round-trips: ID_NS + 5 <-> ID_HOST + 5. */
+TEST_F(idmapped_overlay, chown)
+{
+ char path[PATH_MAX];
+ struct stat st;
+ int ovl, fd;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ ovl = ovl_mount(self->base, false);
+ ASSERT_GE(ovl, 0);
+ ASSERT_EQ(ovl_idmap(ovl), 0);
+
+ fd = openat(ovl, "f", O_CREAT | O_WRONLY | O_EXCL, 0644);
+ ASSERT_GE(fd, 0);
+ EXPECT_EQ(close(fd), 0);
+
+ ASSERT_EQ(fchownat(ovl, "f", ID_NS + 5, ID_NS + 5, 0), 0);
+
+ ASSERT_EQ(fstatat(ovl, "f", &st, 0), 0);
+ EXPECT_EQ(st.st_uid, ID_NS + 5);
+ EXPECT_EQ(st.st_gid, ID_NS + 5);
+
+ snprintf(path, sizeof(path), "%s/u/f", self->base);
+ ASSERT_EQ(stat(path, &st), 0);
+ EXPECT_EQ(st.st_uid, ID_HOST + 5);
+ EXPECT_EQ(st.st_gid, ID_HOST + 5);
+
+ EXPECT_EQ(close(ovl), 0);
+}
+
+/*
+ * Composition: an idmapped lower layer underneath an idmapped overlay mount.
+ * An on-disk id is mapped by the layer idmap into the overlay-final range and
+ * then by the mount idmap into the caller's range:
+ *
+ * on-disk LAYER_HOST+7 --layer--> ID_HOST+7 --mount--> ID_NS+7
+ */
+TEST_F(idmapped_overlay, composition)
+{
+ char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX], path[PATH_MAX];
+ struct stat st;
+ int ovl, fd_lower;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ snprintf(lower, sizeof(lower), "%s/l", self->base);
+ snprintf(upper, sizeof(upper), "%s/u", self->base);
+ snprintf(work, sizeof(work), "%s/w", self->base);
+
+ /* Put the lower layer's ids in the on-disk [LAYER_HOST, ..) range. */
+ ASSERT_EQ(chown(lower, LAYER_HOST, LAYER_HOST), 0);
+ snprintf(path, sizeof(path), "%s/l/file", self->base);
+ ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
+ ASSERT_EQ(chown(path, LAYER_HOST + 7, LAYER_HOST + 7), 0);
+
+ /* Idmapped lower: on-disk LAYER_HOST <-> overlay-final ID_HOST. */
+ fd_lower = idmapped_layer_fd(lower, LAYER_HOST, ID_HOST, ID_RANGE);
+ ASSERT_GE(fd_lower, 0);
+
+ ovl = ovl_mount_lower_fd(upper, work, fd_lower);
+ ASSERT_GE(ovl, 0);
+ EXPECT_EQ(close(fd_lower), 0);
+
+ /* Idmap the overlay mount: overlay-final ID_HOST <-> caller ID_NS. */
+ ASSERT_EQ(ovl_idmap(ovl), 0);
+
+ ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0);
+ EXPECT_EQ(st.st_uid, ID_NS + 7);
+ EXPECT_EQ(st.st_gid, ID_NS + 7);
+
+ EXPECT_EQ(close(ovl), 0);
+}
+
+/* An idmapped overlay mount whose sb lives inside a user namespace. */
+TEST_F(idmapped_overlay, userns)
+{
+ int u1;
+ pid_t pid;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ /* u1 backs the overlay sb: identity-mapped, but not the init ns. */
+ u1 = get_userns_fd(0, 0, 65536);
+ if (u1 < 0)
+ SKIP(return, "user namespaces not available");
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0) {
+ int ret = userns_overlay_child(u1);
+
+ _exit(ret ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+ EXPECT_EQ(wait_for_pid(pid), 0);
+
+ EXPECT_EQ(close(u1), 0);
+}
+
+/*
+ * An nfs_export overlay can be idmapped, and decodable file handles round-trip
+ * through the idmapped mount with correctly mapped ownership. Overlay file
+ * handles encode object identity, not ownership, so the mount idmap does not
+ * affect them; it only maps the owner reported once a handle is reopened.
+ */
+TEST_F(idmapped_overlay, nfs_export_handles)
+{
+ char path[PATH_MAX], mnt[128];
+ union {
+ struct file_handle fh;
+ char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ];
+ } fhu;
+ struct file_handle *fh = &fhu.fh;
+ struct stat st;
+ int ovl, mfd, fd, mount_id;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ snprintf(path, sizeof(path), "%s/l/file", self->base);
+ ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
+ ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0);
+
+ /* nfs_export=on gives decodable overlay file handles. */
+ ovl = ovl_mount(self->base, true);
+ if (ovl < 0)
+ SKIP(return, "overlayfs nfs_export not supported");
+ ASSERT_EQ(ovl_idmap(ovl), 0);
+
+ /* Attach the idmapped mount so handles can be resolved against it. */
+ snprintf(mnt, sizeof(mnt), "%s/mnt", self->base);
+ ASSERT_EQ(mkdir(mnt, 0755), 0);
+ ASSERT_EQ(sys_move_mount(ovl, "", AT_FDCWD, mnt,
+ MOVE_MOUNT_F_EMPTY_PATH), 0);
+
+ snprintf(path, sizeof(path), "%s/file", mnt);
+ fh->handle_bytes = MAX_HANDLE_SZ;
+ ASSERT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), 0);
+
+ mfd = open(mnt, O_RDONLY | O_DIRECTORY);
+ ASSERT_GE(mfd, 0);
+ fd = open_by_handle_at(mfd, fh, O_RDONLY);
+ EXPECT_EQ(close(mfd), 0);
+ ASSERT_GE(fd, 0);
+
+ ASSERT_EQ(fstat(fd, &st), 0);
+ EXPECT_EQ(st.st_uid, ID_NS + 7);
+ EXPECT_EQ(st.st_gid, ID_NS + 7);
+
+ EXPECT_EQ(close(fd), 0);
+ EXPECT_EQ(close(ovl), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c
index 3c0b93183348..7a293544233d 100644
--- a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c
+++ b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c
@@ -624,7 +624,7 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds)
ASSERT_EQ(sys_move_mount(fd_tmpfs, "", -EBADF, "/set_layers_via_fds_tmpfs", MOVE_MOUNT_F_EMPTY_PATH), 0);
- fd_tmp = open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ fd_tmp = sys_open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tmp, 0);
layer_fds[0] = openat(fd_tmp, "upper", O_CLOEXEC | O_DIRECTORY | O_PATH);
@@ -633,25 +633,25 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds)
layer_fds[1] = openat(fd_tmp, "work", O_CLOEXEC | O_DIRECTORY | O_PATH);
ASSERT_GE(layer_fds[1], 0);
- layer_fds[2] = open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[2] = sys_open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[2], 0);
- layer_fds[3] = open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[3] = sys_open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[3], 0);
- layer_fds[4] = open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[4] = sys_open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[4], 0);
- layer_fds[5] = open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[5] = sys_open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[5], 0);
- layer_fds[6] = open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[6] = sys_open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[6], 0);
- layer_fds[7] = open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[7] = sys_open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[7], 0);
- layer_fds[8] = open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ layer_fds[8] = sys_open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(layer_fds[8], 0);
ASSERT_EQ(close(fd_tmpfs), 0);