#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "../wrappers.h"
#include "../utils.h"
#include "../statmount/statmount.h"
#include "../../kselftest_harness.h"
#include <linux/stat.h>
#ifndef MOVE_MOUNT_BENEATH
#define MOVE_MOUNT_BENEATH 0x00000200
#endif
static uint64_t get_unique_mnt_id_fd(int fd)
{
struct statx sx;
int ret;
ret = statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID_UNIQUE, &sx);
if (ret)
return 0;
if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE))
return 0;
return sx.stx_mnt_id;
}
static int setup_locked_overmount(void)
{
if (unshare(CLONE_NEWNS))
return 1;
if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
return 2;
rmdir("/mnt_dir");
if (mkdir("/mnt_dir", 0755))
return 3;
if (mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL))
return 4;
if (mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL))
return 5;
if (setup_userns())
return 6;
if (!umount2("/mnt_dir", MNT_DETACH) || errno != EINVAL)
return 7;
return 0;
}
static int create_detached_tmpfs(void)
{
int fs_fd, mnt_fd;
fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
if (fs_fd < 0)
return -1;
if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
close(fs_fd);
return -1;
}
mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC, 0);
close(fs_fd);
return mnt_fd;
}
FIXTURE(move_mount) {
uint64_t orig_root_id;
};
FIXTURE_SETUP(move_mount)
{
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
self->orig_root_id = get_unique_mnt_id("/");
ASSERT_NE(self->orig_root_id, 0);
}
FIXTURE_TEARDOWN(move_mount)
{
}
TEST_F(move_mount, beneath_rootfs_success)
{
int fd_tree, ret;
uint64_t clone_id, root_id;
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_NE(clone_id, self->orig_root_id);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(fd_tree);
ASSERT_EQ(chroot("."), 0);
root_id = get_unique_mnt_id("/");
ASSERT_NE(root_id, 0);
ASSERT_EQ(root_id, clone_id);
ASSERT_EQ(umount2(".", MNT_DETACH), 0);
}
TEST_F(move_mount, beneath_rootfs_old_root_stacked)
{
int fd_tree, ret;
uint64_t clone_id;
struct statmount sm;
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_NE(clone_id, self->orig_root_id);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(fd_tree);
ASSERT_EQ(chroot("."), 0);
ASSERT_EQ(statmount(self->orig_root_id, 0, 0,
STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0), 0);
ASSERT_EQ(sm.mnt_parent_id, clone_id);
ASSERT_EQ(umount2(".", MNT_DETACH), 0);
}
TEST_F(move_mount, beneath_rootfs_in_chroot_fail)
{
int fd_tree, ret;
uint64_t chroot_id, clone_id;
rmdir("/chroot_dir");
ASSERT_EQ(mkdir("/chroot_dir", 0755), 0);
chroot_id = get_unique_mnt_id("/chroot_dir");
ASSERT_NE(chroot_id, 0);
ASSERT_EQ(self->orig_root_id, chroot_id);
ASSERT_EQ(chdir("/chroot_dir"), 0);
ASSERT_EQ(chroot("."), 0);
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_NE(clone_id, chroot_id);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EINVAL);
close(fd_tree);
}
TEST_F(move_mount, beneath_rootfs_in_chroot_success)
{
int fd_tree, ret;
uint64_t chroot_id, clone_id, root_id;
struct statmount sm;
rmdir("/chroot_dir");
ASSERT_EQ(mkdir("/chroot_dir", 0755), 0);
ASSERT_EQ(mount("tmpfs", "/chroot_dir", "tmpfs", 0, NULL), 0);
chroot_id = get_unique_mnt_id("/chroot_dir");
ASSERT_NE(chroot_id, 0);
ASSERT_EQ(chdir("/chroot_dir"), 0);
ASSERT_EQ(chroot("."), 0);
ASSERT_EQ(get_unique_mnt_id("/"), chroot_id);
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_NE(clone_id, chroot_id);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(fd_tree);
ASSERT_EQ(chroot("."), 0);
root_id = get_unique_mnt_id("/");
ASSERT_NE(root_id, 0);
ASSERT_EQ(root_id, clone_id);
ASSERT_EQ(statmount(chroot_id, 0, 0,
STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0), 0);
ASSERT_EQ(sm.mnt_parent_id, clone_id);
ASSERT_EQ(umount2(".", MNT_DETACH), 0);
}
TEST_F(move_mount, beneath_rootfs_locked_transfer)
{
int fd_tree, ret;
uint64_t clone_id, root_id;
ASSERT_EQ(setup_userns(), 0);
ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC |
AT_RECURSIVE);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH |
MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(fd_tree);
ASSERT_EQ(chroot("."), 0);
root_id = get_unique_mnt_id("/");
ASSERT_EQ(root_id, clone_id);
ASSERT_EQ(umount2(".", MNT_DETACH), 0);
root_id = get_unique_mnt_id("/");
ASSERT_EQ(root_id, clone_id);
}
TEST_F(move_mount, beneath_rootfs_locked_containment)
{
int fd_tree, ret;
uint64_t clone_id, root_id;
ASSERT_EQ(setup_userns(), 0);
ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
ASSERT_EQ(umount2("/", MNT_DETACH), -1);
ASSERT_EQ(errno, EINVAL);
fd_tree = sys_open_tree(AT_FDCWD, "/",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC |
AT_RECURSIVE);
ASSERT_GE(fd_tree, 0);
clone_id = get_unique_mnt_id_fd(fd_tree);
ASSERT_NE(clone_id, 0);
ASSERT_EQ(fchdir(fd_tree), 0);
ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/",
MOVE_MOUNT_F_EMPTY_PATH |
MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(fd_tree);
ASSERT_EQ(chroot("."), 0);
root_id = get_unique_mnt_id("/");
ASSERT_EQ(root_id, clone_id);
ASSERT_EQ(umount2(".", MNT_DETACH), 0);
root_id = get_unique_mnt_id("/");
ASSERT_EQ(root_id, clone_id);
ASSERT_EQ(umount2("/", MNT_DETACH), -1);
ASSERT_EQ(errno, EINVAL);
}
TEST_F(move_mount, beneath_non_rootfs_locked_transfer)
{
int mnt_fd, ret;
uint64_t mnt_new_id, mnt_visible_id;
ASSERT_EQ(setup_locked_overmount(), 0);
mnt_fd = create_detached_tmpfs();
ASSERT_GE(mnt_fd, 0);
mnt_new_id = get_unique_mnt_id_fd(mnt_fd);
ASSERT_NE(mnt_new_id, 0);
ret = sys_move_mount(mnt_fd, "", AT_FDCWD, "/mnt_dir",
MOVE_MOUNT_F_EMPTY_PATH |
MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(mnt_fd);
ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), 0);
mnt_visible_id = get_unique_mnt_id("/mnt_dir");
ASSERT_EQ(mnt_visible_id, mnt_new_id);
}
TEST_F(move_mount, beneath_non_rootfs_locked_containment)
{
int mnt_fd, ret;
uint64_t mnt_new_id, mnt_visible_id;
ASSERT_EQ(setup_locked_overmount(), 0);
mnt_fd = create_detached_tmpfs();
ASSERT_GE(mnt_fd, 0);
mnt_new_id = get_unique_mnt_id_fd(mnt_fd);
ASSERT_NE(mnt_new_id, 0);
ret = sys_move_mount(mnt_fd, "", AT_FDCWD, "/mnt_dir",
MOVE_MOUNT_F_EMPTY_PATH |
MOVE_MOUNT_BENEATH);
ASSERT_EQ(ret, 0);
close(mnt_fd);
ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), 0);
mnt_visible_id = get_unique_mnt_id("/mnt_dir");
ASSERT_EQ(mnt_visible_id, mnt_new_id);
ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), -1);
ASSERT_EQ(errno, EINVAL);
}
TEST_HARNESS_MAIN