diff options
Diffstat (limited to 'tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c')
| -rw-r--r-- | tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c b/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c new file mode 100644 index 000000000000..6e21c58258c3 --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c @@ -0,0 +1,225 @@ +// 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 |
