root/tools/testing/selftests/filesystems/empty_mntns/clone3_empty_mntns_test.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Tests for empty mount namespace creation via clone3() CLONE_EMPTY_MNTNS
 *
 * These tests exercise the clone3() code path for creating empty mount
 * namespaces, which is distinct from the unshare() path tested in
 * empty_mntns_test.c.  With clone3(), CLONE_EMPTY_MNTNS (0x2000000000ULL)
 * is a 64-bit flag that implies CLONE_NEWNS.  The implication happens in
 * kernel_clone() before copy_process(), unlike unshare() where it goes
 * through UNSHARE_EMPTY_MNTNS -> CLONE_EMPTY_MNTNS conversion in
 * unshare_nsproxy_namespaces().
 *
 * Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/mount.h>
#include <linux/stat.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "../utils.h"
#include "../wrappers.h"
#include "clone3/clone3_selftests.h"
#include "empty_mntns.h"
#include "kselftest_harness.h"

static pid_t clone3_empty_mntns(uint64_t extra_flags)
{
        struct __clone_args args = {
                .flags          = CLONE_EMPTY_MNTNS | extra_flags,
                .exit_signal    = SIGCHLD,
        };

        return sys_clone3(&args, sizeof(args));
}

static bool clone3_empty_mntns_supported(void)
{
        pid_t pid;
        int status;

        pid = fork();
        if (pid < 0)
                return false;

        if (pid == 0) {
                if (enter_userns())
                        _exit(1);

                pid = clone3_empty_mntns(0);
                if (pid < 0)
                        _exit(1);

                if (pid == 0)
                        _exit(0);

                _exit(wait_for_pid(pid) != 0);
        }

        if (waitpid(pid, &status, 0) != pid)
                return false;

        if (!WIFEXITED(status))
                return false;

        return WEXITSTATUS(status) == 0;
}

FIXTURE(clone3_empty_mntns) {};

FIXTURE_SETUP(clone3_empty_mntns)
{
        if (!clone3_empty_mntns_supported())
                SKIP(return, "CLONE_EMPTY_MNTNS via clone3 not supported");
}

FIXTURE_TEARDOWN(clone3_empty_mntns) {}

/*
 * Basic clone3() with CLONE_EMPTY_MNTNS: child gets empty mount namespace
 * with exactly 1 mount and root == cwd.
 */
