699 lines
33 KiB
C
699 lines
33 KiB
C
// Native smoke test for posixsrv discovery, stable PID registration, and bounded transactions.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <ntlite/module_loader.h>
|
|
#include <ntlite/posix_service.h>
|
|
#include <ntlite/process_loader.h>
|
|
#include <ntlite/path.h>
|
|
#include <ntlite/resource.h>
|
|
#include <ntlite/security.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define CHILD_ARGUMENT "--pid-child"
|
|
|
|
// Writes one test diagnostic through the native console.
|
|
static void print(const char* text) {
|
|
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
|
|
(void)SysWriteConsole(&request);
|
|
}
|
|
|
|
// Terminates the smoke test with one visible failure reason.
|
|
static void fail(const char* text) {
|
|
print("posix_service_smoke: ");
|
|
print(text);
|
|
print("\n");
|
|
for (;;) SysExitProcess(1);
|
|
}
|
|
|
|
// Returns whether the current command line contains one ASCII token.
|
|
static bool command_contains(const char* token) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !token)
|
|
return false;
|
|
size_t tokenLength = strlen(token);
|
|
if (tokenLength == 0 || tokenLength > peb->CommandLine.Length) return false;
|
|
for (size_t i = 0; i + tokenLength <= peb->CommandLine.Length; ++i) {
|
|
if (memcmp(peb->CommandLine.Data + i, token, tokenLength) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Resolves /Services/posix through RSSRV and connects one independent client endpoint.
|
|
static KSTATUS connect_posixsrv(SYS_HANDLE* endpointOut) {
|
|
if (!endpointOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
*endpointOut = SYS_HANDLE_INVALID;
|
|
|
|
SYS_HANDLE resource = SYS_HANDLE_INVALID;
|
|
KSTATUS status = NtlResourceConnect(&resource);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_HANDLE listener = SYS_HANDLE_INVALID;
|
|
status = NtlResourceLookup(resource, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH),
|
|
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, &listener);
|
|
(void)NtlResourceClose(resource);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_CONNECT_PORT_REQUEST connect;
|
|
memset(&connect, 0, sizeof(connect));
|
|
connect.Size = sizeof(connect);
|
|
connect.ListenerHandle = listener;
|
|
connect.Flags = SYS_PORT_CONNECT_FLAG_NONE;
|
|
connect.EndpointHandleOut = endpointOut;
|
|
status = SysConnectPort(&connect);
|
|
(void)SysCloseHandle(listener);
|
|
return status;
|
|
}
|
|
|
|
// Performs one fixed-size request with optional capability transfer and validates reply framing.
|
|
static KSTATUS call_posixsrv_exchange(SYS_HANDLE endpoint,
|
|
const NTL_POSIX_REQUEST* request,
|
|
const SYS_PORT_HANDLE_TRANSFER* requestHandles,
|
|
uint32_t requestHandleCount,
|
|
SYS_HANDLE* replyHandlesOut,
|
|
uint32_t replyHandleCapacity,
|
|
uint32_t expectedReplyHandleCount,
|
|
NTL_POSIX_REPLY* replyOut) {
|
|
if (endpoint == SYS_HANDLE_INVALID || !request || !replyOut ||
|
|
expectedReplyHandleCount > replyHandleCapacity ||
|
|
(replyHandleCapacity != 0 && !replyHandlesOut))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
memset(replyOut, 0, sizeof(*replyOut));
|
|
if (replyHandlesOut) {
|
|
for (uint32_t i = 0; i < replyHandleCapacity; ++i)
|
|
replyHandlesOut[i] = SYS_HANDLE_INVALID;
|
|
}
|
|
|
|
SYS_PORT_MESSAGE_INFO info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.Size = sizeof(info);
|
|
|
|
SYS_CALL_PORT_REQUEST call;
|
|
memset(&call, 0, sizeof(call));
|
|
call.Size = sizeof(call);
|
|
call.EndpointHandle = endpoint;
|
|
call.Flags = SYS_PORT_FLAG_NONE;
|
|
call.RequestPayload = request;
|
|
call.RequestLength = sizeof(*request);
|
|
call.RequestHandles = requestHandles;
|
|
call.RequestHandleCount = requestHandleCount;
|
|
call.ReplyPayload = replyOut;
|
|
call.ReplyCapacity = sizeof(*replyOut);
|
|
call.ReplyHandlesOut = replyHandlesOut;
|
|
call.ReplyHandleCapacity = replyHandleCapacity;
|
|
call.ReplyInfoOut = &info;
|
|
call.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
KSTATUS status = SysCallPort(&call);
|
|
if (!KSUCCESS(status)) return status;
|
|
uint32_t expectedHandles = KSUCCESS(replyOut->Status) ? expectedReplyHandleCount : 0u;
|
|
if (info.Type != SYS_PORT_MESSAGE_REPLY || info.HandleCount != expectedHandles ||
|
|
info.PayloadLength != sizeof(*replyOut) || replyOut->Size != sizeof(*replyOut) ||
|
|
replyOut->Version != NTL_POSIX_PROTOCOL_VERSION ||
|
|
replyOut->Operation != request->Operation ||
|
|
replyOut->Flags != NTL_POSIX_REQUEST_FLAG_NONE) {
|
|
if (replyHandlesOut) {
|
|
uint32_t closeCount = info.HandleCount < replyHandleCapacity ?
|
|
info.HandleCount : replyHandleCapacity;
|
|
for (uint32_t i = 0; i < closeCount; ++i) {
|
|
if (replyHandlesOut[i] != SYS_HANDLE_INVALID)
|
|
(void)SysCloseHandle(replyHandlesOut[i]);
|
|
replyHandlesOut[i] = SYS_HANDLE_INVALID;
|
|
}
|
|
}
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Performs one request that expects no capabilities in the reply.
|
|
static KSTATUS call_posixsrv(SYS_HANDLE endpoint,
|
|
const NTL_POSIX_REQUEST* request,
|
|
const SYS_PORT_HANDLE_TRANSFER* handles,
|
|
uint32_t handleCount,
|
|
NTL_POSIX_REPLY* replyOut) {
|
|
return call_posixsrv_exchange(endpoint, request, handles, handleCount,
|
|
(SYS_HANDLE*)0, 0, 0, replyOut);
|
|
}
|
|
|
|
// Constructs the common fixed-size request header used by the service protocol.
|
|
static NTL_POSIX_REQUEST make_request(uint32_t operation) {
|
|
NTL_POSIX_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Version = NTL_POSIX_PROTOCOL_VERSION;
|
|
request.Operation = operation;
|
|
request.Flags = NTL_POSIX_REQUEST_FLAG_NONE;
|
|
return request;
|
|
}
|
|
|
|
// Registers the current native process through the accepted Port peer capability.
|
|
static KSTATUS register_current_process(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_REGISTER_PROCESS);
|
|
size_t cwdRequired = 0;
|
|
KSTATUS status = NtlGetCurrentDirectory(request.Path, sizeof(request.Path), &cwdRequired);
|
|
if (!KSUCCESS(status)) return status;
|
|
request.PathLength = (uint32_t)strlen(request.Path);
|
|
return call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, replyOut);
|
|
}
|
|
|
|
// Provides a valid user APC target for protocol-level thread registration tests.
|
|
static void __stdcall signal_probe_apc(void* context, void* argument1, void* argument2) {
|
|
(void)context;
|
|
(void)argument1;
|
|
(void)argument2;
|
|
}
|
|
|
|
// Registers the current thread by transferring a restricted self capability.
|
|
static KSTATUS register_current_thread(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) {
|
|
SYS_HANDLE self = SYS_HANDLE_INVALID;
|
|
KSTATUS status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY,
|
|
&self);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_PORT_HANDLE_TRANSFER transfer;
|
|
memset(&transfer, 0, sizeof(transfer));
|
|
transfer.Handle = self;
|
|
transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_THREAD_RIGHT_NOTIFY;
|
|
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
|
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_REGISTER_THREAD);
|
|
request.SignalApcRoutine = (uint64_t)(uintptr_t)signal_probe_apc;
|
|
status = call_posixsrv(endpoint, &request, &transfer, 1u, replyOut);
|
|
(void)SysCloseHandle(self);
|
|
return status;
|
|
}
|
|
|
|
// Unregisters the calling thread using the Port sender identity.
|
|
static KSTATUS unregister_current_thread(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_UNREGISTER_THREAD);
|
|
KSTATUS status = call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0,
|
|
replyOut);
|
|
if (!KSUCCESS(status)) return status;
|
|
return replyOut->Status;
|
|
}
|
|
|
|
// Creates one handle-less server-owned description and returns its stable identity.
|
|
static uint64_t create_synthetic_description(SYS_HANDLE endpoint,
|
|
uint32_t type,
|
|
uint32_t statusFlags,
|
|
NTL_POSIX_REPLY* replyOut) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_CREATE_OPEN_DESCRIPTION);
|
|
request.OpenDescriptionType = type;
|
|
request.OpenStatusFlags = statusFlags;
|
|
if (!KSUCCESS(call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0,
|
|
replyOut)) || !KSUCCESS(replyOut->Status) ||
|
|
replyOut->OpenDescriptionId == 0 || replyOut->SharedOpenDescription == 0)
|
|
fail("shared description creation failed");
|
|
return replyOut->OpenDescriptionId;
|
|
}
|
|
|
|
// Releases one server-owned description held by the current registered process.
|
|
static KSTATUS release_description(SYS_HANDLE endpoint, uint64_t descriptionId,
|
|
NTL_POSIX_REPLY* replyOut) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION);
|
|
request.OpenDescriptionId = descriptionId;
|
|
KSTATUS status = call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0,
|
|
replyOut);
|
|
if (!KSUCCESS(status)) return status;
|
|
return replyOut->Status;
|
|
}
|
|
|
|
typedef struct VFORK_SUBSTRATE_CONTEXT {
|
|
SYS_HANDLE Endpoint;
|
|
uint32_t ParentPid;
|
|
uint32_t ChildPid;
|
|
uint64_t Token;
|
|
uint64_t FdToken;
|
|
uint64_t DescriptionId;
|
|
volatile uint32_t Result;
|
|
} VFORK_SUBSTRATE_CONTEXT;
|
|
|
|
// Attaches one native thread to a borrowed logical process and verifies routed FD ownership.
|
|
static void vfork_substrate_thread(uintptr_t argument) {
|
|
VFORK_SUBSTRATE_CONTEXT* context = (VFORK_SUBSTRATE_CONTEXT*)argument;
|
|
if (!context) SysExitThread(1);
|
|
|
|
SYS_HANDLE self = SYS_HANDLE_INVALID;
|
|
KSTATUS status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE |
|
|
SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, &self);
|
|
if (!KSUCCESS(status)) { context->Result = 1u; SysExitThread(1); }
|
|
SYS_PORT_HANDLE_TRANSFER transfer;
|
|
memset(&transfer, 0, sizeof(transfer));
|
|
transfer.Handle = self;
|
|
transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE |
|
|
SYS_THREAD_RIGHT_NOTIFY;
|
|
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_VFORK_ATTACH);
|
|
request.TargetPosixPid = context->ChildPid;
|
|
request.VforkToken = context->Token;
|
|
request.SignalApcRoutine = (uint64_t)(uintptr_t)signal_probe_apc;
|
|
NTL_POSIX_REPLY reply;
|
|
status = call_posixsrv(context->Endpoint, &request, &transfer, 1u, &reply);
|
|
(void)SysCloseHandle(self);
|
|
if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.PosixPid != context->ChildPid ||
|
|
reply.ProcessState != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED ||
|
|
reply.VforkToken != context->Token) {
|
|
context->Result = 2u;
|
|
SysExitThread(2);
|
|
}
|
|
|
|
request = make_request(NTL_POSIX_OP_GET_PROCESS_ATTRIBUTES);
|
|
status = call_posixsrv(context->Endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply);
|
|
if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.PosixPid != context->ChildPid ||
|
|
reply.ParentPosixPid != context->ParentPid ||
|
|
reply.ProcessState != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED) {
|
|
context->Result = 3u;
|
|
SysExitThread(3);
|
|
}
|
|
|
|
request = make_request(NTL_POSIX_OP_IMPORT_FD_INHERITANCE);
|
|
request.FdInheritanceToken = context->FdToken;
|
|
request.FdInheritanceIndex = 0;
|
|
status = call_posixsrv_exchange(context->Endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0,
|
|
(SYS_HANDLE*)0, 0, 0, &reply);
|
|
if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.FdInheritanceCount != 1u ||
|
|
reply.FdDescriptionTotal != 1u || reply.FdDescriptionCount != 1u ||
|
|
reply.FdManifest[0].Descriptor != 7 ||
|
|
reply.FdManifest[0].OpenDescriptionId != context->DescriptionId ||
|
|
reply.FdDescriptions[0].OpenDescriptionId != context->DescriptionId) {
|
|
context->Result = 4u;
|
|
SysExitThread(4);
|
|
}
|
|
|
|
request = make_request(NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION);
|
|
request.OpenDescriptionId = context->DescriptionId;
|
|
status = call_posixsrv(context->Endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply);
|
|
if (!KSUCCESS(status) || !KSUCCESS(reply.Status)) {
|
|
context->Result = 5u;
|
|
SysExitThread(5);
|
|
}
|
|
|
|
context->Result = 0u;
|
|
SysExitThread(0);
|
|
}
|
|
|
|
// Exercises the server-owned borrowed-process record and sender-TID logical routing.
|
|
static void test_vfork_substrate(SYS_HANDLE endpoint,
|
|
uint32_t parentPid,
|
|
uint64_t descriptionId,
|
|
uint32_t baselineProcesses,
|
|
uint32_t baselineThreads) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_CREATE_FD_INHERITANCE);
|
|
request.FdInheritanceCount = 1u;
|
|
request.FdManifest[0].Descriptor = 7;
|
|
request.FdManifest[0].OpenDescriptionId = descriptionId;
|
|
NTL_POSIX_REPLY reply;
|
|
if (!KSUCCESS(call_posixsrv(endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.FdInheritanceToken == 0)
|
|
fail("vfork descriptor snapshot failed");
|
|
uint64_t fdToken = reply.FdInheritanceToken;
|
|
|
|
request = make_request(NTL_POSIX_OP_VFORK_BEGIN);
|
|
request.FdInheritanceToken = fdToken;
|
|
if (!KSUCCESS(call_posixsrv(endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.SpawnedPosixPid == 0 || reply.VforkToken == 0 ||
|
|
reply.RegisteredProcesses != baselineProcesses + 1u)
|
|
fail("vfork borrowed child creation failed");
|
|
|
|
VFORK_SUBSTRATE_CONTEXT context;
|
|
memset(&context, 0, sizeof(context));
|
|
context.Endpoint = endpoint;
|
|
context.ParentPid = parentPid;
|
|
context.ChildPid = reply.SpawnedPosixPid;
|
|
context.Token = reply.VforkToken;
|
|
context.FdToken = fdToken;
|
|
context.DescriptionId = descriptionId;
|
|
context.Result = UINT32_MAX;
|
|
|
|
SYS_HANDLE thread = SYS_HANDLE_INVALID;
|
|
uint32_t tid = 0;
|
|
SYS_CREATE_THREAD_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Entry = (uintptr_t)vfork_substrate_thread;
|
|
create.Argument = (uintptr_t)&context;
|
|
create.Priority = 1u;
|
|
create.ThreadHandleOut = &thread;
|
|
create.ThreadIdOut = &tid;
|
|
if (!KSUCCESS(SysCreateThread(&create)) || thread == SYS_HANDLE_INVALID || tid == 0)
|
|
fail("vfork borrowed native thread creation failed");
|
|
if (!KSUCCESS(SysWait(thread, SYS_WAIT_FLAG_NONE)))
|
|
fail("vfork borrowed native thread wait failed");
|
|
(void)SysCloseHandle(thread);
|
|
if (context.Result != 0u)
|
|
fail("vfork borrowed thread routing failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_VFORK_CANCEL);
|
|
request.TargetPosixPid = context.ChildPid;
|
|
request.VforkToken = context.Token;
|
|
if (!KSUCCESS(call_posixsrv(endpoint, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.RegisteredProcesses != baselineProcesses ||
|
|
reply.RegisteredThreads != baselineThreads)
|
|
fail("vfork borrowed child cleanup failed");
|
|
}
|
|
|
|
// Waits for the process monitor to retire one process and all of its shared holders.
|
|
static void wait_registry_counts(SYS_HANDLE endpoint,
|
|
uint32_t expectedProcesses,
|
|
uint32_t expectedDescriptions) {
|
|
for (uint32_t attempt = 0; attempt < 4096u; ++attempt) {
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_QUERY);
|
|
NTL_POSIX_REPLY reply;
|
|
if (!KSUCCESS(call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status))
|
|
fail("registry query failed");
|
|
if (reply.RegisteredProcesses == expectedProcesses &&
|
|
reply.RegisteredOpenDescriptions == expectedDescriptions)
|
|
return;
|
|
(void)SysYield();
|
|
}
|
|
fail("exited process registry state was not retired");
|
|
}
|
|
|
|
// Runs a second native process so the server must allocate a distinct stable POSIX PID.
|
|
static uint32_t spawn_child_posix_pid(uint32_t* nativePidOut) {
|
|
if (nativePidOut) *nativePidOut = 0;
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION ||
|
|
!peb->ImagePath.Data || peb->ImagePath.Length == 0 || peb->ImagePath.Length > SYS_PATH_MAX)
|
|
fail("invalid self image path");
|
|
|
|
char path[SYS_PATH_MAX + 1u];
|
|
memcpy(path, peb->ImagePath.Data, peb->ImagePath.Length);
|
|
path[peb->ImagePath.Length] = '\0';
|
|
|
|
char command[SYS_PATH_MAX + 32u];
|
|
size_t pathLength = strlen(path);
|
|
size_t argumentLength = strlen(CHILD_ARGUMENT);
|
|
if (pathLength + 1u + argumentLength + 1u > sizeof(command))
|
|
fail("child command line too long");
|
|
memcpy(command, path, pathLength);
|
|
command[pathLength] = ' ';
|
|
memcpy(command + pathLength + 1u, CHILD_ARGUMENT, argumentLength + 1u);
|
|
|
|
SYS_HANDLE process = SYS_HANDLE_INVALID;
|
|
NTL_CREATE_SYSTEM_PROCESS_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
create.ImageFile = SYS_HANDLE_INVALID;
|
|
create.Path = SysSvFromCstr(path);
|
|
create.CommandLine = SysSvFromCstr(command);
|
|
create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
create.ResolveModuleName = NtlResolveSystemModuleName;
|
|
NTL_POSIX_BOOTSTRAP_DATA bootstrap;
|
|
memset(&bootstrap, 0, sizeof(bootstrap));
|
|
bootstrap.Magic = NTL_POSIX_BOOTSTRAP_MAGIC;
|
|
bootstrap.Version = NTL_POSIX_BOOTSTRAP_VERSION;
|
|
bootstrap.Size = sizeof(bootstrap);
|
|
create.StartupData = &bootstrap;
|
|
create.StartupDataSize = sizeof(bootstrap);
|
|
create.ProcessHandleOut = &process;
|
|
KSTATUS status = NtlCreateSystemProcess(&create);
|
|
if (!KSUCCESS(status)) fail("child process creation failed");
|
|
|
|
uint32_t nativePid = 0;
|
|
if (!KSUCCESS(SysGetProcessPid(process, &nativePid)))
|
|
fail("child native pid query failed");
|
|
if (nativePidOut) *nativePidOut = nativePid;
|
|
|
|
if (!KSUCCESS(SysWait(process, SYS_WAIT_FLAG_NONE)))
|
|
fail("child process wait failed");
|
|
int32_t exitCode = 0;
|
|
if (!KSUCCESS(SysGetProcessExitCode(process, &exitCode)))
|
|
fail("child exit query failed");
|
|
(void)SysCloseHandle(process);
|
|
if (exitCode <= 0) fail("child returned invalid POSIX pid");
|
|
return (uint32_t)exitCode;
|
|
}
|
|
|
|
// Registers and returns the stable PID when running as the self-spawned child.
|
|
static void run_child_mode(void) {
|
|
NTL_POSIX_BOOTSTRAP_DATA bootstrap;
|
|
size_t bootstrapSize = 0;
|
|
if (!KSUCCESS(NtlGetProcessStartupData(&bootstrap, sizeof(bootstrap), &bootstrapSize)) ||
|
|
bootstrapSize != sizeof(bootstrap) || bootstrap.Magic != NTL_POSIX_BOOTSTRAP_MAGIC ||
|
|
bootstrap.Version != NTL_POSIX_BOOTSTRAP_VERSION || bootstrap.Size != sizeof(bootstrap) ||
|
|
bootstrap.Reserved != 0 || bootstrap.Token != 0 || bootstrap.PosixPid != 0 ||
|
|
bootstrap.Generation != 0 || bootstrap.FdInheritanceToken != 0)
|
|
for (;;) SysExitProcess(4);
|
|
|
|
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(connect_posixsrv(&endpoint)))
|
|
for (;;) SysExitProcess(2);
|
|
NTL_POSIX_REPLY reply;
|
|
if (!KSUCCESS(register_current_process(endpoint, &reply)) || !KSUCCESS(reply.Status) ||
|
|
reply.PosixPid == 0 || reply.PosixPid > 0x7FFFFFFFu ||
|
|
reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING)
|
|
for (;;) SysExitProcess(3);
|
|
uint32_t pid = reply.PosixPid;
|
|
NTL_POSIX_REPLY descriptionReply;
|
|
uint64_t leakedDescription = create_synthetic_description(
|
|
endpoint, NTL_POSIX_OPEN_TYPE_DEV_NULL, NTL_POSIX_OPEN_STATUS_WRONLY,
|
|
&descriptionReply);
|
|
NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared =
|
|
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
|
|
if (leakedDescription == 0 || !shared || shared->DescriptionId != leakedDescription ||
|
|
shared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
|
|
shared->Version != NTL_POSIX_SHARED_OPEN_VERSION)
|
|
for (;;) SysExitProcess(5);
|
|
// Intentionally leak the holder; the server's native-process monitor must reclaim it.
|
|
(void)SysDisconnectPort(endpoint);
|
|
(void)SysCloseHandle(endpoint);
|
|
for (;;) SysExitProcess((int32_t)pid);
|
|
}
|
|
|
|
// Exercises service discovery, stable identity, protocol versions, and transaction boundaries.
|
|
int main(void) {
|
|
if (command_contains(CHILD_ARGUMENT)) run_child_mode();
|
|
|
|
print("posix_service_smoke: start\n");
|
|
|
|
SYS_HANDLE first = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(connect_posixsrv(&first))) fail("first connect failed");
|
|
|
|
SYS_SECURITY_CONTEXT_INFO security;
|
|
memset(&security, 0, sizeof(security));
|
|
security.Size = sizeof(security);
|
|
if (!KSUCCESS(SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security)))
|
|
fail("security query failed");
|
|
|
|
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_QUERY);
|
|
NTL_POSIX_REPLY reply;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status))
|
|
fail("query failed");
|
|
if (reply.ClientProcessId != SysGetCurrentPid() || reply.ClientSecurityId != security.SecurityId ||
|
|
reply.ActiveTransactions != 0 || reply.TransactionCapacity != NTL_POSIX_TRANSACTION_CAPACITY ||
|
|
reply.PosixPid != 0)
|
|
fail("pre-registration identity mismatch");
|
|
|
|
if (!KSUCCESS(register_current_process(first, &reply)) || !KSUCCESS(reply.Status))
|
|
fail("process registration failed");
|
|
uint32_t stablePid = reply.PosixPid;
|
|
if (stablePid == 0 || stablePid == SysGetCurrentPid() || reply.NativeGeneration != 1u ||
|
|
reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING)
|
|
fail("stable process identity mismatch");
|
|
if (reply.UserId != 0 || reply.EffectiveUserId != 0 || reply.GroupId != 0 ||
|
|
reply.EffectiveGroupId != 0 || reply.ProcessGroupId != stablePid ||
|
|
reply.SessionId != stablePid || reply.SupplementaryGroupCount != 0 ||
|
|
reply.NameLength != 4u || strcmp(reply.Name, "root") != 0)
|
|
fail("initial shared process attributes mismatch");
|
|
char registeredCwd[SYS_PATH_MAX + 1u];
|
|
size_t registeredCwdRequired = 0;
|
|
if (!KSUCCESS(NtlGetCurrentDirectory(registeredCwd, sizeof(registeredCwd), ®isteredCwdRequired)) ||
|
|
reply.CurrentDirectoryLength != strlen(registeredCwd) ||
|
|
strcmp(reply.CurrentDirectory, registeredCwd) != 0)
|
|
fail("registered cwd mismatch");
|
|
|
|
uint32_t baselineProcesses = reply.RegisteredProcesses;
|
|
uint32_t baselineDescriptions = reply.RegisteredOpenDescriptions;
|
|
uint32_t baselineThreads = reply.RegisteredThreads;
|
|
|
|
if (!KSUCCESS(register_current_thread(first, &reply)) || !KSUCCESS(reply.Status) ||
|
|
reply.RegisteredThreads != baselineThreads + 1u)
|
|
fail("thread registration failed");
|
|
if (!KSUCCESS(register_current_thread(first, &reply)) || !KSUCCESS(reply.Status) ||
|
|
reply.RegisteredThreads != baselineThreads + 1u)
|
|
fail("duplicate thread registration was not idempotent");
|
|
if (!KSUCCESS(unregister_current_thread(first, &reply)) ||
|
|
reply.RegisteredThreads != baselineThreads)
|
|
fail("thread unregister failed");
|
|
if (unregister_current_thread(first, &reply) != NTLSTATUS_NOT_FOUND)
|
|
fail("duplicate thread unregister was accepted");
|
|
|
|
NTL_POSIX_REPLY descriptionReply;
|
|
uint64_t descriptionId = create_synthetic_description(
|
|
first, NTL_POSIX_OPEN_TYPE_DEV_NULL, NTL_POSIX_OPEN_STATUS_WRONLY,
|
|
&descriptionReply);
|
|
if (descriptionReply.RegisteredOpenDescriptions != baselineDescriptions + 1u ||
|
|
descriptionReply.OpenDescriptionType != NTL_POSIX_OPEN_TYPE_DEV_NULL ||
|
|
descriptionReply.OpenStatusFlags != NTL_POSIX_OPEN_STATUS_WRONLY)
|
|
fail("shared description registry mismatch");
|
|
NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared =
|
|
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
|
|
if (!shared || shared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
|
|
shared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
|
|
shared->Type != NTL_POSIX_OPEN_TYPE_DEV_NULL ||
|
|
shared->DescriptionId != descriptionId ||
|
|
(uint32_t)shared->StatusFlags != NTL_POSIX_OPEN_STATUS_WRONLY)
|
|
fail("shared description pointer mismatch");
|
|
|
|
request = make_request(NTL_POSIX_OP_SET_OPEN_STATUS);
|
|
request.OpenDescriptionId = descriptionId;
|
|
request.OpenStatusFlags = NTL_POSIX_OPEN_STATUS_WRONLY | NTL_POSIX_OPEN_STATUS_NONBLOCK;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &descriptionReply)) ||
|
|
!KSUCCESS(descriptionReply.Status) || descriptionReply.OpenDescriptionId != descriptionId ||
|
|
descriptionReply.OpenStatusFlags != request.OpenStatusFlags ||
|
|
(uint32_t)shared->StatusFlags != request.OpenStatusFlags)
|
|
fail("server-serialized status update failed");
|
|
|
|
test_vfork_substrate(first, stablePid, descriptionId,
|
|
baselineProcesses, baselineThreads);
|
|
|
|
if (!KSUCCESS(release_description(first, descriptionId, &descriptionReply)) ||
|
|
descriptionReply.RegisteredOpenDescriptions != baselineDescriptions)
|
|
fail("shared description release failed");
|
|
if (release_description(first, descriptionId, &descriptionReply) != NTLSTATUS_NOT_FOUND)
|
|
fail("duplicate shared description release was accepted");
|
|
|
|
request = make_request(NTL_POSIX_OP_CREATE_PIPE);
|
|
SYS_HANDLE pipeHandles[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
|
|
if (!KSUCCESS(call_posixsrv_exchange(first, &request,
|
|
(const SYS_PORT_HANDLE_TRANSFER*)0, 0,
|
|
pipeHandles, 2u, 2u, &descriptionReply)) ||
|
|
!KSUCCESS(descriptionReply.Status) ||
|
|
descriptionReply.RegisteredOpenDescriptions != baselineDescriptions + 2u ||
|
|
descriptionReply.OpenDescriptionType != NTL_POSIX_OPEN_TYPE_PIPE_READ ||
|
|
descriptionReply.OpenDescriptionType2 != NTL_POSIX_OPEN_TYPE_PIPE_WRITE ||
|
|
descriptionReply.OpenStatusFlags != NTL_POSIX_OPEN_STATUS_RDONLY ||
|
|
descriptionReply.OpenStatusFlags2 != NTL_POSIX_OPEN_STATUS_WRONLY ||
|
|
descriptionReply.OpenDescriptionId == 0 || descriptionReply.OpenDescriptionId2 == 0 ||
|
|
descriptionReply.OpenDescriptionId == descriptionReply.OpenDescriptionId2 ||
|
|
pipeHandles[0] == SYS_HANDLE_INVALID || pipeHandles[1] == SYS_HANDLE_INVALID)
|
|
fail("server-owned pipe creation failed");
|
|
|
|
NTL_POSIX_SHARED_OPEN_DESCRIPTION* pipeReadShared =
|
|
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
|
|
NTL_POSIX_SHARED_OPEN_DESCRIPTION* pipeWriteShared =
|
|
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription2;
|
|
SYS_PIPE_SHARED_STATE* pipeShared = pipeReadShared ?
|
|
(SYS_PIPE_SHARED_STATE*)(uintptr_t)pipeReadShared->SharedObjectData :
|
|
(SYS_PIPE_SHARED_STATE*)0;
|
|
if (!pipeReadShared || !pipeWriteShared || !pipeShared ||
|
|
pipeReadShared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
|
|
pipeWriteShared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
|
|
pipeReadShared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
|
|
pipeWriteShared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
|
|
pipeReadShared->DescriptionId != descriptionReply.OpenDescriptionId ||
|
|
pipeWriteShared->DescriptionId != descriptionReply.OpenDescriptionId2 ||
|
|
pipeReadShared->SharedObjectData != pipeWriteShared->SharedObjectData ||
|
|
pipeShared->Magic != SYS_PIPE_SHARED_MAGIC || pipeShared->Version != SYS_PIPE_SHARED_VERSION ||
|
|
pipeShared->Capacity != SYS_PIPE_BUFFER_CAPACITY ||
|
|
pipeShared->HeaderBytes != SYS_PIPE_SHARED_HEADER_BYTES ||
|
|
pipeShared->ReaderOpen == 0 || pipeShared->WriterOpen == 0)
|
|
fail("server-owned pipe shared state mismatch");
|
|
|
|
if (!KSUCCESS(release_description(first, descriptionReply.OpenDescriptionId, &reply)) ||
|
|
reply.RegisteredOpenDescriptions != baselineDescriptions + 1u ||
|
|
pipeShared->ReaderOpen == 0)
|
|
fail("pipe read master release invalidated live alias");
|
|
if (!KSUCCESS(SysCloseHandle(pipeHandles[0])) || pipeShared->ReaderOpen != 0)
|
|
fail("pipe read alias close did not publish reader shutdown");
|
|
pipeHandles[0] = SYS_HANDLE_INVALID;
|
|
|
|
if (!KSUCCESS(SysCloseHandle(pipeHandles[1])))
|
|
fail("pipe write alias close failed");
|
|
pipeHandles[1] = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(release_description(first, descriptionReply.OpenDescriptionId2, &reply)) ||
|
|
reply.RegisteredOpenDescriptions != baselineDescriptions)
|
|
fail("server-owned pipe description release failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_LOOKUP_USER);
|
|
request.LookupKind = NTL_POSIX_LOOKUP_BY_NAME;
|
|
memcpy(request.Name, "user", 5u);
|
|
request.NameLength = 4u;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.UserId != 1000u || reply.GroupId != 1000u ||
|
|
strcmp(reply.Name, "user") != 0)
|
|
fail("identity database lookup failed");
|
|
|
|
uint32_t childNativePid = 0;
|
|
uint32_t childPosixPid = spawn_child_posix_pid(&childNativePid);
|
|
if (childPosixPid == stablePid || childPosixPid == childNativePid)
|
|
fail("child POSIX identity was not independent");
|
|
wait_registry_counts(first, 1u, baselineDescriptions);
|
|
|
|
request = make_request(NTL_POSIX_OP_BEGIN_TRANSACTION);
|
|
request.TransactionKind = NTL_POSIX_TRANSACTION_SPAWN;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.TransactionId == 0 || reply.ActiveTransactions != 1)
|
|
fail("transaction begin failed");
|
|
uint32_t transactionId = reply.TransactionId;
|
|
|
|
SYS_HANDLE second = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(connect_posixsrv(&second))) fail("second connect failed");
|
|
if (!KSUCCESS(register_current_process(second, &reply)) || !KSUCCESS(reply.Status) ||
|
|
reply.PosixPid != stablePid || reply.NativeGeneration != 1u)
|
|
fail("same-process reconnect changed POSIX identity");
|
|
|
|
request = make_request(NTL_POSIX_OP_QUERY);
|
|
if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.ActiveTransactions != 1 || reply.PosixPid != stablePid)
|
|
fail("process transaction sharing failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_QUERY);
|
|
request.Version = NTL_POSIX_PROTOCOL_VERSION + 1u;
|
|
if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
reply.Status != NTLSTATUS_NOT_SUPPORTED)
|
|
fail("version rejection failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION);
|
|
request.TransactionId = transactionId;
|
|
if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.TransactionId != transactionId || reply.ActiveTransactions != 0)
|
|
fail("cross-connection transaction cancel failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION);
|
|
request.TransactionId = transactionId;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
reply.Status != NTLSTATUS_NOT_FOUND)
|
|
fail("unknown transaction rejection failed");
|
|
|
|
request = make_request(NTL_POSIX_OP_BEGIN_TRANSACTION);
|
|
request.TransactionKind = NTL_POSIX_TRANSACTION_EXEC;
|
|
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.TransactionId == 0 || reply.ActiveTransactions != 1)
|
|
fail("exec transaction begin failed");
|
|
uint32_t reconnectTransactionId = reply.TransactionId;
|
|
|
|
(void)SysDisconnectPort(first);
|
|
(void)SysCloseHandle(first);
|
|
(void)SysDisconnectPort(second);
|
|
(void)SysCloseHandle(second);
|
|
|
|
SYS_HANDLE third = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(connect_posixsrv(&third))) fail("reconnect failed");
|
|
if (!KSUCCESS(register_current_process(third, &reply)) || !KSUCCESS(reply.Status) ||
|
|
reply.PosixPid != stablePid || reply.NativeGeneration != 1u || reply.ActiveTransactions != 1)
|
|
fail("live process lost process-owned transaction after disconnect");
|
|
request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION);
|
|
request.TransactionId = reconnectTransactionId;
|
|
if (!KSUCCESS(call_posixsrv(third, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
|
|
!KSUCCESS(reply.Status) || reply.ActiveTransactions != 0)
|
|
fail("reconnected transaction cancel failed");
|
|
(void)SysDisconnectPort(third);
|
|
(void)SysCloseHandle(third);
|
|
|
|
print("posix_service_smoke: PASS\n");
|
|
return 0;
|
|
}
|