mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 14:04:27 -04:00
selftests/filesystems/overlayfs: test idmapped overlay mounts
Add a selftest for idmapping the merged overlay mount itself. It
applies an idmapping to a freshly created (still detached) overlay
mount with mount_setattr(MOUNT_ATTR_IDMAP) and checks that:
- getattr reports ownership mapped through the mount idmap;
- a file created through the idmapped mount is stored on the upper
layer with the corresponding overlay-final id;
- chown through the idmapped mount round-trips;
- an nfs_export overlay can be idmapped and decodable file handles
round-trip through it with correctly mapped ownership.
The layers live on a private tmpfs and are owned by the host id so that
they map to id 0 through the test idmapping, allowing the root caller
to operate on them.
Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-9-7381632aa402@kernel.org
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
dev_in_maps
|
||||
set_layers_via_fds
|
||||
idmapped_mounts
|
||||
|
||||
@@ -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
|
||||
|
||||
501
tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
Normal file
501
tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user