TEST_F(clone3_empty_mntns, basic)
{
        pid_t pid, inner;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                if (enter_userns())
                        _exit(1);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        uint64_t root_id, cwd_id;

                        if (count_mounts() != 1)
                                _exit(3);

                        root_id = get_unique_mnt_id("/");
                        cwd_id = get_unique_mnt_id(".");
                        if (root_id == 0 || cwd_id == 0)
                                _exit(4);

                        if (root_id != cwd_id)
                                _exit(5);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * CLONE_EMPTY_MNTNS implies CLONE_NEWNS.  Verify that it works without
 * explicitly setting CLONE_NEWNS (tests fork.c:2627-2630).
 */
TEST_F(clone3_empty_mntns, implies_newns)
{
        pid_t pid, inner;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                ssize_t parent_mounts;

                if (enter_userns())
                        _exit(1);

                /* Verify we have mounts in our current namespace. */
                parent_mounts = count_mounts();
                if (parent_mounts < 1)
                        _exit(2);

                /* Only CLONE_EMPTY_MNTNS, no explicit CLONE_NEWNS. */
                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(3);

                if (inner == 0) {
                        if (count_mounts() != 1)
                                _exit(4);

                        _exit(0);
                }

                /* Parent still has its mounts. */
                if (count_mounts() != parent_mounts)
                        _exit(5);

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Helper macro: generate a test that clones with CLONE_EMPTY_MNTNS |
 * @extra_flags and verifies the child has exactly one mount.
 */
#define TEST_CLONE3_FLAGS(test_name, extra_flags)                       \
TEST_F(clone3_empty_mntns, test_name)                                   \
{                                                                       \
        pid_t pid, inner;                                               \
                                                                        \
        pid = fork();                                                   \
        ASSERT_GE(pid, 0);                                              \
                                                                        \
        if (pid == 0) {                                                 \
                if (enter_userns())                                     \
                        _exit(1);                                       \
                                                                        \
                inner = clone3_empty_mntns(extra_flags);                \
                if (inner < 0)                                          \
                        _exit(2);                                       \
                                                                        \
                if (inner == 0) {                                       \
                        if (count_mounts() != 1)                        \
                                _exit(3);                               \
                        _exit(0);                                       \
                }                                                       \
                                                                        \
                _exit(wait_for_pid(inner));                             \
        }                                                               \
                                                                        \
        ASSERT_EQ(wait_for_pid(pid), 0);                                \
}

/* Redundant CLONE_NEWNS | CLONE_EMPTY_MNTNS should succeed. */
TEST_CLONE3_FLAGS(with_explicit_newns, CLONE_NEWNS)

/* CLONE_EMPTY_MNTNS combined with CLONE_NEWUSER. */
TEST_CLONE3_FLAGS(with_newuser, CLONE_NEWUSER)

/* CLONE_EMPTY_MNTNS combined with other namespace flags. */
TEST_CLONE3_FLAGS(with_other_ns_flags, CLONE_NEWUTS | CLONE_NEWIPC)

/*
 * CLONE_EMPTY_MNTNS combined with CLONE_NEWPID.
 */
TEST_F(clone3_empty_mntns, with_newpid)
{
        pid_t pid, inner;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                if (enter_userns())
                        _exit(1);

                inner = clone3_empty_mntns(CLONE_NEWPID);
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        if (count_mounts() != 1)
                                _exit(3);

                        /* In a new PID namespace, getpid() returns 1. */
                        if (getpid() != 1)
                                _exit(4);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * CLONE_EMPTY_MNTNS | CLONE_FS must fail because the implied CLONE_NEWNS
 * and CLONE_FS are mutually exclusive (fork.c:1981).
 */
TEST_F(clone3_empty_mntns, with_clone_fs_fails)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                struct __clone_args args = {
                        .flags          = CLONE_EMPTY_MNTNS | CLONE_FS,
                        .exit_signal    = SIGCHLD,
                };
                pid_t ret;

                if (enter_userns())
                        _exit(1);

                ret = sys_clone3(&args, sizeof(args));
                if (ret >= 0) {
                        if (ret == 0)
                                _exit(0);
                        wait_for_pid(ret);
                        _exit(2);
                }

                if (errno != EINVAL)
                        _exit(3);

                _exit(0);
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * CLONE_EMPTY_MNTNS combined with CLONE_PIDFD returns a valid pidfd.
 */
TEST_F(clone3_empty_mntns, with_pidfd)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                struct __clone_args args = {
                        .flags          = CLONE_EMPTY_MNTNS | CLONE_PIDFD,
                        .exit_signal    = SIGCHLD,
                };
                int pidfd = -1;
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                args.pidfd = (uintptr_t)&pidfd;

                inner = sys_clone3(&args, sizeof(args));
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        if (count_mounts() != 1)
                                _exit(3);

                        _exit(0);
                }

                /* Verify we got a valid pidfd. */
                if (pidfd < 0)
                        _exit(4);

                close(pidfd);
                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * clone3 without CAP_SYS_ADMIN must fail with EPERM.
 */
TEST_F(clone3_empty_mntns, eperm_without_caps)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                pid_t ret;

                /* Skip if already root. */
                if (getuid() == 0)
                        _exit(0);

                ret = clone3_empty_mntns(0);
                if (ret >= 0) {
                        if (ret == 0)
                                _exit(0);
                        wait_for_pid(ret);
                        _exit(1);
                }

                if (errno != EPERM)
                        _exit(2);

                _exit(0);
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Parent's mount namespace is unaffected after clone3 with CLONE_EMPTY_MNTNS.
 */
TEST_F(clone3_empty_mntns, parent_unchanged)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                ssize_t nr_before, nr_after;
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                nr_before = count_mounts();
                if (nr_before < 1)
                        _exit(2);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(3);

                if (inner == 0)
                        _exit(0);

                if (wait_for_pid(inner) != 0)
                        _exit(4);

                nr_after = count_mounts();
                if (nr_after != nr_before)
                        _exit(5);

                _exit(0);
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Parent with many mounts: child still gets exactly 1 mount.
 */
TEST_F(clone3_empty_mntns, many_parent_mounts)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                char tmpdir[] = "/tmp/clone3_mntns_test.XXXXXX";
                pid_t inner;
                int i;

                if (enter_userns())
                        _exit(1);

                if (unshare(CLONE_NEWNS))
                        _exit(2);

                if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
                        _exit(3);

                if (!mkdtemp(tmpdir))
                        _exit(4);

                if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=1M"))
                        _exit(5);

                for (i = 0; i < 5; i++) {
                        char subdir[256];

                        snprintf(subdir, sizeof(subdir), "%s/sub%d", tmpdir, i);
                        if (mkdir(subdir, 0755) && errno != EEXIST)
                                _exit(6);
                        if (mount(subdir, subdir, NULL, MS_BIND, NULL))
                                _exit(7);
                }

                if (count_mounts() < 5)
                        _exit(8);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(9);

                if (inner == 0) {
                        if (count_mounts() != 1)
                                _exit(10);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Verify the child's root mount is nullfs with expected statmount properties.
 */
TEST_F(clone3_empty_mntns, mount_properties)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        struct statmount *sm;
                        uint64_t root_id;

                        root_id = get_unique_mnt_id("/");
                        if (!root_id)
                                _exit(3);

                        sm = statmount_alloc(root_id, 0,
                                             STATMOUNT_MNT_BASIC |
                                             STATMOUNT_MNT_POINT |
                                             STATMOUNT_FS_TYPE, 0);
                        if (!sm)
                                _exit(4);

                        /* Root mount point is "/". */
                        if (!(sm->mask & STATMOUNT_MNT_POINT))
                                _exit(5);
                        if (strcmp(sm->str + sm->mnt_point, "/") != 0)
                                _exit(6);

                        /* Filesystem type is nullfs. */
                        if (!(sm->mask & STATMOUNT_FS_TYPE))
                                _exit(7);
                        if (strcmp(sm->str + sm->fs_type, "nullfs") != 0)
                                _exit(8);

                        /* Root mount is its own parent. */
                        if (!(sm->mask & STATMOUNT_MNT_BASIC))
                                _exit(9);
                        if (sm->mnt_parent_id != sm->mnt_id)
                                _exit(10);

                        free(sm);
                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Listmount returns only the root mount in the child's empty namespace.
 */
TEST_F(clone3_empty_mntns, listmount_single_entry)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        uint64_t list[16];
                        ssize_t nr_mounts;
                        uint64_t root_id;

                        nr_mounts = listmount(LSMT_ROOT, 0, 0, list, 16, 0);
                        if (nr_mounts != 1)
                                _exit(3);

                        root_id = get_unique_mnt_id("/");
                        if (!root_id)
                                _exit(4);

                        if (list[0] != root_id)
                                _exit(5);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Child can mount tmpfs over nullfs root (the primary container use case).
 *
 * Uses the new mount API (fsopen/fsmount/move_mount) because resolving
 * "/" returns the process root directly without following overmounts.
 * The mount fd from fsmount lets us fchdir + chroot into the new tmpfs.
 */
TEST_F(clone3_empty_mntns, child_overmount_tmpfs)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(2);

                if (inner == 0) {
                        struct statmount *sm;
                        uint64_t root_id;
                        int fd, fsfd, mntfd;

                        if (count_mounts() != 1)
                                _exit(3);

                        /* Verify root is nullfs. */
                        root_id = get_unique_mnt_id("/");
                        if (!root_id)
                                _exit(4);

                        sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
                        if (!sm)
                                _exit(5);
                        if (!(sm->mask & STATMOUNT_FS_TYPE))
                                _exit(6);
                        if (strcmp(sm->str + sm->fs_type, "nullfs") != 0)
                                _exit(7);
                        free(sm);

                        /* Create tmpfs via the new mount API. */
                        fsfd = sys_fsopen("tmpfs", 0);
                        if (fsfd < 0)
                                _exit(8);

                        if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING,
                                         "size", "1M", 0)) {
                                close(fsfd);
                                _exit(9);
                        }

                        if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE,
                                         NULL, NULL, 0)) {
                                close(fsfd);
                                _exit(10);
                        }

                        mntfd = sys_fsmount(fsfd, 0, 0);
                        close(fsfd);
                        if (mntfd < 0)
                                _exit(11);

                        /* Attach tmpfs to "/". */
                        if (sys_move_mount(mntfd, "", AT_FDCWD, "/",
                                           MOVE_MOUNT_F_EMPTY_PATH)) {
                                close(mntfd);
                                _exit(12);
                        }

                        if (count_mounts() != 2) {
                                close(mntfd);
                                _exit(13);
                        }

                        /* Enter the tmpfs. */
                        if (fchdir(mntfd)) {
                                close(mntfd);
                                _exit(14);
                        }

                        if (chroot(".")) {
                                close(mntfd);
                                _exit(15);
                        }

                        close(mntfd);

                        /* Verify "/" is now tmpfs. */
                        root_id = get_unique_mnt_id("/");
                        if (!root_id)
                                _exit(16);

                        sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
                        if (!sm)
                                _exit(17);
                        if (!(sm->mask & STATMOUNT_FS_TYPE))
                                _exit(18);
                        if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0)
                                _exit(19);
                        free(sm);

                        /* Verify tmpfs is writable. */
                        fd = open("/testfile", O_CREAT | O_RDWR, 0644);
                        if (fd < 0)
                                _exit(20);

                        if (write(fd, "test", 4) != 4) {
                                close(fd);
                                _exit(21);
                        }
                        close(fd);

                        if (access("/testfile", F_OK))
                                _exit(22);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Multiple clone3 calls with CLONE_EMPTY_MNTNS produce children with
 * distinct mount namespace root mount IDs.
 */
