| // 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 |