summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 13:38:36 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 13:38:36 -0700
commitaaed66fadba2d2de8fe0daa0aa3eac827d2076b9 (patch)
tree13bf879302b369bc58162772b1b27ff043804dad
parent55668d04e3a821281f885e59ac951a22525b78e3 (diff)
parent077ab8985ee278c3d8618182d335b0f0cd919e16 (diff)
Merge tag 'vfs-7.3-rc1.ovl' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull overlayfs updates from Christian Brauner: "This lets the merged overlayfs mount itself be idmapped through mount_setattr(MOUNT_ATTR_IDMAP), in addition to the already supported idmapped lower and upper layers. The same overlay tree can then be exposed under a different ownership view. Overlayfs already normalizes every underlying id through the relevant layer idmap when ovl_copyattr() copies attributes into the overlay inode. So the overlay inode's i_uid and i_gid are overlay-final ids. The overlay mount idmap composes on top of that and is applied at the overlay-inode boundary only while the underlying layers keep being accessed with the mounter's credentials through their own (possibly idmapped) mounts. So this only changes how the caller sees the overlay inode and never widens the mounter's access to the layers. The second, mounter-credential check in ovl_permission() against the real inode stays on the layer idmap. Most paths need no change because the VFS applies the mount idmap to the overlay inode before overlayfs runs or after it returns at the syscall boundary. Overlayfs only has to change where it bypasses the generic path. This also included is a fix for a double end_creating() on the overlayfs casefold-mismatch path" * tag 'vfs-7.3-rc1.ovl' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: ovl: fix double end_creating() on the casefold-mismatch path ovl: document security.capability idmapping on the xattr forward paths selftests/filesystems/overlayfs: test idmapped overlay mounts selftests/filesystems/overlayfs: fix set_layers_via_fds link error docs: document idmapped overlay mounts ovl: allow idmapping overlay mounts ovl: handle idmapped mounts in ovl_set_acl() ovl: handle idmapped mounts in ovl_getattr() ovl: handle idmapped mounts in ovl_setattr() ovl: handle idmapped mounts in ovl_permission() ovl: handle idmapped mounts in ovl_create_object() and ovl_tmpfile()
-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);