/*++ Copyright (c) Microsoft. All rights reserved. Module Name: execve.c Abstract: This file contains the execve tests. --*/ #include "lxtcommon.h" #include "unittests.h" #include #include #include #include #include #include #include #include "lxtutil.h" #define LXT_NAME "Execve" #define LXT_EXECV_TEST_DIRECTORY "/data/test/execvDir" int ExecveExecValidate(char* Path); int ExecveValidate(int ExpectedPid); int ExecveVariationArguments(PLXT_ARGS Args); int ExecveVariationSingle(PLXT_ARGS Args); int ExecveVariationMultipleWithThreads(PLXT_ARGS Args); int ExecveWaitForChild(void); void* ExecveWorkerThread(void* Arg); void* ExecveWorkerThread2(void* Arg); // // Global constants // static const LXT_VARIATION g_LxtVariations[] = { {"Execve - Single", ExecveVariationSingle}, {"Execve - Multiple with threads", ExecveVariationMultipleWithThreads}, {"Execve - Arguments", ExecveVariationArguments}}; int ExecveTestEntry(int Argc, char* Argv[], char** Envp) /*++ --*/ { LXT_ARGS Args; int Result; // // For a child invocation, validate that the PID/TID is as expected. // if ((Argc == 3) && (strcmp(Argv[1], "-c") == 0)) { int Pid; Pid = atoi(Argv[2]); LxtCheckResult(ExecveValidate(Pid)); goto ErrorExit; } // // For environment variable child validation, make sure a non-NULL pointer // is passed but there are no arguments. // if ((Argc == 2) && (strcmp(Argv[1], "-e") == 0)) { LxtCheckTrue(Envp != NULL); char** Env; int Count = 0; for (Env = Envp; *Env != NULL; Env += 1) { Count += 1; } LxtCheckTrue(Count == 0); goto ErrorExit; } // // Run the master test. // LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME)); LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations))); ErrorExit: LxtUninitialize(); return !LXT_SUCCESS(Result); } int ExecveExecValidate(char* Path) /*++ --*/ { char* ExecArgs[5]; char ExpectedPid[16]; int Pid; int Result; Pid = getpid(); LxtLogInfo("Execve'ing validation for PID %d", Pid); sprintf(ExpectedPid, "%d", Pid); ExecArgs[0] = Path; ExecArgs[1] = "execve"; ExecArgs[2] = "-c"; ExecArgs[3] = ExpectedPid; ExecArgs[4] = NULL; execv(ExecArgs[0], ExecArgs); LxtCheckTrue(FALSE); Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int ExecveValidate(int ExpectedPid) /*++ --*/ { int Pid; int Result; int ThreadCount; int Tid; // // Check that the PID/TID matches the expected value. // Pid = getpid(); Tid = gettid(); LxtCheckTrue(Pid == ExpectedPid); LxtCheckTrue(Tid == ExpectedPid); Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } #define COMMAND_LINE_SIZE (((size_t)MAX_ARG_STRINGS) + 1) #define LARGE_STRING_SIZE (((size_t)MAX_ARG_STRLEN) + 1) #define MAX_STACK_SIZE ((size_t)(2 * 1024 * 1024)) int ExecveVariationArguments(PLXT_ARGS Args) /*++ --*/ { int ChildPid; char* ExecArgs[4]; char** ExecArgsLong; size_t Index; char* LongString; int Result; int Status; size_t TotalSize; ExecArgsLong = NULL; LongString = NULL; // // Test a null environment block. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = "/bin/true"; ExecArgs[1] = NULL; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(ExecveWaitForChild()); } // // Test exec args with spaces and path. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = "/bin/true with a space"; ExecArgs[1] = NULL; LxtCheckErrno(LxtExecve("/bin/true", ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(ExecveWaitForChild()); } // // Validate that a null environment block results in zero entries for the // environment argument to the main function. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = WSL_UNIT_TEST_BINARY; ExecArgs[1] = "execve"; ExecArgs[2] = "-e"; ExecArgs[3] = NULL; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(ExecveWaitForChild()); } // // Allocate a very long string of 'a' and null terminate to // validate command line parsing limits. // LongString = malloc(LARGE_STRING_SIZE); if (LongString == NULL) { LxtLogError("malloc"); goto ErrorExit; } memset(LongString, 'a', LARGE_STRING_SIZE); LongString[LARGE_STRING_SIZE - 1] = '\0'; // // Create a child and verify that exec fails with too long of a string // in the command line or environment variable array. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = "/bin/false"; ExecArgs[1] = LongString; ExecArgs[2] = NULL; LxtCheckErrnoFailure(LxtExecve(ExecArgs[0], ExecArgs, NULL), E2BIG); LxtCheckErrnoFailure(LxtExecve(ExecArgs[0], NULL, &ExecArgs[1]), E2BIG); // // Shorten the string and verify the exec call succeeds. // ExecArgs[0] = "/bin/true"; ExecArgs[1] = LongString; ExecArgs[2] = NULL; LongString[LARGE_STRING_SIZE - 2] = '\0'; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); } else { LxtCheckResult(ExecveWaitForChild()); } // // Allocate a huge argument array. // ExecArgsLong = malloc(MAX_STACK_SIZE * sizeof(char*)); if (ExecArgsLong == NULL) { LxtLogError("malloc of %zd size buffer", (MAX_STACK_SIZE * sizeof(char*))); goto ErrorExit; } for (Index = 0; Index < MAX_STACK_SIZE; Index += 1) { ExecArgsLong[Index] = "a"; } ExecArgsLong[MAX_STACK_SIZE - 1] = NULL; // // Create a child and verify that exec fails with too many arguments in the // command line or environment variable array. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgsLong[0] = "/bin/false"; ExecArgsLong[MAX_STACK_SIZE - 1] = NULL; LxtCheckErrnoFailure(LxtExecve(ExecArgsLong[0], ExecArgsLong, NULL), E2BIG); LxtCheckErrnoFailure(LxtExecve(ExecArgsLong[0], NULL, &ExecArgsLong[1]), E2BIG); // // Shorten the argument list and verify that the command succeeds. // ExecArgsLong[0] = "/bin/true"; ExecArgsLong[(MAX_STACK_SIZE / 4)] = NULL; LxtCheckErrno(LxtExecve(ExecArgsLong[0], ExecArgsLong, NULL)); } else { LxtCheckResult(ExecveWaitForChild()); } // // Test an empty command line array. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = NULL; LxtCheckErrno(LxtExecve("/bin/echo", ExecArgs, NULL)); // // The parent waits for the child to exit with SIGABRT. // } else { LxtLogInfo("Waiting for child to exit"); wait(&Status); LxtLogInfo("Status %d", Status); LxtCheckTrue((WIFSIGNALED(Status)) && (WTERMSIG(Status) == SIGABRT)); LxtLogInfo("Child exited with SIGABRT"); } // // Test a null command line pointer. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { LxtCheckErrno(LxtExecve("/bin/echo", NULL, NULL)); // // The parent waits for the child to exit SIGABRT. // } else { LxtLogInfo("Waiting for child to exit"); wait(&Status); LxtLogInfo("Status %d", Status); LxtCheckTrue((WIFSIGNALED(Status)) && (WTERMSIG(Status) == SIGABRT)); LxtLogInfo("Child exited with SIGABRT"); } // // Check that executing a directory fails with the expected error code. // mkdir(LXT_EXECV_TEST_DIRECTORY, 0777); ExecArgs[0] = LXT_EXECV_TEST_DIRECTORY; ExecArgs[1] = NULL; LxtCheckErrnoFailure(LxtExecve(ExecArgs[0], ExecArgs, NULL), EACCES); // // Verify that exec with a NULL filename fails. // LxtCheckErrnoFailure(LxtExecve(NULL, NULL, NULL), EFAULT); ExecArgs[0] = "/bin/echo"; ExecArgs[1] = NULL; LxtCheckErrnoFailure(LxtExecve(NULL, ExecArgs, NULL), EFAULT); Result = LXT_RESULT_SUCCESS; ErrorExit: rmdir(LXT_EXECV_TEST_DIRECTORY); if (LongString != NULL) { free(LongString); } if (ExecArgsLong != NULL) { free(ExecArgsLong); } if (ChildPid == 0) { _exit(Result); } return Result; } int ExecveVariationSingle(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int Result; LxtLogInfo("Forking single child"); LxtCheckResult(ChildPid = fork()); // // The child process executes the validation program. // if (ChildPid == 0) { LxtCheckResult(ExecveExecValidate(WSL_UNIT_TEST_BINARY)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(ExecveWaitForChild()); } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int ExecveVariationMultipleWithThreads(PLXT_ARGS Args) /*++ --*/ { #define NUM_CHILDREN 32 int ChildPid; int ProcessIndex; int Result; // // Launch all child processes. // for (ProcessIndex = 0; ProcessIndex < NUM_CHILDREN; ProcessIndex += 1) { LxtLogInfo("Forking child (#%d)", ProcessIndex); LxtCheckResult(ChildPid = fork()); // // In the child, create worker threads and then exec the validation // step from the leader. // if (ChildPid == 0) { pthread_t Thread; int ThreadCount; int ThreadIndex; ThreadCount = ProcessIndex + 1; LxtLogInfo("Creating %d thread(s) for PID %d", ThreadCount, getpid()); for (ThreadIndex = 0; ThreadIndex < ThreadCount; ThreadIndex += 1) { LxtCheckResultError(pthread_create(&Thread, NULL, ExecveWorkerThread, NULL)); } // // Sleep for 100ms and then execute the validation step. // usleep(100000); LxtCheckResult(ExecveExecValidate(WSL_UNIT_TEST_BINARY)); } } // // Launch again, this time calling exec from the non-leader thread. // for (ProcessIndex = 0; ProcessIndex < NUM_CHILDREN; ProcessIndex += 1) { LxtLogInfo("Forking child (#%d)", ProcessIndex); LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { pthread_t Thread; int ThreadCount; int ThreadIndex; ThreadCount = ProcessIndex + 1; LxtLogInfo("Creating %d thread(s) for PID %d", ThreadCount, getpid()); for (ThreadIndex = 0; ThreadIndex < ThreadCount; ThreadIndex += 1) { LxtCheckResultError(pthread_create(&Thread, NULL, ExecveWorkerThread2, Args)); } // // Continuously sleep for 100ms. // for (;;) { usleep(100000); } } } // // Wait for all child processes to exit. // LxtLogInfo("Waiting for children to exit"); for (ProcessIndex = 0; ProcessIndex < (2 * NUM_CHILDREN); ProcessIndex += 1) { LxtCheckResult(ExecveWaitForChild()); LxtLogInfo("Child exited (#%d)", ProcessIndex); } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int ExecveWaitForChild(void) /*++ --*/ { int Result; int Status; wait(&Status); LxtCheckTrue((WIFEXITED(Status)) && (WEXITSTATUS(Status) == 0)); Result = 0; ErrorExit: return Result; } void* ExecveWorkerThread(void* Arg) /*++ --*/ { // // Continuously sleep for 100ms. // for (;;) { usleep(100000); } return NULL; } void* ExecveWorkerThread2(void* Arg) /*++ --*/ { int Result; PLXT_ARGS Args = (PLXT_ARGS)Arg; // // Sleep for 100ms and then execute the validation step. // usleep(100000); LxtCheckResult(ExecveExecValidate(WSL_UNIT_TEST_BINARY)); ErrorExit: return (void*)(long)Result; }