Files
linux/tools/testing/selftests/namespaces/nsid_test.c
Linus Torvalds 509d3f4584 Merge tag 'mm-nonmm-stable-2025-12-06-11-14' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull non-MM updates from Andrew Morton:

 - "panic: sys_info: Refactor and fix a potential issue" (Andy Shevchenko)
   fixes a build issue and does some cleanup in ib/sys_info.c

 - "Implement mul_u64_u64_div_u64_roundup()" (David Laight)
   enhances the 64-bit math code on behalf of a PWM driver and beefs up
   the test module for these library functions

 - "scripts/gdb/symbols: make BPF debug info available to GDB" (Ilya Leoshkevich)
   makes BPF symbol names, sizes, and line numbers available to the GDB
   debugger

 - "Enable hung_task and lockup cases to dump system info on demand" (Feng Tang)
   adds a sysctl which can be used to cause additional info dumping when
   the hung-task and lockup detectors fire

 - "lib/base64: add generic encoder/decoder, migrate users" (Kuan-Wei Chiu)
   adds a general base64 encoder/decoder to lib/ and migrates several
   users away from their private implementations

 - "rbree: inline rb_first() and rb_last()" (Eric Dumazet)
   makes TCP a little faster

 - "liveupdate: Rework KHO for in-kernel users" (Pasha Tatashin)
   reworks the KEXEC Handover interfaces in preparation for Live Update
   Orchestrator (LUO), and possibly for other future clients

 - "kho: simplify state machine and enable dynamic updates" (Pasha Tatashin)
   increases the flexibility of KEXEC Handover. Also preparation for LUO

 - "Live Update Orchestrator" (Pasha Tatashin)
   is a major new feature targeted at cloud environments. Quoting the
   cover letter:

      This series introduces the Live Update Orchestrator, a kernel
      subsystem designed to facilitate live kernel updates using a
      kexec-based reboot. This capability is critical for cloud
      environments, allowing hypervisors to be updated with minimal
      downtime for running virtual machines. LUO achieves this by
      preserving the state of selected resources, such as memory,
      devices and their dependencies, across the kernel transition.

      As a key feature, this series includes support for preserving
      memfd file descriptors, which allows critical in-memory data, such
      as guest RAM or any other large memory region, to be maintained in
      RAM across the kexec reboot.

   Mike Rappaport merits a mention here, for his extensive review and
   testing work.

 - "kexec: reorganize kexec and kdump sysfs" (Sourabh Jain)
   moves the kexec and kdump sysfs entries from /sys/kernel/ to
   /sys/kernel/kexec/ and adds back-compatibility symlinks which can
   hopefully be removed one day

 - "kho: fixes for vmalloc restoration" (Mike Rapoport)
   fixes a BUG which was being hit during KHO restoration of vmalloc()
   regions

* tag 'mm-nonmm-stable-2025-12-06-11-14' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (139 commits)
  calibrate: update header inclusion
  Reinstate "resource: avoid unnecessary lookups in find_next_iomem_res()"
  vmcoreinfo: track and log recoverable hardware errors
  kho: fix restoring of contiguous ranges of order-0 pages
  kho: kho_restore_vmalloc: fix initialization of pages array
  MAINTAINERS: TPM DEVICE DRIVER: update the W-tag
  init: replace simple_strtoul with kstrtoul to improve lpj_setup
  KHO: fix boot failure due to kmemleak access to non-PRESENT pages
  Documentation/ABI: new kexec and kdump sysfs interface
  Documentation/ABI: mark old kexec sysfs deprecated
  kexec: move sysfs entries to /sys/kernel/kexec
  test_kho: always print restore status
  kho: free chunks using free_page() instead of kfree()
  selftests/liveupdate: add kexec test for multiple and empty sessions
  selftests/liveupdate: add simple kexec-based selftest for LUO
  selftests/liveupdate: add userspace API selftests
  docs: add documentation for memfd preservation via LUO
  mm: memfd_luo: allow preserving memfd
  liveupdate: luo_file: add private argument to store runtime state
  mm: shmem: export some functions to internal.h
  ...
2025-12-06 14:01:20 -08:00

982 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;
};
FIXTURE_SETUP(nsid) {
self->child_pid = 0;
}
FIXTURE_TEARDOWN(nsid) {
/* Clean up any child process that may still be running */
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));
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));
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