mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
The timens_separate and pidns_separate test cases fork a grandchild that calls pause(). FIXTURE_TEARDOWN only kills the direct child, which is the init process of the grandchild's namespace. Once the child (init) exits, the grandchild is reparented to the host init but remains alive and continues to hold the inherited write end of the test runner's TAP pipe open. tap_prefix never receives EOF and blocks indefinitely, hanging the entire test collection. Record the grandchild PID in the fixture struct so that teardown can send SIGKILL and reap it before dealing with the child. The grandchild must be reaped first because the child acts as its PID namespace init; killing the child first would kill the grandchild without giving us a chance to waitpid() it. Signed-off-by: Ricardo B. Marlière <rbm@suse.com> Link: https://patch.msgid.link/20260407-selftests-namespaces_fixes-v1-1-59109909d88b@suse.com Signed-off-by: Christian Brauner <brauner@kernel.org>
994 lines
22 KiB
C
994 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <libgen.h>
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <sys/mount.h>
|
|
#include <poll.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/nsfs.h>
|
|
#include "kselftest_harness.h"
|
|
|
|
/* Fixture for tests that create child processes */
|
|
FIXTURE(nsid) {
|
|
pid_t child_pid;
|
|
pid_t grandchild_pid;
|
|
};
|
|
|
|
FIXTURE_SETUP(nsid) {
|
|
self->child_pid = 0;
|
|
self->grandchild_pid = 0;
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(nsid) {
|
|
/*
|
|
* Kill grandchild first: timens_separate and pidns_separate fork a
|
|
* grandchild that calls pause(). It is reparented to init on child
|
|
* exit and keeps the test runner's tap pipe open, hanging the runner.
|
|
*/
|
|
if (self->grandchild_pid > 0) {
|
|
kill(self->grandchild_pid, SIGKILL);
|
|
waitpid(self->grandchild_pid, NULL, 0);
|
|
}
|
|
if (self->child_pid > 0) {
|
|
kill(self->child_pid, SIGKILL);
|
|
waitpid(self->child_pid, NULL, 0);
|
|
}
|
|
}
|
|
|
|
TEST(nsid_mntns_basic)
|
|
{
|
|
__u64 mnt_ns_id = 0;
|
|
int fd_mntns;
|
|
int ret;
|
|
|
|
/* Open the current mount namespace */
|
|
fd_mntns = open("/proc/self/ns/mnt", O_RDONLY);
|
|
ASSERT_GE(fd_mntns, 0);
|
|
|
|
/* Get the mount namespace ID */
|
|
ret = ioctl(fd_mntns, NS_GET_MNTNS_ID, &mnt_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(mnt_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 mnt_ns_id2 = 0;
|
|
ret = ioctl(fd_mntns, NS_GET_ID, &mnt_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(mnt_ns_id, mnt_ns_id2);
|
|
|
|
close(fd_mntns);
|
|
}
|
|
|
|
TEST_F(nsid, mntns_separate)
|
|
{
|
|
__u64 parent_mnt_ns_id = 0;
|
|
__u64 child_mnt_ns_id = 0;
|
|
int fd_parent_mntns, fd_child_mntns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's mount namespace ID */
|
|
fd_parent_mntns = open("/proc/self/ns/mnt", O_RDONLY);
|
|
ASSERT_GE(fd_parent_mntns, 0);
|
|
ret = ioctl(fd_parent_mntns, NS_GET_ID, &parent_mnt_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_mnt_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new mount namespace */
|
|
ret = unshare(CLONE_NEWNS);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_mntns);
|
|
SKIP(return, "No permission to create mount namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's mount namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
|
|
fd_child_mntns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_mntns, 0);
|
|
|
|
/* Get child's mount namespace ID */
|
|
ret = ioctl(fd_child_mntns, NS_GET_ID, &child_mnt_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_mnt_ns_id, 0);
|
|
|
|
/* Parent and child should have different mount namespace IDs */
|
|
ASSERT_NE(parent_mnt_ns_id, child_mnt_ns_id);
|
|
|
|
close(fd_parent_mntns);
|
|
close(fd_child_mntns);
|
|
}
|
|
|
|
TEST(nsid_cgroupns_basic)
|
|
{
|
|
__u64 cgroup_ns_id = 0;
|
|
int fd_cgroupns;
|
|
int ret;
|
|
|
|
/* Open the current cgroup namespace */
|
|
fd_cgroupns = open("/proc/self/ns/cgroup", O_RDONLY);
|
|
ASSERT_GE(fd_cgroupns, 0);
|
|
|
|
/* Get the cgroup namespace ID */
|
|
ret = ioctl(fd_cgroupns, NS_GET_ID, &cgroup_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(cgroup_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 cgroup_ns_id2 = 0;
|
|
ret = ioctl(fd_cgroupns, NS_GET_ID, &cgroup_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(cgroup_ns_id, cgroup_ns_id2);
|
|
|
|
close(fd_cgroupns);
|
|
}
|
|
|
|
TEST_F(nsid, cgroupns_separate)
|
|
{
|
|
__u64 parent_cgroup_ns_id = 0;
|
|
__u64 child_cgroup_ns_id = 0;
|
|
int fd_parent_cgroupns, fd_child_cgroupns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's cgroup namespace ID */
|
|
fd_parent_cgroupns = open("/proc/self/ns/cgroup", O_RDONLY);
|
|
ASSERT_GE(fd_parent_cgroupns, 0);
|
|
ret = ioctl(fd_parent_cgroupns, NS_GET_ID, &parent_cgroup_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_cgroup_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new cgroup namespace */
|
|
ret = unshare(CLONE_NEWCGROUP);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_cgroupns);
|
|
SKIP(return, "No permission to create cgroup namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's cgroup namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/cgroup", pid);
|
|
fd_child_cgroupns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_cgroupns, 0);
|
|
|
|
/* Get child's cgroup namespace ID */
|
|
ret = ioctl(fd_child_cgroupns, NS_GET_ID, &child_cgroup_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_cgroup_ns_id, 0);
|
|
|
|
/* Parent and child should have different cgroup namespace IDs */
|
|
ASSERT_NE(parent_cgroup_ns_id, child_cgroup_ns_id);
|
|
|
|
close(fd_parent_cgroupns);
|
|
close(fd_child_cgroupns);
|
|
}
|
|
|
|
TEST(nsid_ipcns_basic)
|
|
{
|
|
__u64 ipc_ns_id = 0;
|
|
int fd_ipcns;
|
|
int ret;
|
|
|
|
/* Open the current IPC namespace */
|
|
fd_ipcns = open("/proc/self/ns/ipc", O_RDONLY);
|
|
ASSERT_GE(fd_ipcns, 0);
|
|
|
|
/* Get the IPC namespace ID */
|
|
ret = ioctl(fd_ipcns, NS_GET_ID, &ipc_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(ipc_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 ipc_ns_id2 = 0;
|
|
ret = ioctl(fd_ipcns, NS_GET_ID, &ipc_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(ipc_ns_id, ipc_ns_id2);
|
|
|
|
close(fd_ipcns);
|
|
}
|
|
|
|
TEST_F(nsid, ipcns_separate)
|
|
{
|
|
__u64 parent_ipc_ns_id = 0;
|
|
__u64 child_ipc_ns_id = 0;
|
|
int fd_parent_ipcns, fd_child_ipcns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's IPC namespace ID */
|
|
fd_parent_ipcns = open("/proc/self/ns/ipc", O_RDONLY);
|
|
ASSERT_GE(fd_parent_ipcns, 0);
|
|
ret = ioctl(fd_parent_ipcns, NS_GET_ID, &parent_ipc_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_ipc_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new IPC namespace */
|
|
ret = unshare(CLONE_NEWIPC);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_ipcns);
|
|
SKIP(return, "No permission to create IPC namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's IPC namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/ipc", pid);
|
|
fd_child_ipcns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_ipcns, 0);
|
|
|
|
/* Get child's IPC namespace ID */
|
|
ret = ioctl(fd_child_ipcns, NS_GET_ID, &child_ipc_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_ipc_ns_id, 0);
|
|
|
|
/* Parent and child should have different IPC namespace IDs */
|
|
ASSERT_NE(parent_ipc_ns_id, child_ipc_ns_id);
|
|
|
|
close(fd_parent_ipcns);
|
|
close(fd_child_ipcns);
|
|
}
|
|
|
|
TEST(nsid_utsns_basic)
|
|
{
|
|
__u64 uts_ns_id = 0;
|
|
int fd_utsns;
|
|
int ret;
|
|
|
|
/* Open the current UTS namespace */
|
|
fd_utsns = open("/proc/self/ns/uts", O_RDONLY);
|
|
ASSERT_GE(fd_utsns, 0);
|
|
|
|
/* Get the UTS namespace ID */
|
|
ret = ioctl(fd_utsns, NS_GET_ID, &uts_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(uts_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 uts_ns_id2 = 0;
|
|
ret = ioctl(fd_utsns, NS_GET_ID, &uts_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(uts_ns_id, uts_ns_id2);
|
|
|
|
close(fd_utsns);
|
|
}
|
|
|
|
TEST_F(nsid, utsns_separate)
|
|
{
|
|
__u64 parent_uts_ns_id = 0;
|
|
__u64 child_uts_ns_id = 0;
|
|
int fd_parent_utsns, fd_child_utsns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's UTS namespace ID */
|
|
fd_parent_utsns = open("/proc/self/ns/uts", O_RDONLY);
|
|
ASSERT_GE(fd_parent_utsns, 0);
|
|
ret = ioctl(fd_parent_utsns, NS_GET_ID, &parent_uts_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_uts_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new UTS namespace */
|
|
ret = unshare(CLONE_NEWUTS);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_utsns);
|
|
SKIP(return, "No permission to create UTS namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's UTS namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/uts", pid);
|
|
fd_child_utsns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_utsns, 0);
|
|
|
|
/* Get child's UTS namespace ID */
|
|
ret = ioctl(fd_child_utsns, NS_GET_ID, &child_uts_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_uts_ns_id, 0);
|
|
|
|
/* Parent and child should have different UTS namespace IDs */
|
|
ASSERT_NE(parent_uts_ns_id, child_uts_ns_id);
|
|
|
|
close(fd_parent_utsns);
|
|
close(fd_child_utsns);
|
|
}
|
|
|
|
TEST(nsid_userns_basic)
|
|
{
|
|
__u64 user_ns_id = 0;
|
|
int fd_userns;
|
|
int ret;
|
|
|
|
/* Open the current user namespace */
|
|
fd_userns = open("/proc/self/ns/user", O_RDONLY);
|
|
ASSERT_GE(fd_userns, 0);
|
|
|
|
/* Get the user namespace ID */
|
|
ret = ioctl(fd_userns, NS_GET_ID, &user_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(user_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 user_ns_id2 = 0;
|
|
ret = ioctl(fd_userns, NS_GET_ID, &user_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(user_ns_id, user_ns_id2);
|
|
|
|
close(fd_userns);
|
|
}
|
|
|
|
TEST_F(nsid, userns_separate)
|
|
{
|
|
__u64 parent_user_ns_id = 0;
|
|
__u64 child_user_ns_id = 0;
|
|
int fd_parent_userns, fd_child_userns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's user namespace ID */
|
|
fd_parent_userns = open("/proc/self/ns/user", O_RDONLY);
|
|
ASSERT_GE(fd_parent_userns, 0);
|
|
ret = ioctl(fd_parent_userns, NS_GET_ID, &parent_user_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_user_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new user namespace */
|
|
ret = unshare(CLONE_NEWUSER);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_userns);
|
|
SKIP(return, "No permission to create user namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's user namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
|
|
fd_child_userns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_userns, 0);
|
|
|
|
/* Get child's user namespace ID */
|
|
ret = ioctl(fd_child_userns, NS_GET_ID, &child_user_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_user_ns_id, 0);
|
|
|
|
/* Parent and child should have different user namespace IDs */
|
|
ASSERT_NE(parent_user_ns_id, child_user_ns_id);
|
|
|
|
close(fd_parent_userns);
|
|
close(fd_child_userns);
|
|
}
|
|
|
|
TEST(nsid_timens_basic)
|
|
{
|
|
__u64 time_ns_id = 0;
|
|
int fd_timens;
|
|
int ret;
|
|
|
|
/* Open the current time namespace */
|
|
fd_timens = open("/proc/self/ns/time", O_RDONLY);
|
|
if (fd_timens < 0) {
|
|
SKIP(return, "Time namespaces not supported");
|
|
}
|
|
|
|
/* Get the time namespace ID */
|
|
ret = ioctl(fd_timens, NS_GET_ID, &time_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(time_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 time_ns_id2 = 0;
|
|
ret = ioctl(fd_timens, NS_GET_ID, &time_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(time_ns_id, time_ns_id2);
|
|
|
|
close(fd_timens);
|
|
}
|
|
|
|
TEST_F(nsid, timens_separate)
|
|
{
|
|
__u64 parent_time_ns_id = 0;
|
|
__u64 child_time_ns_id = 0;
|
|
int fd_parent_timens, fd_child_timens;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Open the current time namespace */
|
|
fd_parent_timens = open("/proc/self/ns/time", O_RDONLY);
|
|
if (fd_parent_timens < 0) {
|
|
SKIP(return, "Time namespaces not supported");
|
|
}
|
|
|
|
/* Get parent's time namespace ID */
|
|
ret = ioctl(fd_parent_timens, NS_GET_ID, &parent_time_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_time_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new time namespace */
|
|
ret = unshare(CLONE_NEWTIME);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES || errno == EINVAL) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Fork a grandchild to actually enter the new namespace */
|
|
pid_t grandchild = fork();
|
|
if (grandchild == 0) {
|
|
/* Grandchild is in the new namespace */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
pause();
|
|
_exit(0);
|
|
} else if (grandchild > 0) {
|
|
/* Child writes grandchild PID and waits */
|
|
write(pipefd[1], "Y", 1);
|
|
write(pipefd[1], &grandchild, sizeof(grandchild));
|
|
close(pipefd[1]);
|
|
pause(); /* Keep the parent alive to maintain the grandchild */
|
|
_exit(0);
|
|
} else {
|
|
_exit(1);
|
|
}
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_timens);
|
|
close(pipefd[0]);
|
|
SKIP(return, "Cannot create time namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
pid_t grandchild_pid;
|
|
ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid));
|
|
self->grandchild_pid = grandchild_pid;
|
|
close(pipefd[0]);
|
|
|
|
/* Open grandchild's time namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/time", grandchild_pid);
|
|
fd_child_timens = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_timens, 0);
|
|
|
|
/* Get child's time namespace ID */
|
|
ret = ioctl(fd_child_timens, NS_GET_ID, &child_time_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_time_ns_id, 0);
|
|
|
|
/* Parent and child should have different time namespace IDs */
|
|
ASSERT_NE(parent_time_ns_id, child_time_ns_id);
|
|
|
|
close(fd_parent_timens);
|
|
close(fd_child_timens);
|
|
}
|
|
|
|
TEST(nsid_pidns_basic)
|
|
{
|
|
__u64 pid_ns_id = 0;
|
|
int fd_pidns;
|
|
int ret;
|
|
|
|
/* Open the current PID namespace */
|
|
fd_pidns = open("/proc/self/ns/pid", O_RDONLY);
|
|
ASSERT_GE(fd_pidns, 0);
|
|
|
|
/* Get the PID namespace ID */
|
|
ret = ioctl(fd_pidns, NS_GET_ID, &pid_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(pid_ns_id, 0);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 pid_ns_id2 = 0;
|
|
ret = ioctl(fd_pidns, NS_GET_ID, &pid_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(pid_ns_id, pid_ns_id2);
|
|
|
|
close(fd_pidns);
|
|
}
|
|
|
|
TEST_F(nsid, pidns_separate)
|
|
{
|
|
__u64 parent_pid_ns_id = 0;
|
|
__u64 child_pid_ns_id = 0;
|
|
int fd_parent_pidns, fd_child_pidns;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's PID namespace ID */
|
|
fd_parent_pidns = open("/proc/self/ns/pid", O_RDONLY);
|
|
ASSERT_GE(fd_parent_pidns, 0);
|
|
ret = ioctl(fd_parent_pidns, NS_GET_ID, &parent_pid_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_pid_ns_id, 0);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new PID namespace */
|
|
ret = unshare(CLONE_NEWPID);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Fork a grandchild to actually enter the new namespace */
|
|
pid_t grandchild = fork();
|
|
if (grandchild == 0) {
|
|
/* Grandchild is in the new namespace */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
pause();
|
|
_exit(0);
|
|
} else if (grandchild > 0) {
|
|
/* Child writes grandchild PID and waits */
|
|
write(pipefd[1], "Y", 1);
|
|
write(pipefd[1], &grandchild, sizeof(grandchild));
|
|
close(pipefd[1]);
|
|
pause(); /* Keep the parent alive to maintain the grandchild */
|
|
_exit(0);
|
|
} else {
|
|
_exit(1);
|
|
}
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_pidns);
|
|
close(pipefd[0]);
|
|
SKIP(return, "No permission to create PID namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
pid_t grandchild_pid;
|
|
ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid));
|
|
self->grandchild_pid = grandchild_pid;
|
|
close(pipefd[0]);
|
|
|
|
/* Open grandchild's PID namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/pid", grandchild_pid);
|
|
fd_child_pidns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_pidns, 0);
|
|
|
|
/* Get child's PID namespace ID */
|
|
ret = ioctl(fd_child_pidns, NS_GET_ID, &child_pid_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_pid_ns_id, 0);
|
|
|
|
/* Parent and child should have different PID namespace IDs */
|
|
ASSERT_NE(parent_pid_ns_id, child_pid_ns_id);
|
|
|
|
close(fd_parent_pidns);
|
|
close(fd_child_pidns);
|
|
}
|
|
|
|
TEST(nsid_netns_basic)
|
|
{
|
|
__u64 net_ns_id = 0;
|
|
__u64 netns_cookie = 0;
|
|
int fd_netns;
|
|
int sock;
|
|
socklen_t optlen;
|
|
int ret;
|
|
|
|
/* Open the current network namespace */
|
|
fd_netns = open("/proc/self/ns/net", O_RDONLY);
|
|
ASSERT_GE(fd_netns, 0);
|
|
|
|
/* Get the network namespace ID via ioctl */
|
|
ret = ioctl(fd_netns, NS_GET_ID, &net_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(net_ns_id, 0);
|
|
|
|
/* Create a socket to get the SO_NETNS_COOKIE */
|
|
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
ASSERT_GE(sock, 0);
|
|
|
|
/* Get the network namespace cookie via socket option */
|
|
optlen = sizeof(netns_cookie);
|
|
ret = getsockopt(sock, SOL_SOCKET, SO_NETNS_COOKIE, &netns_cookie, &optlen);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(optlen, sizeof(netns_cookie));
|
|
|
|
/* The namespace ID and cookie should be identical */
|
|
ASSERT_EQ(net_ns_id, netns_cookie);
|
|
|
|
/* Verify we can get the same ID again */
|
|
__u64 net_ns_id2 = 0;
|
|
ret = ioctl(fd_netns, NS_GET_ID, &net_ns_id2);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_EQ(net_ns_id, net_ns_id2);
|
|
|
|
close(sock);
|
|
close(fd_netns);
|
|
}
|
|
|
|
TEST_F(nsid, netns_separate)
|
|
{
|
|
__u64 parent_net_ns_id = 0;
|
|
__u64 parent_netns_cookie = 0;
|
|
__u64 child_net_ns_id = 0;
|
|
__u64 child_netns_cookie = 0;
|
|
int fd_parent_netns, fd_child_netns;
|
|
int parent_sock, child_sock;
|
|
socklen_t optlen;
|
|
int ret;
|
|
pid_t pid;
|
|
int pipefd[2];
|
|
|
|
/* Get parent's network namespace ID */
|
|
fd_parent_netns = open("/proc/self/ns/net", O_RDONLY);
|
|
ASSERT_GE(fd_parent_netns, 0);
|
|
ret = ioctl(fd_parent_netns, NS_GET_ID, &parent_net_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(parent_net_ns_id, 0);
|
|
|
|
/* Get parent's network namespace cookie */
|
|
parent_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
ASSERT_GE(parent_sock, 0);
|
|
optlen = sizeof(parent_netns_cookie);
|
|
ret = getsockopt(parent_sock, SOL_SOCKET, SO_NETNS_COOKIE, &parent_netns_cookie, &optlen);
|
|
ASSERT_EQ(ret, 0);
|
|
|
|
/* Verify parent's ID and cookie match */
|
|
ASSERT_EQ(parent_net_ns_id, parent_netns_cookie);
|
|
|
|
/* Create a pipe for synchronization */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new network namespace */
|
|
ret = unshare(CLONE_NEWNET);
|
|
if (ret != 0) {
|
|
/* Skip test if we don't have permission */
|
|
if (errno == EPERM || errno == EACCES) {
|
|
write(pipefd[1], "S", 1); /* Signal skip */
|
|
_exit(0);
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
/* Signal success */
|
|
write(pipefd[1], "Y", 1);
|
|
close(pipefd[1]);
|
|
|
|
/* Keep namespace alive */
|
|
pause();
|
|
_exit(0);
|
|
}
|
|
|
|
/* Track child for cleanup */
|
|
self->child_pid = pid;
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
char buf;
|
|
ASSERT_EQ(read(pipefd[0], &buf, 1), 1);
|
|
close(pipefd[0]);
|
|
|
|
if (buf == 'S') {
|
|
/* Child couldn't create namespace, skip test */
|
|
close(fd_parent_netns);
|
|
close(parent_sock);
|
|
SKIP(return, "No permission to create network namespace");
|
|
}
|
|
|
|
ASSERT_EQ(buf, 'Y');
|
|
|
|
/* Open child's network namespace */
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/proc/%d/ns/net", pid);
|
|
fd_child_netns = open(path, O_RDONLY);
|
|
ASSERT_GE(fd_child_netns, 0);
|
|
|
|
/* Get child's network namespace ID */
|
|
ret = ioctl(fd_child_netns, NS_GET_ID, &child_net_ns_id);
|
|
ASSERT_EQ(ret, 0);
|
|
ASSERT_NE(child_net_ns_id, 0);
|
|
|
|
/* Create socket in child's namespace to get cookie */
|
|
ret = setns(fd_child_netns, CLONE_NEWNET);
|
|
if (ret == 0) {
|
|
child_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
ASSERT_GE(child_sock, 0);
|
|
|
|
optlen = sizeof(child_netns_cookie);
|
|
ret = getsockopt(child_sock, SOL_SOCKET, SO_NETNS_COOKIE, &child_netns_cookie, &optlen);
|
|
ASSERT_EQ(ret, 0);
|
|
|
|
/* Verify child's ID and cookie match */
|
|
ASSERT_EQ(child_net_ns_id, child_netns_cookie);
|
|
|
|
close(child_sock);
|
|
|
|
/* Return to parent namespace */
|
|
setns(fd_parent_netns, CLONE_NEWNET);
|
|
}
|
|
|
|
/* Parent and child should have different network namespace IDs */
|
|
ASSERT_NE(parent_net_ns_id, child_net_ns_id);
|
|
if (child_netns_cookie != 0) {
|
|
ASSERT_NE(parent_netns_cookie, child_netns_cookie);
|
|
}
|
|
|
|
close(fd_parent_netns);
|
|
close(fd_child_netns);
|
|
close(parent_sock);
|
|
}
|
|
|
|
TEST_HARNESS_MAIN
|