// Userspace prepared-process smoke test for inert construction and child bootstrap vectors. #include #include #include #include #include #include #include #include #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(); }