193 lines
4.8 KiB
C
193 lines
4.8 KiB
C
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
#define _GNU_SOURCE
|
|
#include <sched.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
|
|
#include "../test.h"
|
|
|
|
#define STACK_SIZE (1024 * 1024)
|
|
|
|
// --- Test for unshare(CLONE_NEWNS) ---
|
|
|
|
#define UNSHARE_MNT "/mnt/unshare_test"
|
|
#define UNSHARE_FILE UNSHARE_MNT "/child.txt"
|
|
|
|
static int unshare_child_fn(void)
|
|
{
|
|
CHECK(unshare(CLONE_NEWNS));
|
|
|
|
// Mount a tmpfs in the new namespace. This should not be visible to the parent.
|
|
CHECK(mount("ramfs_child", UNSHARE_MNT, "ramfs", 0, ""));
|
|
|
|
int fd = CHECK(open(UNSHARE_FILE, O_CREAT | O_WRONLY, 0644));
|
|
CHECK(close(fd));
|
|
CHECK(access(UNSHARE_FILE, F_OK));
|
|
|
|
CHECK(umount(UNSHARE_MNT));
|
|
|
|
CHECK_WITH(access(UNSHARE_FILE, F_OK), errno == ENOENT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
FN_TEST(unshare_newns)
|
|
{
|
|
// Setup
|
|
CHECK_WITH(mkdir("/mnt", 0755), errno == 0 | errno == EEXIST);
|
|
CHECK_WITH(mkdir(UNSHARE_MNT, 0755), errno == 0 | errno == EEXIST);
|
|
|
|
TEST_ERRNO(access(UNSHARE_FILE, F_OK), ENOENT);
|
|
|
|
pid_t pid = TEST_SUCC(fork());
|
|
|
|
if (pid == 0) {
|
|
exit(unshare_child_fn());
|
|
} else {
|
|
int status;
|
|
TEST_SUCC(waitpid(pid, &status, 0));
|
|
TEST_RES(WIFEXITED(status) && WEXITSTATUS(status), _ret == 0);
|
|
// Verify that the child's mount operations were not visible to the parent.
|
|
TEST_ERRNO(access(UNSHARE_FILE, F_OK), ENOENT);
|
|
}
|
|
|
|
// Teardown
|
|
TEST_SUCC(rmdir(UNSHARE_MNT));
|
|
}
|
|
END_TEST()
|
|
|
|
// --- Test for clone(CLONE_NEWNS) ---
|
|
|
|
#define CLONE_PARENT_MNT "/mnt/clone_parent"
|
|
#define CLONE_CHILD_MNT "/mnt/clone_child"
|
|
#define PARENT_FILE CLONE_PARENT_MNT "/parent.txt"
|
|
#define CHILD_FILE CLONE_CHILD_MNT "/child.txt"
|
|
|
|
static int clone_child_fn(void *arg)
|
|
{
|
|
CHECK(access(PARENT_FILE, F_OK));
|
|
|
|
CHECK(mount("ramfs_child", CLONE_CHILD_MNT, "ramfs", 0, ""));
|
|
int fd = CHECK(open(CHILD_FILE, O_CREAT | O_WRONLY, 0644));
|
|
CHECK(close(fd));
|
|
|
|
CHECK(umount(CLONE_PARENT_MNT));
|
|
|
|
// Verify parent's mount is gone from child's view.
|
|
CHECK_WITH(access(PARENT_FILE, F_OK), errno == ENOENT);
|
|
|
|
// Verify child's own mount is still present.
|
|
CHECK(access(CHILD_FILE, F_OK));
|
|
|
|
return 0;
|
|
}
|
|
|
|
FN_TEST(clone_newns)
|
|
{
|
|
// Setup
|
|
CHECK_WITH(mkdir("/mnt", 0755), errno == 0 | errno == EEXIST);
|
|
CHECK_WITH(mkdir(CLONE_PARENT_MNT, 0755), errno == 0 | errno == EEXIST);
|
|
CHECK_WITH(mkdir(CLONE_CHILD_MNT, 0755), errno == 0 | errno == EEXIST);
|
|
|
|
TEST_SUCC(mount("ramfs_parent", CLONE_PARENT_MNT, "ramfs", 0, ""));
|
|
int fd = TEST_SUCC(open(PARENT_FILE, O_CREAT | O_WRONLY, 0644));
|
|
TEST_SUCC(close(fd));
|
|
|
|
char *stack = malloc(STACK_SIZE);
|
|
char *stack_top = stack + STACK_SIZE;
|
|
|
|
pid_t pid = TEST_SUCC(
|
|
clone(clone_child_fn, stack_top, CLONE_NEWNS | SIGCHLD, NULL));
|
|
|
|
int status;
|
|
TEST_SUCC(waitpid(pid, &status, 0));
|
|
TEST_RES(WIFEXITED(status) && WEXITSTATUS(status), _ret == 0);
|
|
|
|
// Parent's mount should be unaffected by child's umount.
|
|
TEST_SUCC(access(PARENT_FILE, F_OK));
|
|
// Child's mount should not be visible to the parent.
|
|
TEST_ERRNO(access(CHILD_FILE, F_OK), ENOENT);
|
|
|
|
// Teardown
|
|
free(stack);
|
|
TEST_SUCC(umount(CLONE_PARENT_MNT));
|
|
TEST_SUCC(rmdir(CLONE_PARENT_MNT));
|
|
TEST_SUCC(rmdir(CLONE_CHILD_MNT));
|
|
}
|
|
END_TEST()
|
|
|
|
// --- Test for setns(CLONE_NEWNS) ---
|
|
|
|
#define SETNS_MNT "/mnt/setns_target"
|
|
#define SETNS_FILE SETNS_MNT "/target.txt"
|
|
|
|
// This function runs in a child process to set up a target namespace.
|
|
static int setns_target_fn(int pipe_write_fd)
|
|
{
|
|
CHECK(unshare(CLONE_NEWNS));
|
|
|
|
CHECK(mount("ramfs_target", SETNS_MNT, "ramfs", 0, ""));
|
|
|
|
int fd = CHECK(open(SETNS_FILE, O_CREAT | O_WRONLY, 0644));
|
|
CHECK(close(fd));
|
|
|
|
// Signal to the parent that setup is complete.
|
|
char ok = 'K';
|
|
CHECK(write(pipe_write_fd, &ok, 1));
|
|
CHECK(close(pipe_write_fd));
|
|
|
|
// Wait to be killed by the parent.
|
|
pause();
|
|
return 0;
|
|
}
|
|
|
|
FN_TEST(setns_newns)
|
|
{
|
|
// Setup
|
|
CHECK_WITH(mkdir("/mnt", 0755), errno == 0 | errno == EEXIST);
|
|
CHECK_WITH(mkdir(SETNS_MNT, 0755), errno == 0 | errno == EEXIST);
|
|
|
|
int pipefd[2];
|
|
TEST_SUCC(pipe(pipefd));
|
|
|
|
pid_t child_pid = TEST_SUCC(fork());
|
|
|
|
if (child_pid == 0) {
|
|
close(pipefd[0]);
|
|
exit(setns_target_fn(pipefd[1]));
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
char buf[10];
|
|
TEST_SUCC(read(pipefd[0], &buf, 1));
|
|
close(pipefd[0]);
|
|
|
|
int pid_fd = TEST_SUCC(syscall(SYS_pidfd_open, child_pid, 0));
|
|
|
|
// Switching into the child's UTS namespace will not change the CWD.
|
|
TEST_SUCC(chdir("/mnt"));
|
|
TEST_SUCC(setns(pid_fd, CLONE_NEWUTS));
|
|
TEST_RES(getcwd(buf, sizeof(buf)), strcmp(buf, "/mnt") == 0);
|
|
|
|
// Switching into the child's mount namespace will reset the CWD.
|
|
TEST_SUCC(setns(pid_fd, CLONE_NEWNS));
|
|
TEST_RES(getcwd(buf, sizeof(buf)), strcmp(buf, "/") == 0);
|
|
|
|
TEST_SUCC(close(pid_fd));
|
|
|
|
// Check if we can see the file created by the child in its namespace.
|
|
TEST_SUCC(access(SETNS_FILE, F_OK));
|
|
|
|
// Teardown
|
|
TEST_SUCC(kill(child_pid, SIGKILL));
|
|
TEST_SUCC(waitpid(child_pid, NULL, 0));
|
|
|
|
TEST_SUCC(umount(SETNS_MNT));
|
|
TEST_SUCC(rmdir(SETNS_MNT));
|
|
}
|
|
END_TEST()
|