Re: [PATCH v2 2/3] selftests: overlayfs: reject handles for mixed lower idmaps

From: Amir Goldstein

Date: Mon Sep 21 2026 - 04:17:29 EST


On Sun, Sep 20, 2026 at 10:33 PM Jérémy Jean
<Jeremy.Jean@xxxxxxxxxxxxxxxxx> wrote:
>
> Add an idmapped overlay test for two lower layers that share a backing
> superblock but use different idmaps. Mount with index=on,nfs_export=on and
> verify decodable handle requests fail with EOPNOTSUPP after overlayfs falls
> back to nfs_export=off.
>
> Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
> ---
> .../filesystems/overlayfs/idmapped_mounts.c | 98 ++++++++++++++++++-
> 1 file changed, 94 insertions(+), 4 deletions(-)
>
> diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
> index 44a75839f4ed..0e94bd464f1c 100644
> --- a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
> +++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
> @@ -159,8 +159,16 @@ static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range)
> 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)
> +static int layer_fd(const char *path)
> +{
> + return sys_open_tree(AT_FDCWD, path,
> + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
> +}
> +
> +/* Overlay with lower layers passed by fd plus a plain upper/work. */
> +static int ovl_mount_lower_fds(const char *upper, const char *work,
> + int fd_lower1, int fd_lower2,
> + const char *index, const char *nfs_export)
> {
> int fsfd, ovl;
>
> @@ -171,8 +179,19 @@ static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower)
> 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))
> + sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower1))
> + goto err;
> + if (fd_lower2 >= 0 &&
> + sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower2))
> + goto err;
> + if (index &&
> + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", index, 0))
> + goto err;
> + if (nfs_export &&
> + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export",
> + nfs_export, 0))
> + goto err;
> + if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
> goto err;
>
> ovl = sys_fsmount(fsfd, 0, 0);
> @@ -183,6 +202,23 @@ static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower)
> return -1;
> }
>
> +static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower)
> +{
> + return ovl_mount_lower_fds(upper, work, fd_lower, -1, NULL, NULL);
> +}
> +
> +static int setup_extra_layer(const char *base, const char *name)
> +{
> + char path[PATH_MAX];
> +
> + snprintf(path, sizeof(path), "%s/%s", base, name);
> + if (mkdir(path, 0755) && errno != EEXIST)
> + return -1;
> + if (chown(path, ID_HOST, ID_HOST))
> + return -1;
> + return 0;
> +}
> +
> /*
> * 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
> @@ -498,4 +534,58 @@ TEST_F(idmapped_overlay, nfs_export_handles)
> EXPECT_EQ(close(ovl), 0);
> }
>
> +/*
> + * Lower file handles cannot be decoded safely when two lower layers share a
> + * superblock but use different idmaps. nfs_export should be disabled for that
> + * layout, so a normal decodable handle request must fail with EOPNOTSUPP.
> + */
> +TEST_F(idmapped_overlay, nfs_export_same_sb_mixed_idmaps)
> +{
> + char lower1[PATH_MAX], lower2[PATH_MAX], upper[PATH_MAX], work[PATH_MAX];
> + 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;
> + int fd_lower1, fd_lower2, ovl, mount_id;
> +
> + if (!ovl_supported())
> + SKIP(return, "overlayfs not supported");
> +
> + snprintf(lower1, sizeof(lower1), "%s/l", self->base);
> + snprintf(lower2, sizeof(lower2), "%s/l2", self->base);
> + snprintf(upper, sizeof(upper), "%s/u", self->base);
> + snprintf(work, sizeof(work), "%s/w", self->base);
> + ASSERT_EQ(setup_extra_layer(self->base, "l2"), 0);
> +
> + snprintf(path, sizeof(path), "%s/l2/file", self->base);
> + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
> + ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0);
> +
> + fd_lower1 = idmapped_layer_fd(lower1, ID_HOST, ID_NS, ID_RANGE);
> + ASSERT_GE(fd_lower1, 0);
> + fd_lower2 = layer_fd(lower2);
> + ASSERT_GE(fd_lower2, 0);
> +
> + ovl = ovl_mount_lower_fds(upper, work, fd_lower1, fd_lower2, "on", "on");
> + if (ovl < 0)
> + SKIP(return, "overlayfs nfs_export not supported");
> + EXPECT_EQ(close(fd_lower1), 0);
> + EXPECT_EQ(close(fd_lower2), 0);
> +
> + 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;
> + errno = 0;
> + EXPECT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), -1);
> + EXPECT_EQ(errno, EOPNOTSUPP);
> +
> + EXPECT_EQ(close(ovl), 0);
> +}
> +
> TEST_HARNESS_MAIN

The expected behavior depends on whether Christian says this
setup is legit in production.

Apart from the that you may add:

Reviewed-by: Amir Goldstein <amir73il@xxxxxxxxx>

Thanks,
Amir.