TEST_F(clone3_empty_mntns, repeated)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                int pipe1[2], pipe2[2];
                uint64_t id1 = 0, id2 = 0;
                pid_t inner1, inner2;

                if (enter_userns())
                        _exit(1);

                if (pipe(pipe1) || pipe(pipe2))
                        _exit(2);

                inner1 = clone3_empty_mntns(0);
                if (inner1 < 0)
                        _exit(3);

                if (inner1 == 0) {
                        uint64_t root_id;

                        close(pipe1[0]);
                        root_id = get_unique_mnt_id("/");
                        if (write(pipe1[1], &root_id, sizeof(root_id)) != sizeof(root_id))
                                _exit(1);
                        close(pipe1[1]);
                        _exit(0);
                }

                inner2 = clone3_empty_mntns(0);
                if (inner2 < 0)
                        _exit(4);

                if (inner2 == 0) {
                        uint64_t root_id;

                        close(pipe2[0]);
                        root_id = get_unique_mnt_id("/");
                        if (write(pipe2[1], &root_id, sizeof(root_id)) != sizeof(root_id))
                                _exit(1);
                        close(pipe2[1]);
                        _exit(0);
                }

                close(pipe1[1]);
                close(pipe2[1]);

                if (read(pipe1[0], &id1, sizeof(id1)) != sizeof(id1))
                        _exit(5);
                if (read(pipe2[0], &id2, sizeof(id2)) != sizeof(id2))
                        _exit(6);

                close(pipe1[0]);
                close(pipe2[0]);

                if (wait_for_pid(inner1) || wait_for_pid(inner2))
                        _exit(7);

                /* Each child must have a distinct root mount ID. */
                if (id1 == 0 || id2 == 0)
                        _exit(8);
                if (id1 == id2)
                        _exit(9);

                _exit(0);
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Verify setns() into a child's empty mount namespace works.
 */
