262 lines
10 KiB
C
262 lines
10 KiB
C
// Userspace prepared-process smoke test for inert construction and child bootstrap vectors.
|
|
|
|
#include <stdbool.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <ntlite/path.h>
|
|
#include <ntlite/process_loader.h>
|
|
#include <ntlite/resource.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#define TEST_STARTUP_WORD 0x31503550u
|
|
|
|
// Exits without relying on a hosted runtime.
|
|
static void exit_now(int code) {
|
|
for (;;) SysExitProcess(code);
|
|
}
|
|
|
|
// Compares one native string view with a C string.
|
|
static bool view_equals_cstr(SYS_STRING_VIEW view, const char* text) {
|
|
size_t length = strlen(text);
|
|
return view.Length == length && (!length || memcmp(view.Data, text, length) == 0);
|
|
}
|
|
|
|
// Detects the child instance through the diagnostic command line.
|
|
static bool is_child(void) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
static const char marker[] = "process-builder-child";
|
|
return peb && peb->CommandLine.Length == sizeof(marker) - 1u &&
|
|
memcmp(peb->CommandLine.Data, marker, sizeof(marker) - 1u) == 0;
|
|
}
|
|
|
|
// Verifies the serialized generic argv/envp vectors and opaque startup data.
|
|
static void verify_child_bootstrap(void) {
|
|
uint32_t count = 0;
|
|
SYS_STRING_VIEW entry;
|
|
if (NtlGetProcessArgumentCount(&count) != NTLSTATUS_SUCCESS || count != 3u) exit_now(20);
|
|
if (NtlGetProcessArgument(0, &entry) != NTLSTATUS_SUCCESS ||
|
|
!view_equals_cstr(entry, "process_builder_smoke")) exit_now(21);
|
|
if (NtlGetProcessArgument(1, &entry) != NTLSTATUS_SUCCESS ||
|
|
!view_equals_cstr(entry, "vector-child")) exit_now(22);
|
|
if (NtlGetProcessArgument(2, &entry) != NTLSTATUS_SUCCESS ||
|
|
!view_equals_cstr(entry, "arg two")) exit_now(23);
|
|
|
|
if (NtlGetProcessEnvironmentCount(&count) != NTLSTATUS_SUCCESS || count != 2u) exit_now(24);
|
|
if (NtlGetProcessEnvironment(0, &entry) != NTLSTATUS_SUCCESS ||
|
|
!view_equals_cstr(entry, "P5_TEST=builder")) exit_now(25);
|
|
if (NtlGetProcessEnvironment(1, &entry) != NTLSTATUS_SUCCESS ||
|
|
!view_equals_cstr(entry, "EMPTY=")) exit_now(26);
|
|
|
|
uint32_t startupWord = 0;
|
|
size_t startupSize = 0;
|
|
if (NtlGetProcessStartupData(&startupWord, sizeof(startupWord), &startupSize) != NTLSTATUS_SUCCESS ||
|
|
startupSize != sizeof(startupWord) || startupWord != TEST_STARTUP_WORD)
|
|
exit_now(27);
|
|
}
|
|
|
|
// Runs the child half and signals the inherited parent event after validation.
|
|
static void run_child(void) {
|
|
SYS_HANDLE event = SysGetStartupCapability("test.builder-event");
|
|
if (event == SYS_HANDLE_INVALID) exit_now(10);
|
|
verify_child_bootstrap();
|
|
if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11);
|
|
exit_now(0);
|
|
}
|
|
|
|
// Runs the parent half through prepared-process and convenience construction paths.
|
|
static int run_parent(void) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION)
|
|
return 1;
|
|
|
|
SYS_HANDLE imageFile = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlOpenFile(peb->ImagePath,
|
|
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_OPEN_EXISTING,
|
|
SYS_FILE_FLAG_NONE,
|
|
&imageFile);
|
|
if (!KSUCCESS(status)) return 2;
|
|
|
|
SYS_HANDLE event = SYS_HANDLE_INVALID;
|
|
status = SysCreateEvent(0, 0, &event);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(imageFile);
|
|
return 3;
|
|
}
|
|
|
|
NTL_PROCESS_STARTUP_CAPABILITY startupCapability;
|
|
memset(&startupCapability, 0, sizeof(startupCapability));
|
|
strcpy(startupCapability.Name, "test.builder-event");
|
|
startupCapability.SourceHandle = event;
|
|
startupCapability.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY;
|
|
startupCapability.DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
|
|
|
SYS_STRING_VIEW arguments[] = {
|
|
SYS_SV_LITERAL("process_builder_smoke"),
|
|
SYS_SV_LITERAL("vector-child"),
|
|
SYS_SV_LITERAL("arg two")
|
|
};
|
|
SYS_STRING_VIEW environment[] = {
|
|
SYS_SV_LITERAL("P5_TEST=builder"),
|
|
SYS_SV_LITERAL("EMPTY=")
|
|
};
|
|
uint32_t startupWord = TEST_STARTUP_WORD;
|
|
SYS_HANDLE child = SYS_HANDLE_INVALID;
|
|
|
|
NTL_CREATE_SYSTEM_PROCESS_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
request.ImageFile = imageFile;
|
|
request.Path = peb->ImagePath;
|
|
request.CommandLine = SYS_SV_LITERAL("process-builder-child");
|
|
request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
request.ResolveModuleName = NtlResolveSystemModuleName;
|
|
request.StartupCapabilities = &startupCapability;
|
|
request.StartupCapabilityCount = 1u;
|
|
request.Arguments.Entries = arguments;
|
|
request.Arguments.Count = (uint32_t)(sizeof(arguments) / sizeof(arguments[0]));
|
|
request.Environment.Entries = environment;
|
|
request.Environment.Count = (uint32_t)(sizeof(environment) / sizeof(environment[0]));
|
|
request.StartupData = &startupWord;
|
|
request.StartupDataSize = sizeof(startupWord);
|
|
request.ProcessHandleOut = &child;
|
|
// Exercise the public opaque preparation lifecycle without starting the child.
|
|
SYS_HANDLE systemRoot = SYS_HANDLE_INVALID;
|
|
status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &systemRoot);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 8;
|
|
}
|
|
NTL_PREPARE_PROCESS_REQUEST prepare;
|
|
memset(&prepare, 0, sizeof(prepare));
|
|
prepare.Size = sizeof(prepare);
|
|
prepare.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
prepare.RootHandle = systemRoot;
|
|
prepare.Path = peb->ImagePath;
|
|
prepare.CommandLine = request.CommandLine;
|
|
prepare.SecurityContext = request.SecurityContext;
|
|
prepare.ModuleRootHandle = systemRoot;
|
|
prepare.ModuleDirectory = SYS_SV_LITERAL("system");
|
|
prepare.ResolveModuleName = request.ResolveModuleName;
|
|
prepare.StartupCapabilities = request.StartupCapabilities;
|
|
prepare.StartupCapabilityCount = request.StartupCapabilityCount;
|
|
prepare.Arguments = request.Arguments;
|
|
prepare.Environment = request.Environment;
|
|
prepare.StartupData = request.StartupData;
|
|
prepare.StartupDataSize = request.StartupDataSize;
|
|
|
|
// Invalid requests never publish a partially constructed preparation.
|
|
NTL_PROCESS_PREPARATION* preparation = 0;
|
|
uint32_t savedSize = prepare.Size;
|
|
prepare.Size = 0;
|
|
status = NtlPrepareProcess(&prepare, imageFile, &preparation);
|
|
prepare.Size = savedSize;
|
|
if (status != NTLSTATUS_INVALID_PARAMETER || preparation != 0) {
|
|
(void)SysCloseHandle(systemRoot);
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 16;
|
|
}
|
|
status = NtlPrepareProcess(&prepare, imageFile, &preparation);
|
|
if (!KSUCCESS(status) || !preparation) {
|
|
(void)SysCloseHandle(systemRoot);
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 9;
|
|
}
|
|
SYS_HANDLE borrowed = SYS_HANDLE_INVALID;
|
|
uint32_t nativePid = 0;
|
|
status = NtlPreparedProcessGetHandle(preparation, &borrowed);
|
|
if (!KSUCCESS(status) || borrowed == SYS_HANDLE_INVALID ||
|
|
!KSUCCESS(SysGetProcessPid(borrowed, &nativePid)) || nativePid == 0) {
|
|
NtlAbortPreparedProcess(preparation);
|
|
(void)SysCloseHandle(systemRoot);
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 10;
|
|
}
|
|
NtlAbortPreparedProcess(preparation);
|
|
|
|
// A second preparation starts the child. Start consumes its allocation;
|
|
// callers must not also abort or close the previously borrowed handle.
|
|
preparation = 0;
|
|
status = NtlPrepareProcess(&prepare, imageFile, &preparation);
|
|
if (!KSUCCESS(status) || !preparation) {
|
|
(void)SysCloseHandle(systemRoot);
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 11;
|
|
}
|
|
status = NtlStartPreparedProcess(preparation, &child);
|
|
(void)SysCloseHandle(systemRoot);
|
|
if (!KSUCCESS(status) || child == SYS_HANDLE_INVALID) {
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 12;
|
|
}
|
|
status = SysWait(event, SYS_WAIT_FLAG_NONE);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysKillProcess(child, (int32_t)NTLSTATUS_PROCESS_KILLED);
|
|
(void)SysCloseHandle(child);
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 13;
|
|
}
|
|
status = SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
int32_t directExitCode = -1;
|
|
if (KSUCCESS(status)) status = SysGetProcessExitCode(child, &directExitCode);
|
|
(void)SysCloseHandle(child);
|
|
child = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(status) || directExitCode != 0) {
|
|
(void)SysCloseHandle(imageFile);
|
|
(void)SysCloseHandle(event);
|
|
return 14;
|
|
}
|
|
|
|
// Preserve coverage for NtlCreateSystemProcess's convenience wrapper.
|
|
(void)SysCloseHandle(event);
|
|
status = SysCreateEvent(0, 0, &event);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(imageFile);
|
|
return 15;
|
|
}
|
|
startupCapability.SourceHandle = event;
|
|
request.ProcessHandleOut = &child;
|
|
status = NtlCreateSystemProcess(&request);
|
|
(void)SysCloseHandle(imageFile);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(event);
|
|
return 4;
|
|
}
|
|
|
|
status = SysWait(event, SYS_WAIT_FLAG_NONE);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysKillProcess(child, (int32_t)NTLSTATUS_PROCESS_KILLED);
|
|
(void)SysCloseHandle(child);
|
|
(void)SysCloseHandle(event);
|
|
return 5;
|
|
}
|
|
status = SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(child);
|
|
(void)SysCloseHandle(event);
|
|
return 6;
|
|
}
|
|
|
|
int32_t exitCode = -1;
|
|
status = SysGetProcessExitCode(child, &exitCode);
|
|
(void)SysCloseHandle(child);
|
|
(void)SysCloseHandle(event);
|
|
if (!KSUCCESS(status) || exitCode != 0) return 7;
|
|
return 0;
|
|
}
|
|
|
|
// Exercises generic userspace process construction in parent and child roles.
|
|
int main(void) {
|
|
if (is_child()) run_child();
|
|
return run_parent();
|
|
}
|