WSL/test/linux/unit_tests/execve.c

620 lines
13 KiB
C
Raw Permalink Normal View History

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
execve.c
Abstract:
This file contains the execve tests.
--*/
#include "lxtcommon.h"
#include "unittests.h"
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/binfmts.h>
#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;
}