WSL/test/linux/unit_tests/namespace.c

1658 lines
40 KiB
C
Raw Permalink Normal View History

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
Namespace.c
Abstract:
This file is a Namespace test.
--*/
#include "lxtcommon.h"
#include "unittests.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <dirent.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <net/if.h>
#include <bits/local_lim.h>
#include <linux/capability.h>
#include <linux/futex.h>
#include <linux/net_namespace.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/reboot.h>
#define LXT_NAME "Namespace"
#define SOCKET_LOOPBACK_IF_NAME "lo"
#define LxtReboot(_magic1, _magic2, _cmd, _arg) (syscall(SYS_reboot, (_magic1), (_magic2), (_cmd), (_arg)))
int NamespaceNetwork(PLXT_ARGS Args);
int NamespaceNetworkProcfs(PLXT_ARGS Args);
int NamespaceNetworkProcfsCheckFile(char* File, int ExpectedLineCount);
int NamespaceSetNs(PLXT_ARGS Args);
int NamespaceStat(PLXT_ARGS Args);
int NamespacePid(PLXT_ARGS Args);
int NamespacePidCheckProcPidStatStatusFiles(char* Dir);
void* NamespacePidChildPThread(void* Args);
int NamespacePidGetProcPidFolderCount(char* Dir, int* Count);
int NamespacePidBasic(int Level);
int NamespacePidBasicChild(void* Param);
int NamespacePidParentPThread(void);
int NamespaceUts(PLXT_ARGS Args);
int NamespaceIpc(PLXT_ARGS Args);
int NamespaceCloneInvalid(PLXT_ARGS Args);
//
// Global constants
//
static const LXT_VARIATION g_LxtVariations[] = {
{"NamespaceSetNs", NamespaceSetNs},
{"NamespaceStat", NamespaceStat},
{"Namespace UTS", NamespaceUts},
{"Namespace PID", NamespacePid},
{"Namespace Network", NamespaceNetwork},
{"Namespace Network - reading /proc/<pid>/net", NamespaceNetworkProcfs},
{"Namespace IPC", NamespaceIpc},
{"Namespace Clone - invalid namespace flags", NamespaceCloneInvalid}};
typedef struct _LXT_NAMESPACE_DATA
{
const char* Name;
int NsType;
} LXT_NAMESPACE_DATA, *PLXT_NAMESPACE_DATA;
static const LXT_NAMESPACE_DATA g_LxtNamespaces[] = {
{"ipc", CLONE_NEWIPC}, {"mnt", CLONE_NEWNS}, {"net", CLONE_NEWNET}, {"pid", CLONE_NEWPID}, {"user", CLONE_NEWUSER}, {"uts", CLONE_NEWUTS}};
int NamespaceTestEntry(int Argc, char* Argv[])
/*++
--*/
{
LXT_ARGS Args;
int Result;
LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME));
LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations)));
ErrorExit:
LxtUninitialize();
return !LXT_SUCCESS(Result);
}
void NamespaceSetNsChild(int NsFd)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Result;
//
// Drop the CAP_SYS_ADMIN capability.
//
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
LxtCheckErrno(LxtCapGet(&CapHeader, CapData)) CapData[CAP_TO_INDEX(CAP_SYS_ADMIN)].effective &= ~CAP_TO_MASK(CAP_SYS_ADMIN);
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// Try to setns without CAP_SYS_ADMIN.
//
LxtCheckErrnoFailure(setns(NsFd, 0), EPERM);
ErrorExit:
_exit(Result);
}
int NamespaceSetNs(PLXT_ARGS Args)
/*++
--*/
{
char Buffer[32];
pid_t ChildPid;
int Index;
int NsFd;
int Result;
NsFd = -1;
//
// Pass invalid parameters to setns
//
LxtCheckErrnoFailure(setns(0, CLONE_NEWPID), EINVAL);
LxtCheckErrnoFailure(setns(0, -1), EINVAL);
LxtCheckErrnoFailure(setns(-1, 0), EBADF);
//
// Pass the self fds to sets.
//
for (Index = 0; Index < LXT_COUNT_OF(g_LxtNamespaces); ++Index)
{
snprintf(Buffer, sizeof(Buffer), "/proc/self/ns/%s", g_LxtNamespaces[Index].Name);
printf("%s\n", Buffer);
LxtCheckErrno(NsFd = open(Buffer, O_RDONLY));
if (g_LxtNamespaces[Index].NsType != CLONE_NEWUSER)
{
LxtCheckErrno(setns(NsFd, 0));
LxtCheckErrno(setns(NsFd, g_LxtNamespaces[Index].NsType));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
NamespaceSetNsChild(NsFd);
}
LxtWaitPidPoll(ChildPid, 0);
}
else
{
LxtCheckErrnoFailure(setns(NsFd, 0), EINVAL);
LxtCheckErrnoFailure(setns(NsFd, g_LxtNamespaces[Index].NsType), EINVAL);
}
LxtCheckErrnoFailure(setns(NsFd, g_LxtNamespaces[(Index + 1) % LXT_COUNT_OF(g_LxtNamespaces)].NsType), EINVAL);
LxtCheckErrnoFailure(setns(NsFd, -1), EINVAL);
LxtClose(NsFd);
NsFd = -1;
}
ErrorExit:
if (NsFd != -1)
{
LxtClose(NsFd);
}
return Result;
}
int NamespaceStat(PLXT_ARGS Args)
{
char Buffer[32];
int Index;
int NsFd;
int Result;
struct stat StatData;
NsFd = -1;
//
// stat each namespace file and check the result.
//
for (Index = 0; Index < LXT_COUNT_OF(g_LxtNamespaces); ++Index)
{
snprintf(Buffer, sizeof(Buffer), "/proc/self/ns/%s", g_LxtNamespaces[Index].Name);
printf("%s\n", Buffer);
LxtCheckErrno(NsFd = open(Buffer, O_RDONLY));
LxtCheckErrno(fstat(NsFd, &StatData));
//
// TODO: st_dev is reported as 0 for files.
//
LxtCheckEqual(major(StatData.st_dev), 0, "%d");
LxtCheckNotEqual(StatData.st_ino, 0, "%d");
LxtCheckNotEqual(StatData.st_mode, 0, "%d");
LxtCheckEqual(StatData.st_nlink, 1, "%d");
LxtCheckEqual(StatData.st_uid, 0, "%d");
LxtCheckEqual(StatData.st_gid, 0, "%d");
LxtCheckEqual(StatData.st_rdev, 0, "%d");
LxtCheckEqual(StatData.st_size, 0, "%d");
LxtCheckEqual(StatData.st_blksize, 4096, "%d");
LxtCheckEqual(StatData.st_blocks, 0, "%d");
LxtClose(NsFd);
NsFd = -1;
}
Result = 0;
ErrorExit:
if (NsFd != -1)
{
LxtClose(NsFd);
}
return Result;
}
int VerifyUtsData(struct utsname* ExpectedValues)
{
struct utsname ActualValues;
int Fd;
int Length;
char ProcfsDomain[HOST_NAME_MAX];
char ProcfsHost[HOST_NAME_MAX];
int Result;
memset(&ActualValues, 0, sizeof(ActualValues));
LxtCheckErrno(uname(&ActualValues));
LxtCheckMemoryEqual(ExpectedValues, &ActualValues, sizeof(ActualValues));
LxtCheckErrno(Fd = open("/proc/sys/kernel/hostname", O_RDONLY));
LxtCheckErrno(Length = read(Fd, ProcfsHost, sizeof(ProcfsHost)));
ProcfsHost[Length - 1] = 0;
LxtClose(Fd);
Fd = -1;
LxtCheckStringEqual(ExpectedValues->nodename, ProcfsHost);
LxtCheckErrno(Fd = open("/proc/sys/kernel/domainname", O_RDONLY));
LxtCheckErrno(Length = read(Fd, ProcfsDomain, sizeof(ProcfsDomain)));
ProcfsDomain[Length - 1] = 0;
LxtClose(Fd);
Fd = -1;
LxtCheckStringEqual(ExpectedValues->domainname, ProcfsDomain);
Result = 0;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
return Result;
}
#define CHILD_HOST "childmachine"
#define CHILD_DOMAIN "childdomain"
int NamespaceUtsChild(struct utsname* ParentValues)
{
int Fd;
char Path[64];
pid_t Ppid;
int Result;
struct utsname UtsBuffer;
Fd = -1;
//
// Check the uts namespace behavior for before\after unshare.
//
memcpy(&UtsBuffer, ParentValues, sizeof(UtsBuffer));
LxtCheckResult(VerifyUtsData(&UtsBuffer));
LxtCheckErrno(unshare(CLONE_NEWUTS));
LxtCheckResult(VerifyUtsData(&UtsBuffer));
LxtCheckErrno(sethostname(CHILD_HOST, sizeof(CHILD_HOST)));
LxtCheckErrno(setdomainname(CHILD_DOMAIN, sizeof(CHILD_DOMAIN)));
memset(UtsBuffer.nodename, 0, sizeof(UtsBuffer.nodename));
memcpy(UtsBuffer.nodename, CHILD_HOST, sizeof(CHILD_HOST));
memset(UtsBuffer.domainname, 0, sizeof(UtsBuffer.domainname));
memcpy(UtsBuffer.domainname, CHILD_DOMAIN, sizeof(CHILD_DOMAIN));
LxtCheckResult(VerifyUtsData(&UtsBuffer));
//
// Check the uts namespace behavior after switching back to the parent
// uts namespace.
//
Ppid = getppid();
sprintf(Path, "/proc/%d/ns/uts", Ppid);
LxtCheckErrno(Fd = open(Path, O_RDONLY));
LxtCheckErrno(setns(Fd, CLONE_NEWUTS));
memcpy(&UtsBuffer, ParentValues, sizeof(UtsBuffer));
LxtCheckResult(VerifyUtsData(&UtsBuffer));
Result = 0;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
return Result;
}
void NamespaceUtsFork(struct utsname* ParentValues)
{
int Result;
Result = NamespaceUtsChild(ParentValues);
_exit(Result);
}
void* NamespaceUtsThread(void* Args)
{
struct utsname* ParentValues;
int Result;
ParentValues = Args;
Result = NamespaceUtsChild(Args);
pthread_exit(&Result);
}
int NamespaceUts(PLXT_ARGS Args)
{
pid_t ChildPid;
char* Name;
char NameBuffer[HOST_NAME_MAX];
int Result;
pthread_t ThreadId = {0};
struct utsname UtsBuffer;
//
// Check the UTS behavior for fork()
//
memset(&UtsBuffer, 0, sizeof(UtsBuffer));
LxtCheckErrno(uname(&UtsBuffer));
LxtCheckResult(VerifyUtsData(&UtsBuffer));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
NamespaceUtsFork(&UtsBuffer);
}
LxtWaitPidPoll(ChildPid, 0);
LxtCheckResult(VerifyUtsData(&UtsBuffer));
//
// Check the UTS behavior for a pthread.
//
LxtCheckErrnoZeroSuccess(pthread_create(&ThreadId, NULL, NamespaceUtsThread, &UtsBuffer));
pthread_join(ThreadId, NULL);
LxtCheckResult(VerifyUtsData(&UtsBuffer));
//
// Test behavior for NULL names.
//
Name = NULL;
LxtCheckErrno(sethostname(Name, 0));
LxtCheckErrno(setdomainname(Name, 0));
memset(NameBuffer, 1, sizeof(NameBuffer));
LxtCheckErrno(gethostname(NameBuffer, sizeof(NameBuffer)));
LxtCheckEqual(NameBuffer[0], 0, "%c");
memset(NameBuffer, 1, sizeof(NameBuffer));
LxtCheckErrno(getdomainname(NameBuffer, sizeof(NameBuffer)));
LxtCheckEqual(NameBuffer[0], 0, "%c");
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidCheckProcPidStatStatusFiles(char* Dir)
{
char Command[80];
int Gid;
int IntValue;
char Line[40];
char Name[20];
int Pid;
int Ppid;
int Result;
char State[10];
FILE* StatFile;
char StatFileName[30];
FILE* StatusFile;
char StatusFileName[30];
int Tid;
int Tgid;
//
// Check the stat file in ProcFs.
//
sprintf(StatFileName, "%s/%s", Dir, "/stat");
StatFile = fopen(StatFileName, "r");
LxtCheckNotEqual(StatFile, NULL, "%p");
LxtCheckEqual(fscanf(StatFile, "%d %s %s %d %d", &Tid, Command, State, &Ppid, &Gid), 5, "%d");
LxtLogInfo("%d %s %s %d %d", Tid, Command, State, Ppid, Gid);
LxtCheckEqual(Tid, 1, "%d");
LxtCheckEqual(Ppid, 0, "%d");
LxtCheckEqual(Gid, 0, "%d");
//
// Check the status file in ProcFs.
//
sprintf(StatusFileName, "%s/%s", Dir, "/status");
StatusFile = fopen(StatusFileName, "r");
LxtCheckNotEqual(StatusFile, NULL, "%p");
Tgid = -1;
Pid = -1;
Ppid = -1;
while (fgets(Line, LXT_COUNT_OF(Line), StatusFile) != NULL)
{
if (sscanf(Line, "%s %d", Name, &IntValue) == 2)
{
if (strcmp(Name, "Tgid:") == 0)
{
Tgid = IntValue;
}
else if (strcmp(Name, "Pid:") == 0)
{
Pid = IntValue;
}
else if (strcmp(Name, "PPid:") == 0)
{
Ppid = IntValue;
}
}
}
LxtCheckEqual(Tgid, 1, "%d");
LxtCheckEqual(Pid, 1, "%d");
LxtCheckEqual(Ppid, 0, "%d");
Result = LXT_RESULT_SUCCESS;
ErrorExit:
//
// Intentially do not close the files. These files are leaked to ensure
// the private procfs instance is cleaned up correctly when there are
// file descriptors that need to be closed during thread group end.
//
return Result;
}
void* NamespacePidChildPThread(void* Args)
{
int Result;
LxtLogError("Child pthread ran unexpectedly in new namespace");
Result = -1;
pthread_exit(&Result);
}
int NamespacePidGetProcPidFolderCount(char* Dir, int* Count)
{
int CountLocal;
struct dirent* DirEnt;
DIR* Fd;
char FullPath[257];
int Result;
struct stat StatBuffer;
//
// Get the number of /proc/<pid> folders.
//
Fd = opendir(Dir);
if (Fd == NULL)
{
LxtLogError("opendir failed, errno: %d (%s)", errno, strerror(errno));
Result = LXT_RESULT_FAILURE;
goto ErrorExit;
}
CountLocal = 0;
while ((DirEnt = readdir(Fd)) != NULL)
{
sprintf(FullPath, "%s/%s", Dir, DirEnt->d_name);
LxtLogInfo("Calling stat() on proc folder %s", FullPath);
LxtCheckErrnoZeroSuccess(stat(FullPath, &StatBuffer));
//
// Must be a directory, and its name must start with a digit.
//
if ((StatBuffer.st_mode & S_IFDIR) != S_IFDIR)
{
continue;
}
if (isdigit(DirEnt->d_name[0]) == FALSE)
{
continue;
}
CountLocal += 1;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd != NULL)
{
closedir(Fd);
}
*Count = CountLocal;
return Result;
}
int PidBasicPipesA[2];
int PidBasicPipesB[2];
#define NAMESPACE_PID_BASIC_TOKEN (0x12345678)
#define PID_BASIC_PARENT_PIPE_READ (PidBasicPipesA[0])
#define PID_BASIC_PARENT_PIPE_WRITE (PidBasicPipesB[1])
#define PID_BASIC_CHILD_PIPE_READ (PidBasicPipesB[0])
#define PID_BASIC_CHILD_PIPE_WRITE (PidBasicPipesA[1])
int NamespacePidBasic(int Level)
{
pid_t ChildId;
LXT_CLONE_ARGS CloneArgs;
pid_t CurrentId;
int Result;
int Size;
int Token;
//
// Create a set of pipes to synchronize with the child PID namespaces.
//
LxtCheckErrnoZeroSuccess(pipe(PidBasicPipesA));
LxtCheckErrnoZeroSuccess(pipe(PidBasicPipesB));
//
// Clone a child into a new PID namespace.
//
LxtCheckResult(LxtClone(NamespacePidBasicChild, &Level, CLONE_NEWPID | SIGCHLD, &CloneArgs));
//
// Close the child pipes.
//
LxtCheckErrnoZeroSuccess(close(PID_BASIC_CHILD_PIPE_READ));
PID_BASIC_CHILD_PIPE_READ = -1;
LxtCheckErrnoZeroSuccess(close(PID_BASIC_CHILD_PIPE_WRITE));
PID_BASIC_CHILD_PIPE_WRITE = -1;
//
// Wait for the entire hierarchy to be created.
//
LxtCheckErrno(Size = read(PID_BASIC_PARENT_PIPE_READ, &Token, sizeof(Token)));
LxtCheckEqual(Size, sizeof(Token), "%d");
LxtCheckEqual(Token, NAMESPACE_PID_BASIC_TOKEN, "%x");
//
// Validate the PGID of the child.
//
LxtCheckErrno(CurrentId = getpgid(0));
LxtCheckErrno(ChildId = getpgid(CloneArgs.CloneId));
LxtCheckEqual(CurrentId, ChildId, "%d");
//
// Validate the SID of the child.
//
LxtCheckErrno(CurrentId = getsid(0));
LxtCheckErrno(ChildId = getsid(CloneArgs.CloneId));
LxtCheckEqual(CurrentId, ChildId, "%d");
//
// Notify the hierarchy to exit and wait.
//
LxtCheckErrno(Size = write(PID_BASIC_PARENT_PIPE_WRITE, &Token, sizeof(Token)));
LxtCheckEqual(Size, sizeof(Token), "%d");
LxtCheckResult(LxtWaitPidPoll(CloneArgs.CloneId, 0));
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidBasicChild(void* Param)
{
pid_t ChildPid;
LXT_CLONE_ARGS CloneArgs;
int Level;
pid_t Pgid;
pid_t Pid;
int PidFolderCount;
pid_t Ppid;
int Result;
pid_t Sid;
int Size;
pid_t Tid;
int Token;
Level = *((int*)Param);
//
// Close the parent pipes.
//
if (Level == 0)
{
LxtCheckErrnoZeroSuccess(close(PID_BASIC_PARENT_PIPE_READ));
PID_BASIC_PARENT_PIPE_READ = -1;
LxtCheckErrnoZeroSuccess(close(PID_BASIC_PARENT_PIPE_WRITE));
PID_BASIC_PARENT_PIPE_WRITE = -1;
}
usleep(1000 * 80);
//
// Validate that the first thread/threadgroup in a PID namespace has PID 1.
//
LxtCheckErrno(Tid = gettid());
LxtCheckEqual(Tid, 1, "%d");
LxtCheckErrno(Pid = getpid());
LxtCheckEqual(Pid, 1, "%d");
//
// Validate that the first thread in a PID namespace cannot see its current
// process group, session or parent.
//
LxtCheckErrno(Pgid = getpgid(0));
LxtCheckEqual(Pgid, 0, "%d");
LxtCheckErrno(Sid = getsid(0));
LxtCheckEqual(Sid, 0, "%d");
LxtCheckErrno(Ppid = getppid());
LxtCheckEqual(Ppid, 0, "%d");
//
// Run the following in its own mount namespace and mark all mounts in the
// new namespace private so changes cannot propagate to the rest of the
// system.
//
LxtCheckErrno(unshare(CLONE_NEWNS));
LxtCheckErrnoZeroSuccess(mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL));
//
// Re-mount /proc. This version of /proc should not contain any PIDs from
// the parent PID namespace.
//
LxtCheckErrnoZeroSuccess(mount(NULL, "/proc", "proc", 0, NULL));
//
// Do some basic validation.
//
LxtCheckErrnoZeroSuccess(access("/proc", R_OK));
LxtCheckErrnoZeroSuccess(access("/proc/1", R_OK));
LxtCheckErrnoZeroSuccess(access("/proc/1/cmdline", R_OK));
LxtCheckErrnoZeroSuccess(access("/proc/self", R_OK));
LxtCheckErrnoZeroSuccess(access("/proc/self/cmdline", R_OK));
LxtCheckErrnoFailure(access("/proc/0", R_OK), ENOENT);
LxtCheckErrnoFailure(access("/proc/1234567890", R_OK), ENOENT);
//
// Check that there is only 1 /proc/<pid> folder.
//
LxtLogInfo("Checking /proc/<pid> folders, before clone, nested level %d", Level);
LxtCheckResult(NamespacePidGetProcPidFolderCount("/proc", &PidFolderCount));
LxtCheckEqual(PidFolderCount, 1, "%d");
//
// Check the /proc/1/stat, /proc/1/status, /proc/1/task/1/stat
// and /proc/1/task/1/status files.
//
NamespacePidCheckProcPidStatStatusFiles("/proc/1/");
NamespacePidCheckProcPidStatStatusFiles("/proc/1/task/1");
//
// Test nested PID namespaces.
//
if (Level < 3)
{
Level += 1;
LxtCheckResult(LxtClone(NamespacePidBasicChild, &Level, CLONE_NEWPID | SIGCHLD, &CloneArgs));
//
// After the clone, check that there are now at least 2 /proc/<pid>
// folders.
//
// N.B. The cloned process will recursively create more cloned PID
// namespaces, causing more PIDs to appear under this /proc mount.
//
LxtLogInfo("Checking /proc/<pid> folders, after clone, nested level %d", Level);
LxtCheckResult(NamespacePidGetProcPidFolderCount("/proc", &PidFolderCount));
LxtCheckGreaterOrEqual(PidFolderCount, 2, "%d");
//
// Check the /proc/1/stat, /proc/1/status, /proc/1/task/1/stat
// and /proc/1/task/1/status files.
//
NamespacePidCheckProcPidStatStatusFiles("/proc/1/");
NamespacePidCheckProcPidStatStatusFiles("/proc/1/task/1");
//
// Wait for the child to exit.
//
LxtCheckResult(LxtWaitPidPoll(CloneArgs.CloneId, 0));
}
else
{
//
// Signal to the test that the hierarchy is created.
//
Token = NAMESPACE_PID_BASIC_TOKEN;
LxtCheckErrno(Size = write(PID_BASIC_CHILD_PIPE_WRITE, &Token, sizeof(Token)));
LxtCheckEqual(Size, sizeof(Token), "%d");
//
// Wait for the notification to exit.
//
LxtCheckErrno(Size = read(PID_BASIC_CHILD_PIPE_READ, &Token, sizeof(Token)));
LxtCheckEqual(Size, sizeof(Token), "%d");
LxtCheckEqual(Token, NAMESPACE_PID_BASIC_TOKEN, "%x");
}
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidParentPThread(void)
{
pthread_t ThreadId = {0};
int Result;
//
// Create a new child PID namespace and check that pthread creation fails.
//
LxtCheckErrno(unshare(CLONE_NEWPID));
LxtCheckEqual(pthread_create(&ThreadId, NULL, NamespacePidChildPThread, NULL), EINVAL, "%d");
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidTerminate(int Level, int* Ready)
{
pid_t ChildPid;
int Index;
int Result;
LxtLogInfo("[%d/%d] PID namespace leader", Level, getpid());
//
// Create 10 child threadgroups that loop sleeping.
//
for (Index = 0; Index < 10; Index += 1)
{
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
for (;;)
{
sleep(-1);
}
}
LxtLogInfo("[%d/%d] PID namespace sleeper %d", Level, getpid(), ChildPid);
}
//
// Create 3 levels of nested PID namespaces and then signal the ready futex.
//
if (Level < 3)
{
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
LxtLogInfo("[%d/%d] PID namespace trampoline", Level, getpid());
LxtCheckErrno(unshare(CLONE_NEWPID));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidTerminate(Level + 1, Ready));
}
_exit(0);
}
}
else
{
LxtLogInfo("[%d/%d] Signaling ready futex...", Level, getpid());
*Ready = 1;
LxtCheckErrno(LxtFutex(Ready, FUTEX_WAKE, 1, NULL, NULL, 0));
}
//
// Sleep.
//
for (;;)
{
sleep(-1);
}
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidTestTerminate()
{
pid_t ChildPid;
void* MapResult;
int* Ready;
int Result;
struct timespec Time;
int WaitStatus;
//
// Create the ready futex.
//
LxtCheckMapErrno(Ready = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0));
*Ready = 0;
//
// Create the top-level PID namespace.
//
LxtCheckErrno(unshare(CLONE_NEWPID));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidTerminate(0, Ready));
}
//
// Wait for the ready futex.
//
Time.tv_sec = 10;
Time.tv_nsec = 0;
LxtLogInfo("[%d] Waiting for all child threadgroups and namespaces to be created...", getpid());
while (*Ready == 0)
{
Result = LxtFutex(Ready, FUTEX_WAIT, 0, &Time, NULL, 0);
if ((Result == -1) && (errno != EAGAIN) && (errno != EINTR))
{
LxtCheckErrno(Result);
}
}
//
// Terminate the top-level PID namespace and wait on the leader.
//
LxtCheckErrnoZeroSuccess(kill(ChildPid, SIGKILL));
LxtWaitPidPoll(ChildPid, SIGKILL);
//
// Sleep and make sure that there are no other waits pending.
//
sleep(1);
LxtCheckErrnoFailure(waitpid(-1, &WaitStatus, WNOHANG), ECHILD);
Result = 0;
ErrorExit:
return Result;
}
int NamespacePidTestReboot()
{
pid_t ChildPid;
pid_t Pid;
int* Ready;
int Result;
int WaitStatus;
Pid = getpid();
//
// Create a child process of init that calls reboot.
//
// N.B. Init is terminated with SIGINT and the signal handler is not
// invoked.
//
LxtCheckErrno(unshare(CLONE_NEWPID));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
LxtCheckErrnoFailure(LxtReboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_CAD_ON, NULL), EINVAL);
LxtCheckErrnoFailure(LxtReboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_CAD_OFF, NULL), EINVAL);
LxtCheckResult(LxtSignalInitialize());
LxtCheckResult(LxtSignalSetupHandler(SIGINT, SA_SIGINFO));
LxtLogInfo("Forking...");
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
LxtCheckResult(LxtReboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL));
_exit(0);
}
LxtLogInfo("Waiting...");
LxtSignalWait();
LxtCheckResult(LxtWaitPidPoll(ChildPid, 0));
_exit(0);
}
//
// Wait for the reboot signal.
//
LxtCheckResult(LxtWaitPidPoll(ChildPid, SIGINT));
//
// Sleep and make sure that there are no other waits pending.
//
sleep(1);
LxtCheckErrnoFailure(waitpid(-1, &WaitStatus, WNOHANG), ECHILD);
Result = 0;
ErrorExit:
if (Pid != getpid())
{
_exit(Result);
}
return Result;
}
int NamespacePid(PLXT_ARGS Args)
{
pid_t ChildPid;
int Result;
//
// Check basic PID namespace behavior.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidBasic(0));
}
LxtWaitPidPoll(ChildPid, 0);
//
// Check the pid namespace behavior for a pthread.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidParentPThread());
}
LxtWaitPidPoll(ChildPid, 0);
//
// Check the pid namespace behavior for signals, termination and waits.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidTestTerminate());
}
LxtWaitPidPoll(ChildPid, 0);
//
// Check the pid namesapce behavior for reboot.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
_exit(NamespacePidTestReboot());
}
LxtWaitPidPoll(ChildPid, 0);
Result = 0;
ErrorExit:
return Result;
}
int NamespaceNetworkGetNSID(int* NetworkNamespaceId)
{
socklen_t AddressLength;
struct rtattr* Attribute;
struct sockaddr_nl BindAddress;
struct nlmsgerr* Error;
struct nlmsghdr* Header;
int Index;
int NetworkNamespaceFd;
int ReceiveResult;
int RemainingLength;
struct
{
struct nlmsghdr nlh;
struct rtgenmsg msg __attribute__((aligned(NLMSG_ALIGNTO)));
struct
{
struct rtattr rta __attribute__((aligned(RTA_ALIGNTO)));
int val __attribute__((aligned(RTA_ALIGNTO)));
} data[5];
} Request, Response;
int Result;
struct rtgenmsg* RtGenMsg;
int Socket;
//
// Open network namespace file descriptor.
//
LxtCheckErrno(NetworkNamespaceFd = open("/proc/self/ns/net", O_RDONLY));
//
// Create and bind socket. Create a RTM_GETNSID request.
//
LxtCheckErrno(Socket = socket(AF_NETLINK, SOCK_RAW, 0));
memset(&BindAddress, 0, sizeof(BindAddress));
BindAddress.nl_family = AF_NETLINK;
AddressLength = sizeof(BindAddress);
LxtCheckErrno(bind(Socket, (struct sockaddr*)&BindAddress, AddressLength));
memset(&Request, 0, sizeof(Request));
Request.nlh.nlmsg_len = NLMSG_SPACE(sizeof(Request.msg)) + RTA_SPACE(sizeof(int));
Request.nlh.nlmsg_type = RTM_GETNSID;
Request.nlh.nlmsg_seq = 0x4567;
Request.msg.rtgen_family = AF_UNSPEC;
Request.nlh.nlmsg_flags = NLM_F_REQUEST;
Request.data[0].rta.rta_len = RTA_LENGTH(sizeof(int));
Request.data[0].rta.rta_type = NETNSA_FD;
Request.data[0].val = NetworkNamespaceFd;
LxtCheckErrno(sendto(Socket, &Request, sizeof(Request), 0, NULL, 0));
memset(&Response, 0, sizeof(Response));
LxtCheckErrno(ReceiveResult = recvfrom(Socket, &Response, sizeof(Response), 0, NULL, 0));
LxtCheckTrue(NLMSG_OK(&Response.nlh, ReceiveResult));
LxtCheckEqual(Response.nlh.nlmsg_type, RTM_NEWNSID, "%hd");
LxtCheckTrue(Response.nlh.nlmsg_len >= NLMSG_LENGTH(sizeof(Response.msg)));
Attribute = &Response.data[0].rta;
RemainingLength = Response.nlh.nlmsg_len - NLMSG_LENGTH(sizeof(Response.msg));
LxtCheckTrue(RTA_OK(Attribute, RemainingLength));
*NetworkNamespaceId = *(int*)RTA_DATA(Attribute);
Attribute = RTA_NEXT(Attribute, RemainingLength);
LxtCheckTrue(RTA_OK(Attribute, RemainingLength) == FALSE);
LxtCheckTrue(NLMSG_OK(NLMSG_NEXT(&Response.nlh, ReceiveResult), ReceiveResult) == FALSE);
Result = 0;
ErrorExit:
if (Socket > 0)
{
close(Socket);
}
if (NetworkNamespaceFd > 0)
{
close(NetworkNamespaceFd);
}
return Result;
}
int NamespaceNetwork(PLXT_ARGS Args)
{
int NetworkNamespaceId;
int OriginalNetworkNamespaceFd;
int Result;
DIR* SysClassNetDirectory;
//
// Open file descriptor of default network namespace.
//
LxtCheckErrno(OriginalNetworkNamespaceFd = open("/proc/self/ns/net", 0));
//
// Verify default namespace ID is set.
//
LxtCheckErrno(NamespaceNetworkGetNSID(&NetworkNamespaceId));
LxtCheckEqual(NetworkNamespaceId, -1, "%d");
//
// Switch to a new network namespace.
//
LxtCheckErrno(unshare(CLONE_NEWNET));
//
// Verify default namespace ID is set.
//
LxtCheckErrno(NamespaceNetworkGetNSID(&NetworkNamespaceId));
LxtCheckEqual(NetworkNamespaceId, -1, "%d");
//
// Switch back to original network namespace.
//
LxtCheckErrno(setns(OriginalNetworkNamespaceFd, CLONE_NEWNET));
Result = 0;
ErrorExit:
if (OriginalNetworkNamespaceFd >= 0)
{
LxtClose(OriginalNetworkNamespaceFd);
}
return Result;
}
int NamespaceNetworkProcfs(PLXT_ARGS Args)
{
pid_t ChildPid;
char FileName[50];
struct ifreq InterfaceUpRequest;
int Result;
int Socket;
//
// Create a child process and switch it to a new network namespace.
// From the parent, read the child's /proc/<pid>/net entries and verify
// that those entries reflect the network state of the child.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
LxtCheckErrnoZeroSuccess(unshare(CLONE_NEWNET));
//
// Bring the loopback up so that it shows up in procfs. (Needed for native
// Ubuntu only - on WSL the loopback is automatically UP on namespace creation.)
//
LxtCheckErrno(Socket = socket(AF_UNIX, SOCK_DGRAM, 0));
memset(&InterfaceUpRequest, 0, sizeof(InterfaceUpRequest));
strncpy(InterfaceUpRequest.ifr_name, SOCKET_LOOPBACK_IF_NAME, sizeof(InterfaceUpRequest.ifr_name) - 1);
usleep(1000 * 100);
LxtCheckErrnoZeroSuccess(ioctl(Socket, SIOCGIFFLAGS, &InterfaceUpRequest));
LxtCheckEqual(InterfaceUpRequest.ifr_flags & IFF_LOOPBACK, IFF_LOOPBACK, "%d");
InterfaceUpRequest.ifr_flags |= IFF_UP;
LxtCheckErrnoZeroSuccess(ioctl(Socket, SIOCSIFFLAGS, &InterfaceUpRequest));
close(Socket);
//
// Keep the child alive so that the parent can examine its /proc/<pid>/net entries.
//
while (1)
;
exit(0);
}
//
// N.B. The sleep is because it can take some time for the lxcore cache to get
// the new network interface notification.
//
usleep(1000 * 200);
//
// Check the /proc/<pid>/net/dev file. This file should have 3 lines
// (2 lines header and one line for lo).
//
sprintf(FileName, "/proc/%d/net/dev", ChildPid);
LxtCheckResult(NamespaceNetworkProcfsCheckFile(FileName, 3));
//
// Check the /proc/<pid>/net/if_inet6 file. This file should have 1 line,
// for lo.
//
sprintf(FileName, "/proc/%d/net/if_inet6", ChildPid);
LxtCheckResult(NamespaceNetworkProcfsCheckFile(FileName, 1));
//
// Check the /proc/<pid>/net/route file. This file has one line on Ubuntu
// (1 line header and no routing entries) and multiple lines on WSL. Therefore,
// don't check the line count for this file.
//
sprintf(FileName, "/proc/%d/net/route", ChildPid);
LxtCheckResult(NamespaceNetworkProcfsCheckFile(FileName, -1));
//
// All done, the child should die now.
//
kill(ChildPid, SIGKILL);
LxtCheckResult(LxtWaitPidPoll(ChildPid, SIGKILL));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int NamespaceNetworkProcfsCheckFile(char* FileName, int ExpectedLineCount)
{
char Buffer[200];
FILE* File;
int LineCount;
int Result;
//
// Open the file up and check how many lines it has.
//
LineCount = 0;
File = fopen(FileName, "r");
LxtCheckTrue(File != NULL);
while (fgets(Buffer, LXT_COUNT_OF(Buffer), File) != NULL)
{
LineCount += 1;
//
// The following strings should not be seen, since the only network
// interface in the file should be lo.
//
LxtCheckTrue(strstr(Buffer, "eth") == NULL);
LxtCheckTrue(strstr(Buffer, "wlan") == NULL);
LxtCheckTrue(strstr(Buffer, "wifi") == NULL);
LxtCheckTrue(strstr(Buffer, "und") == NULL);
}
if (ExpectedLineCount != -1)
{
LxtCheckEqual(LineCount, ExpectedLineCount, "%d");
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (File != NULL)
{
fclose(File);
}
return Result;
}
typedef struct _LXT_NAMESPACE_IPC_DATA
{
int Id;
void* Address;
} LXT_NAMESPACE_IPC_DATA, *PLXT_NAMESPACE_IPC_DATA;
int NamespaceIpcChild(PLXT_NAMESPACE_IPC_DATA Data)
{
int Fd;
char Path[64];
pid_t Ppid;
int Result;
struct shmid_ds Stat;
Fd = -1;
//
// Check the ipc namespace behavior for before\after unshare.
//
LxtCheckErrno(LxtShmCtl(Data->Id, IPC_STAT, &Stat));
LxtCheckErrno(unshare(CLONE_NEWIPC));
LxtCheckErrnoFailure(LxtShmCtl(Data->Id, IPC_STAT, &Stat), EINVAL);
//
// shmdt should still succeed in the new namespace.
//
LxtCheckErrno(LxtShmDt(Data->Address));
//
// Check the ipc namespace behavior after switching back to the parent
// ipc namespace.
//
Ppid = getppid();
sprintf(Path, "/proc/%d/ns/ipc", Ppid);
LxtCheckErrno(Fd = open(Path, O_RDONLY));
LxtCheckErrno(setns(Fd, CLONE_NEWIPC));
LxtCheckErrno(LxtShmCtl(Data->Id, IPC_STAT, &Stat));
Result = 0;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
return Result;
}
void NamespaceIpcFork(PLXT_NAMESPACE_IPC_DATA Data)
{
int Result;
Result = NamespaceIpcChild(Data);
_exit(Result);
}
void* NamespaceIpcThread(void* Args)
{
PLXT_NAMESPACE_IPC_DATA Data;
int Result;
Data = Args;
Result = NamespaceIpcChild(Data);
pthread_exit(&Result);
}
int NamespaceIpc(PLXT_ARGS Args)
{
int ChildPid;
LXT_NAMESPACE_IPC_DATA Data;
void* MapResult;
int Result;
struct shmid_ds Stat;
pthread_t ThreadId = {0};
Data.Id = -1;
Data.Address = NULL;
//
// Check the IPC behavior for fork()
//
LxtCheckErrno(Data.Id = LxtShmGet(IPC_PRIVATE, PAGE_SIZE, 0));
LxtCheckMapErrno(Data.Address = LxtShmAt(Data.Id, NULL, 0));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
NamespaceIpcFork(&Data);
}
LxtWaitPidPoll(ChildPid, 0);
LxtCheckErrno(LxtShmCtl(Data.Id, IPC_STAT, &Stat));
//
// Verify shmdt succeeds and create a new mapping.
//
LxtCheckErrno(LxtShmDt(Data.Address));
LxtCheckMapErrno(Data.Address = LxtShmAt(Data.Id, NULL, 0));
//
// Check the IPC behavior for a pthread.
//
LxtCheckErrnoZeroSuccess(pthread_create(&ThreadId, NULL, NamespaceIpcThread, &Data));
pthread_join(ThreadId, NULL);
LxtCheckErrno(LxtShmCtl(Data.Id, IPC_STAT, &Stat));
//
// Verify shmdt fails (was unmapped by the pthread).
//
LxtCheckErrnoFailure(LxtShmDt(Data.Address), EINVAL);
Data.Address = NULL;
Result = 0;
ErrorExit:
if (Data.Id != -1)
{
LxtShmCtl(Data.Id, IPC_RMID, NULL);
}
if (Data.Address != NULL)
{
LxtShmDt(Data.Address);
}
return Result;
}
int NamespaceCloneInvalidChild(unsigned long Flags, PLXT_PIPE Pipe)
/*++
Description:
This routine is the child process for WaitPidVariationCloneParent.
Arguments:
None..
Return Value:
Returns 0 on success, -1 on failure.
--*/
{
pid_t ChildPid;
pid_t GrandChildParent;
pid_t ChildParent;
int Result;
int WaitPidStatus;
Result = 0;
//
// Create a child process with the requested CLONE_PARENT and other flag.
//
// The new process should not be reported as a child.
//
ChildParent = getppid();
LxtLogInfo("ChildParent %d", ChildParent);
LxtCheckResult(ChildPid = LxtCloneSyscall(Flags | SIGCHLD, NULL, NULL, NULL, NULL));
if (ChildPid == 0)
{
GrandChildParent = getppid();
if ((Flags & CLONE_NEWPID) != 0)
{
LxtCheckEqual(0, GrandChildParent, "%d");
}
else
{
LxtCheckEqual(ChildParent, GrandChildParent, "%d");
}
LxtLogInfo("Grand child %d exiting", LxtGetTid());
}
else
{
LxtCheckErrnoFailure(waitpid(ChildPid, &WaitPidStatus, 0), ECHILD);
LxtCheckResult(write(Pipe->Write, &ChildPid, sizeof(ChildPid)));
LxtLogInfo("Child %d exiting", LxtGetTid());
}
Result = 0;
ErrorExit:
_exit(Result);
}
int NamespaceCloneInvalid(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
LXT_PIPE Pipe = {-1, -1};
int PipeData;
int Result;
ChildPid = -1;
LxtCheckResult(LxtCreatePipe(&Pipe));
//
// CLONE_NEWPID and CLONE_NEWUSER can't be specified with CLONE_THREAD.
//
LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall(CLONE_NEWPID | CLONE_THREAD, NULL, NULL, NULL, NULL), EINVAL);
LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall(CLONE_NEWUSER | CLONE_THREAD, NULL, NULL, NULL, NULL), EINVAL);
//
// CLONE_NEWPID and CLONE_NEWUSER can be specified with CLONE_PARENT
// (incorrect man pages).
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
NamespaceCloneInvalidChild(CLONE_NEWPID | CLONE_PARENT, &Pipe);
}
LxtCheckResult(read(Pipe.Read, &PipeData, sizeof(PipeData)));
LxtCheckResult(LxtWaitPidPoll(PipeData, 0));
LxtCheckResult(LxtWaitPidPoll(ChildPid, 0));
//
// CLONE_NEWIPC and CLONE_SYSVSEM are not allowed together.
//
LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall(CLONE_NEWIPC | CLONE_SYSVSEM, NULL, NULL, NULL, NULL), EINVAL);
//
// TODO_LX: Enable the variation below when CLONE_NEWUSER is supported on
// WSL.
//
/*
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0) {
NamespaceCloneInvalidChild(CLONE_NEWUSER | CLONE_PARENT, &Pipe);
}
LxtCheckResult(read(Pipe.Read, &PipeData, sizeof(PipeData)));
LxtCheckResult(LxtWaitPidPoll(PipeData, 0));
LxtCheckResult(LxtWaitPidPoll(ChildPid, 0));
*/
Result = 0;
ErrorExit:
if (ChildPid == 0)
{
_exit(Result);
}
LxtClosePipe(&Pipe);
return Result;
}