TEST_F(clone3_empty_mntns, setns_into_child_mntns)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                int pipe_fd[2];
                pid_t inner;
                char c;

                if (enter_userns())
                        _exit(1);

                if (pipe(pipe_fd))
                        _exit(2);

                inner = clone3_empty_mntns(0);
                if (inner < 0)
                        _exit(3);

                if (inner == 0) {
                        /* Signal parent we're ready. */
                        close(pipe_fd[0]);
                        if (write(pipe_fd[1], "r", 1) != 1)
                                _exit(1);

                        /*
                         * Wait for parent to finish.  Reading from our
                         * write end will block until the parent closes
                         * its read end, giving us an implicit barrier.
                         */
                        if (read(pipe_fd[1], &c, 1) < 0)
                                ;
                        close(pipe_fd[1]);
                        _exit(0);
                }

                close(pipe_fd[1]);

                /* Wait for child to be ready. */
                if (read(pipe_fd[0], &c, 1) != 1)
                        _exit(4);

                /* Open child's mount namespace. */
                {
                        char path[64];
                        int mntns_fd;

                        snprintf(path, sizeof(path), "/proc/%d/ns/mnt", inner);
                        mntns_fd = open(path, O_RDONLY);
                        if (mntns_fd < 0)
                                _exit(5);

                        if (setns(mntns_fd, CLONE_NEWNS))
                                _exit(6);

                        close(mntns_fd);
                }

                /* Now we should be in the child's empty mntns. */
                if (count_mounts() != 1)
                        _exit(7);

                close(pipe_fd[0]);
                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Tests below do not require CLONE_EMPTY_MNTNS support.
 */

/*
 * Unknown 64-bit flags beyond the known set are rejected.
 */
TEST(unknown_flags_rejected)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                struct __clone_args args = {
                        .flags          = 0x800000000ULL,
                        .exit_signal    = SIGCHLD,
                };
                pid_t ret;

                ret = sys_clone3(&args, sizeof(args));
                if (ret >= 0) {
                        if (ret == 0)
                                _exit(0);
                        wait_for_pid(ret);
                        _exit(1);
                }

                if (errno != EINVAL)
                        _exit(2);

                _exit(0);
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

/*
 * Regular clone3 with CLONE_NEWNS (without CLONE_EMPTY_MNTNS) still
 * copies the full mount tree.
 */
TEST(clone3_newns_full_copy)
{
        pid_t pid;

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                struct __clone_args args = {
                        .flags          = CLONE_NEWNS,
                        .exit_signal    = SIGCHLD,
                };
                ssize_t parent_mounts;
                pid_t inner;

                if (enter_userns())
                        _exit(1);

                parent_mounts = count_mounts();
                if (parent_mounts < 1)
                        _exit(2);

                inner = sys_clone3(&args, sizeof(args));
                if (inner < 0)
                        _exit(3);

                if (inner == 0) {
                        /* Full copy should have at least as many mounts. */
                        if (count_mounts() < parent_mounts)
                                _exit(1);

                        _exit(0);
                }

                _exit(wait_for_pid(inner));
        }

        ASSERT_EQ(wait_for_pid(pid), 0);
}

TEST_HARNESS_MAIN