Files
linux/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c
Christian Brauner 1a398a2378 selftests/empty_mntns: fix statmount_alloc() signature mismatch
empty_mntns.h includes ../statmount/statmount.h which provides a
4-argument statmount_alloc(mnt_id, mnt_ns_id, mask, flags), but then
redefines its own 3-argument version without the flags parameter. This
causes a build failure due to conflicting types.

Remove the duplicate definition from empty_mntns.h and update all
callers to pass 0 for the flags argument.

Fixes: 32f54f2bbc ("selftests/filesystems: add tests for empty mount namespaces")
Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-04-14 09:30:48 +02:00

226 lines
5.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test: rootfs overmounted multiple times with chroot into topmost
*
* This test creates a scenario where:
* 1. A new mount namespace is created with a tmpfs root (via pivot_root)
* 2. A mountpoint is created and overmounted multiple times
* 3. The caller chroots into the topmost mount layer
*
* The test verifies that:
* - Multiple overmounts create separate mount layers
* - Each layer's files are isolated
* - chroot correctly sets the process's root to the topmost layer
* - After chroot, only the topmost layer's files are visible
*
* Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/mount.h>
#include <linux/stat.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../utils.h"
#include "empty_mntns.h"
#include "kselftest_harness.h"
#define NR_OVERMOUNTS 5
/*
* Setup a proper root filesystem using pivot_root.
* This ensures we own the root directory in our user namespace.
*/
static int setup_root(void)
{
char tmpdir[] = "/tmp/overmount_test.XXXXXX";
char oldroot[256];
if (!mkdtemp(tmpdir))
return -1;
/* Mount tmpfs at the temporary directory */
if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=10M"))
return -1;
/* Create directory for old root */
snprintf(oldroot, sizeof(oldroot), "%s/oldroot", tmpdir);
if (mkdir(oldroot, 0755))
return -1;
/* pivot_root to use the tmpfs as new root */
if (syscall(SYS_pivot_root, tmpdir, oldroot))
return -1;
if (chdir("/"))
return -1;
/* Unmount old root */
if (umount2("/oldroot", MNT_DETACH))
return -1;
/* Remove oldroot directory */
if (rmdir("/oldroot"))
return -1;
return 0;
}
/*
* Test scenario:
* 1. Enter a user namespace to gain CAP_SYS_ADMIN
* 2. Create a new mount namespace
* 3. Setup a tmpfs root via pivot_root
* 4. Create a mountpoint /newroot and overmount it multiple times
* 5. Create a marker file in each layer
* 6. Chroot into /newroot (the topmost overmount)
* 7. Verify we're in the topmost layer (only topmost marker visible)
*/
TEST(overmount_chroot)
{
pid_t pid;
pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
ssize_t nr_mounts;
uint64_t mnt_ids[NR_OVERMOUNTS + 1];
uint64_t root_id_before, root_id_after;
struct statmount *sm;
char marker[64];
int fd, i;
/* Step 1: Enter user namespace for privileges */
if (enter_userns())
_exit(1);
/* Step 2: Create a new mount namespace */
if (unshare(CLONE_NEWNS))
_exit(2);
/* Step 3: Make the mount tree private */
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
_exit(3);
/* Step 4: Setup a proper tmpfs root via pivot_root */
if (setup_root())
_exit(4);
/* Create the base mount point for overmounting */
if (mkdir("/newroot", 0755))
_exit(5);
/* Mount base tmpfs on /newroot */
if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
_exit(6);
/* Record base mount ID */
mnt_ids[0] = get_unique_mnt_id("/newroot");
if (!mnt_ids[0])
_exit(7);
/* Create marker in base layer */
fd = open("/newroot/layer_0", O_CREAT | O_RDWR, 0644);
if (fd < 0)
_exit(8);
if (write(fd, "layer_0", 7) != 7) {
close(fd);
_exit(9);
}
close(fd);
/* Step 5: Overmount /newroot multiple times with tmpfs */
for (i = 0; i < NR_OVERMOUNTS; i++) {
if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
_exit(10);
/* Record mount ID for this layer */
mnt_ids[i + 1] = get_unique_mnt_id("/newroot");
if (!mnt_ids[i + 1])
_exit(11);
/* Create a marker file in each layer */
snprintf(marker, sizeof(marker), "/newroot/layer_%d", i + 1);
fd = open(marker, O_CREAT | O_RDWR, 0644);
if (fd < 0)
_exit(12);
if (write(fd, marker, strlen(marker)) != (ssize_t)strlen(marker)) {
close(fd);
_exit(13);
}
close(fd);
}
/* Verify mount count increased */
nr_mounts = count_mounts();
if (nr_mounts < NR_OVERMOUNTS + 2)
_exit(14);
/* Record root mount ID before chroot */
root_id_before = get_unique_mnt_id("/newroot");
/* Verify this is the topmost layer's mount */
if (root_id_before != mnt_ids[NR_OVERMOUNTS])
_exit(15);
/* Step 6: Chroot into /newroot (the topmost overmount) */
if (chroot("/newroot"))
_exit(16);
/* Change to root directory within the chroot */
if (chdir("/"))
_exit(17);
/* Step 7: Verify we're in the topmost layer */
root_id_after = get_unique_mnt_id("/");
/* The mount ID should be the same as the topmost layer */
if (root_id_after != mnt_ids[NR_OVERMOUNTS])
_exit(18);
/* Verify the topmost layer's marker file exists */
snprintf(marker, sizeof(marker), "/layer_%d", NR_OVERMOUNTS);
if (access(marker, F_OK))
_exit(19);
/* Verify we cannot see markers from lower layers (they're hidden) */
for (i = 0; i < NR_OVERMOUNTS; i++) {
snprintf(marker, sizeof(marker), "/layer_%d", i);
if (access(marker, F_OK) == 0)
_exit(20);
}
/* Verify the root mount is tmpfs */
sm = statmount_alloc(root_id_after, 0,
STATMOUNT_MNT_BASIC | STATMOUNT_MNT_ROOT |
STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE, 0);
if (!sm)
_exit(21);
if (sm->mask & STATMOUNT_FS_TYPE) {
if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0) {
free(sm);
_exit(22);
}
}
free(sm);
_exit(0);
}
ASSERT_EQ(wait_for_pid(pid), 0);
}
TEST_HARNESS_MAIN