132f54f2bSChristian Brauner // SPDX-License-Identifier: GPL-2.0-or-later 232f54f2bSChristian Brauner /* 332f54f2bSChristian Brauner * Test: rootfs overmounted multiple times with chroot into topmost 432f54f2bSChristian Brauner * 532f54f2bSChristian Brauner * This test creates a scenario where: 632f54f2bSChristian Brauner * 1. A new mount namespace is created with a tmpfs root (via pivot_root) 732f54f2bSChristian Brauner * 2. A mountpoint is created and overmounted multiple times 832f54f2bSChristian Brauner * 3. The caller chroots into the topmost mount layer 932f54f2bSChristian Brauner * 1032f54f2bSChristian Brauner * The test verifies that: 1132f54f2bSChristian Brauner * - Multiple overmounts create separate mount layers 1232f54f2bSChristian Brauner * - Each layer's files are isolated 1332f54f2bSChristian Brauner * - chroot correctly sets the process's root to the topmost layer 1432f54f2bSChristian Brauner * - After chroot, only the topmost layer's files are visible 1532f54f2bSChristian Brauner * 1632f54f2bSChristian Brauner * Copyright (c) 2024 Christian Brauner <brauner@kernel.org> 1732f54f2bSChristian Brauner */ 1832f54f2bSChristian Brauner 1932f54f2bSChristian Brauner #define _GNU_SOURCE 2032f54f2bSChristian Brauner #include <fcntl.h> 2132f54f2bSChristian Brauner #include <linux/mount.h> 2232f54f2bSChristian Brauner #include <linux/stat.h> 2332f54f2bSChristian Brauner #include <sched.h> 2432f54f2bSChristian Brauner #include <stdio.h> 2532f54f2bSChristian Brauner #include <string.h> 2632f54f2bSChristian Brauner #include <sys/mount.h> 2732f54f2bSChristian Brauner #include <sys/stat.h> 2832f54f2bSChristian Brauner #include <sys/syscall.h> 2932f54f2bSChristian Brauner #include <sys/types.h> 3032f54f2bSChristian Brauner #include <sys/wait.h> 3132f54f2bSChristian Brauner #include <unistd.h> 3232f54f2bSChristian Brauner 3332f54f2bSChristian Brauner #include "../utils.h" 3432f54f2bSChristian Brauner #include "empty_mntns.h" 3532f54f2bSChristian Brauner #include "kselftest_harness.h" 3632f54f2bSChristian Brauner 3732f54f2bSChristian Brauner #define NR_OVERMOUNTS 5 3832f54f2bSChristian Brauner 3932f54f2bSChristian Brauner /* 4032f54f2bSChristian Brauner * Setup a proper root filesystem using pivot_root. 4132f54f2bSChristian Brauner * This ensures we own the root directory in our user namespace. 4232f54f2bSChristian Brauner */ 4332f54f2bSChristian Brauner static int setup_root(void) 4432f54f2bSChristian Brauner { 4532f54f2bSChristian Brauner char tmpdir[] = "/tmp/overmount_test.XXXXXX"; 4632f54f2bSChristian Brauner char oldroot[256]; 4732f54f2bSChristian Brauner 4832f54f2bSChristian Brauner if (!mkdtemp(tmpdir)) 4932f54f2bSChristian Brauner return -1; 5032f54f2bSChristian Brauner 5132f54f2bSChristian Brauner /* Mount tmpfs at the temporary directory */ 5232f54f2bSChristian Brauner if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=10M")) 5332f54f2bSChristian Brauner return -1; 5432f54f2bSChristian Brauner 5532f54f2bSChristian Brauner /* Create directory for old root */ 5632f54f2bSChristian Brauner snprintf(oldroot, sizeof(oldroot), "%s/oldroot", tmpdir); 5732f54f2bSChristian Brauner if (mkdir(oldroot, 0755)) 5832f54f2bSChristian Brauner return -1; 5932f54f2bSChristian Brauner 6032f54f2bSChristian Brauner /* pivot_root to use the tmpfs as new root */ 6132f54f2bSChristian Brauner if (syscall(SYS_pivot_root, tmpdir, oldroot)) 6232f54f2bSChristian Brauner return -1; 6332f54f2bSChristian Brauner 6432f54f2bSChristian Brauner if (chdir("/")) 6532f54f2bSChristian Brauner return -1; 6632f54f2bSChristian Brauner 6732f54f2bSChristian Brauner /* Unmount old root */ 6832f54f2bSChristian Brauner if (umount2("/oldroot", MNT_DETACH)) 6932f54f2bSChristian Brauner return -1; 7032f54f2bSChristian Brauner 7132f54f2bSChristian Brauner /* Remove oldroot directory */ 7232f54f2bSChristian Brauner if (rmdir("/oldroot")) 7332f54f2bSChristian Brauner return -1; 7432f54f2bSChristian Brauner 7532f54f2bSChristian Brauner return 0; 7632f54f2bSChristian Brauner } 7732f54f2bSChristian Brauner 7832f54f2bSChristian Brauner /* 7932f54f2bSChristian Brauner * Test scenario: 8032f54f2bSChristian Brauner * 1. Enter a user namespace to gain CAP_SYS_ADMIN 8132f54f2bSChristian Brauner * 2. Create a new mount namespace 8232f54f2bSChristian Brauner * 3. Setup a tmpfs root via pivot_root 8332f54f2bSChristian Brauner * 4. Create a mountpoint /newroot and overmount it multiple times 8432f54f2bSChristian Brauner * 5. Create a marker file in each layer 8532f54f2bSChristian Brauner * 6. Chroot into /newroot (the topmost overmount) 8632f54f2bSChristian Brauner * 7. Verify we're in the topmost layer (only topmost marker visible) 8732f54f2bSChristian Brauner */ 8832f54f2bSChristian Brauner TEST(overmount_chroot) 8932f54f2bSChristian Brauner { 9032f54f2bSChristian Brauner pid_t pid; 9132f54f2bSChristian Brauner 9232f54f2bSChristian Brauner pid = fork(); 9332f54f2bSChristian Brauner ASSERT_GE(pid, 0); 9432f54f2bSChristian Brauner 9532f54f2bSChristian Brauner if (pid == 0) { 9632f54f2bSChristian Brauner ssize_t nr_mounts; 9732f54f2bSChristian Brauner uint64_t mnt_ids[NR_OVERMOUNTS + 1]; 9832f54f2bSChristian Brauner uint64_t root_id_before, root_id_after; 9932f54f2bSChristian Brauner struct statmount *sm; 10032f54f2bSChristian Brauner char marker[64]; 10132f54f2bSChristian Brauner int fd, i; 10232f54f2bSChristian Brauner 10332f54f2bSChristian Brauner /* Step 1: Enter user namespace for privileges */ 10432f54f2bSChristian Brauner if (enter_userns()) 10532f54f2bSChristian Brauner _exit(1); 10632f54f2bSChristian Brauner 10732f54f2bSChristian Brauner /* Step 2: Create a new mount namespace */ 10832f54f2bSChristian Brauner if (unshare(CLONE_NEWNS)) 10932f54f2bSChristian Brauner _exit(2); 11032f54f2bSChristian Brauner 11132f54f2bSChristian Brauner /* Step 3: Make the mount tree private */ 11232f54f2bSChristian Brauner if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) 11332f54f2bSChristian Brauner _exit(3); 11432f54f2bSChristian Brauner 11532f54f2bSChristian Brauner /* Step 4: Setup a proper tmpfs root via pivot_root */ 11632f54f2bSChristian Brauner if (setup_root()) 11732f54f2bSChristian Brauner _exit(4); 11832f54f2bSChristian Brauner 11932f54f2bSChristian Brauner /* Create the base mount point for overmounting */ 12032f54f2bSChristian Brauner if (mkdir("/newroot", 0755)) 12132f54f2bSChristian Brauner _exit(5); 12232f54f2bSChristian Brauner 12332f54f2bSChristian Brauner /* Mount base tmpfs on /newroot */ 12432f54f2bSChristian Brauner if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M")) 12532f54f2bSChristian Brauner _exit(6); 12632f54f2bSChristian Brauner 12732f54f2bSChristian Brauner /* Record base mount ID */ 12832f54f2bSChristian Brauner mnt_ids[0] = get_unique_mnt_id("/newroot"); 12932f54f2bSChristian Brauner if (!mnt_ids[0]) 13032f54f2bSChristian Brauner _exit(7); 13132f54f2bSChristian Brauner 13232f54f2bSChristian Brauner /* Create marker in base layer */ 13332f54f2bSChristian Brauner fd = open("/newroot/layer_0", O_CREAT | O_RDWR, 0644); 13432f54f2bSChristian Brauner if (fd < 0) 13532f54f2bSChristian Brauner _exit(8); 13632f54f2bSChristian Brauner if (write(fd, "layer_0", 7) != 7) { 13732f54f2bSChristian Brauner close(fd); 13832f54f2bSChristian Brauner _exit(9); 13932f54f2bSChristian Brauner } 14032f54f2bSChristian Brauner close(fd); 14132f54f2bSChristian Brauner 14232f54f2bSChristian Brauner /* Step 5: Overmount /newroot multiple times with tmpfs */ 14332f54f2bSChristian Brauner for (i = 0; i < NR_OVERMOUNTS; i++) { 14432f54f2bSChristian Brauner if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M")) 14532f54f2bSChristian Brauner _exit(10); 14632f54f2bSChristian Brauner 14732f54f2bSChristian Brauner /* Record mount ID for this layer */ 14832f54f2bSChristian Brauner mnt_ids[i + 1] = get_unique_mnt_id("/newroot"); 14932f54f2bSChristian Brauner if (!mnt_ids[i + 1]) 15032f54f2bSChristian Brauner _exit(11); 15132f54f2bSChristian Brauner 15232f54f2bSChristian Brauner /* Create a marker file in each layer */ 15332f54f2bSChristian Brauner snprintf(marker, sizeof(marker), "/newroot/layer_%d", i + 1); 15432f54f2bSChristian Brauner fd = open(marker, O_CREAT | O_RDWR, 0644); 15532f54f2bSChristian Brauner if (fd < 0) 15632f54f2bSChristian Brauner _exit(12); 15732f54f2bSChristian Brauner 15832f54f2bSChristian Brauner if (write(fd, marker, strlen(marker)) != (ssize_t)strlen(marker)) { 15932f54f2bSChristian Brauner close(fd); 16032f54f2bSChristian Brauner _exit(13); 16132f54f2bSChristian Brauner } 16232f54f2bSChristian Brauner close(fd); 16332f54f2bSChristian Brauner } 16432f54f2bSChristian Brauner 16532f54f2bSChristian Brauner /* Verify mount count increased */ 16632f54f2bSChristian Brauner nr_mounts = count_mounts(); 16732f54f2bSChristian Brauner if (nr_mounts < NR_OVERMOUNTS + 2) 16832f54f2bSChristian Brauner _exit(14); 16932f54f2bSChristian Brauner 17032f54f2bSChristian Brauner /* Record root mount ID before chroot */ 17132f54f2bSChristian Brauner root_id_before = get_unique_mnt_id("/newroot"); 17232f54f2bSChristian Brauner 17332f54f2bSChristian Brauner /* Verify this is the topmost layer's mount */ 17432f54f2bSChristian Brauner if (root_id_before != mnt_ids[NR_OVERMOUNTS]) 17532f54f2bSChristian Brauner _exit(15); 17632f54f2bSChristian Brauner 17732f54f2bSChristian Brauner /* Step 6: Chroot into /newroot (the topmost overmount) */ 17832f54f2bSChristian Brauner if (chroot("/newroot")) 17932f54f2bSChristian Brauner _exit(16); 18032f54f2bSChristian Brauner 18132f54f2bSChristian Brauner /* Change to root directory within the chroot */ 18232f54f2bSChristian Brauner if (chdir("/")) 18332f54f2bSChristian Brauner _exit(17); 18432f54f2bSChristian Brauner 18532f54f2bSChristian Brauner /* Step 7: Verify we're in the topmost layer */ 18632f54f2bSChristian Brauner root_id_after = get_unique_mnt_id("/"); 18732f54f2bSChristian Brauner 18832f54f2bSChristian Brauner /* The mount ID should be the same as the topmost layer */ 18932f54f2bSChristian Brauner if (root_id_after != mnt_ids[NR_OVERMOUNTS]) 19032f54f2bSChristian Brauner _exit(18); 19132f54f2bSChristian Brauner 19232f54f2bSChristian Brauner /* Verify the topmost layer's marker file exists */ 19332f54f2bSChristian Brauner snprintf(marker, sizeof(marker), "/layer_%d", NR_OVERMOUNTS); 19432f54f2bSChristian Brauner if (access(marker, F_OK)) 19532f54f2bSChristian Brauner _exit(19); 19632f54f2bSChristian Brauner 19732f54f2bSChristian Brauner /* Verify we cannot see markers from lower layers (they're hidden) */ 19832f54f2bSChristian Brauner for (i = 0; i < NR_OVERMOUNTS; i++) { 19932f54f2bSChristian Brauner snprintf(marker, sizeof(marker), "/layer_%d", i); 20032f54f2bSChristian Brauner if (access(marker, F_OK) == 0) 20132f54f2bSChristian Brauner _exit(20); 20232f54f2bSChristian Brauner } 20332f54f2bSChristian Brauner 20432f54f2bSChristian Brauner /* Verify the root mount is tmpfs */ 20532f54f2bSChristian Brauner sm = statmount_alloc(root_id_after, 0, 20632f54f2bSChristian Brauner STATMOUNT_MNT_BASIC | STATMOUNT_MNT_ROOT | 207*1a398a23SChristian Brauner STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE, 0); 20832f54f2bSChristian Brauner if (!sm) 20932f54f2bSChristian Brauner _exit(21); 21032f54f2bSChristian Brauner 21132f54f2bSChristian Brauner if (sm->mask & STATMOUNT_FS_TYPE) { 21232f54f2bSChristian Brauner if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0) { 21332f54f2bSChristian Brauner free(sm); 21432f54f2bSChristian Brauner _exit(22); 21532f54f2bSChristian Brauner } 21632f54f2bSChristian Brauner } 21732f54f2bSChristian Brauner 21832f54f2bSChristian Brauner free(sm); 21932f54f2bSChristian Brauner _exit(0); 22032f54f2bSChristian Brauner } 22132f54f2bSChristian Brauner 22232f54f2bSChristian Brauner ASSERT_EQ(wait_for_pid(pid), 0); 22332f54f2bSChristian Brauner } 22432f54f2bSChristian Brauner 22532f54f2bSChristian Brauner TEST_HARNESS_MAIN 226