redo boot around an explicit system volume and fixed kernel/HAL images, redo volumes and filesystem paths around capabilities instead of drive numbers and kernel CWD state, add a capability/security model with derived and revocable handles and security contexts, add port IPC and rssrv to manage the userspace resource namespace and publish the system volume, move process, dll and path loading to userspace and add process builder syscalls, init now supervises rssrv and the session process, redo the syscall ABI around request structs and four arguments, add a volume manager and proper partition arrival/removal handling so FAT and NTFS mount dynamically, clean up the HAL/DDK boundary so drivers don't depend on HAL internals, save fpu state properly now, the serial driver doesn't hang on a missing port, fix AHCI startup races, fix timeout accounting, fix shared mmap writeback, more smoke tests

This commit is contained in:
maksw committed 2026-08-20 12:33:17 +02:00
1 parent 4add6789ba
commit f882d339fb
232 files changed
+20934 -8898

No files matched your search

+26 -7
View File
@@ -5,6 +5,8 @@
#include <stdbool.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/process_loader.h>
#define ALL_TESTS_INITIAL_CAPACITY 32u
@@ -14,7 +16,7 @@ typedef struct TEST_ENTRY {
// Writes one NUL-terminated string to the console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -199,7 +201,7 @@ static bool build_child_path(const char* dir, const char* name, char* out, size_
memcpy(out, dir, dirLen);
size_t pos = dirLen;
if (needSep) out[pos++] = '\\';
if (needSep) out[pos++] = '/';
memcpy(out + pos, name, nameLen);
out[total] = '\0';
return true;
@@ -211,8 +213,10 @@ static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY*
*countOut = 0;
uint32_t hDir = 0;
SYS_PATH_HANDLE_REQUEST openReq = { SysSvFromCstr(dir), &hDir };
uint32_t status = SysOpenDir(&openReq);
uint32_t status = NtlOpenDir(SysSvFromCstr(dir),
SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN,
SYS_DIR_FLAG_NONE,
&hDir);
if (status != NTLSTATUS_SUCCESS) return status;
TEST_ENTRY* entries = 0;
@@ -251,13 +255,28 @@ static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY*
// Runs one test process and returns the child's exit code through exitCodeOut.
static uint32_t run_test(const char* path, int32_t* exitCodeOut) {
uint32_t imageFile = SYS_HANDLE_INVALID;
uint32_t status = NtlOpenFile(SysSvFromCstr(path),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&imageFile);
if (status != NTLSTATUS_SUCCESS) return status;
uint32_t process = 0;
SYS_CREATE_PROCESS_REQUEST req;
NTL_CREATE_SYSTEM_PROCESS_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
req.ImageFile = imageFile;
req.Path = SysSvFromCstr(path);
req.CommandLine = SysSvFromCstr(path);
req.HandleOut = &process;
req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
req.ResolveModuleName = NtlResolveSystemModuleName;
req.ProcessHandleOut = &process;
uint32_t status = SysCreateProcess(&req);
status = NtlCreateSystemProcess(&req);
(void)SysCloseHandle(imageFile);
if (status != NTLSTATUS_SUCCESS) return status;
status = SysWait(process, SYS_WAIT_FLAG_NONE);
@@ -6,12 +6,13 @@
#include <string.h>
#include <intrin.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define EVENT_AUTO_RESET 0u
// Prints a C string to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
SysWriteConsole(&request);
}
@@ -37,26 +38,19 @@ static void print_u64(uint64_t value) {
static uint32_t open_test_file(uint32_t* handleOut) {
static const char* paths[] = {
"apc_return_smoke.exe",
"/apps/apc_return_smoke.exe",
"1:/apps/apc_return_smoke.exe",
"0:/apps/apc_return_smoke.exe",
"/SystemRoot/apps/apc_return_smoke.exe",
"async_apc.exe",
"/apps/async_apc.exe",
"1:/apps/async_apc.exe",
"0:/apps/async_apc.exe",
"/system/init.exe",
"0:/system/init.exe",
"1:/system/init.exe",
"/SystemRoot/apps/async_apc.exe",
"/SystemRoot/system/init.exe",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t handle = 0;
SYS_OPEN_FILE_REQUEST request = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&handle);
uint32_t status = SysOpenFile(&request);
uint32_t status = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&handle);
if (status == NTLSTATUS_SUCCESS) {
*handleOut = handle;
return NTLSTATUS_SUCCESS;
@@ -137,10 +131,11 @@ int main(void) {
SYS_ASYNC_FILE_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.FileHandle = file;
request.EventHandle = event;
request.Buffer = buffer;
request.Size = 512;
request.Length = 512;
request.Iosb = &iosb;
request.ApcRoutine = file_apc;
request.ApcContext = &state;
+13 -16
View File
@@ -6,39 +6,35 @@
#include <string.h>
#include <intrin.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define EVENT_AUTO_RESET 0u
// Prints the requested operation.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Opens the first available executable payload for async I/O.
// Tests are usually launched from /apps, so a bare "system/init.exe"
// resolves as /apps/system/init.exe and fails. Prefer the test binary itself,
// then fall back to absolute/drive-qualified paths.
// then fall back to canonical SystemRoot or other relative paths.
static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) {
static const char* paths[] = {
"async_apc.exe",
"/apps/async_apc.exe",
"1:/apps/async_apc.exe",
"0:/apps/async_apc.exe",
"/system/init.exe",
"0:/system/init.exe",
"1:/system/init.exe",
"/SystemRoot/apps/async_apc.exe",
"/SystemRoot/system/init.exe",
"system/init.exe",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*handleOut = h;
if (pathOut)
@@ -87,7 +83,7 @@ static void file_apc(void* context, SYS_IO_STATUS_BLOCK* iosb, uint64_t informat
st->Status = iosb ? iosb->Status : 0xFFFFFFFFu;
st->Information = information;
st->Delivered++;
do { SYS_PRINT_DEBUG_REQUEST req = { SYS_SV_LITERAL("async_apc: user APC callback ran\n") }; SysPrintDebug(&req); } while (0);
do { SYS_PRINT_DEBUG_REQUEST req = { sizeof(SYS_PRINT_DEBUG_REQUEST), SYS_SV_LITERAL("async_apc: user APC callback ran\n") }; SysPrintDebug(&req); } while (0);
}
// Runs the requested operation.
@@ -141,11 +137,12 @@ int main(void) {
SYS_ASYNC_FILE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.FileHandle = file;
req.EventHandle = event;
req.Offset = 0;
req.Buffer = buffer;
req.Size = 512;
req.Length = 512;
req.Iosb = &iosb;
req.ApcRoutine = file_apc;
req.ApcContext = &state;
+13 -16
View File
@@ -5,39 +5,35 @@
#include <stdlib.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define EVENT_AUTO_RESET 0u
// Prints the requested operation.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Opens the first available executable payload for async I/O.
// Tests are usually launched from /apps, so a bare "system/init.exe"
// resolves as /apps/system/init.exe and fails. Prefer the test binary itself,
// then fall back to absolute/drive-qualified paths.
// then fall back to canonical SystemRoot or other relative paths.
static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) {
static const char* paths[] = {
"async_cancel.exe",
"/apps/async_cancel.exe",
"1:/apps/async_cancel.exe",
"0:/apps/async_cancel.exe",
"/system/init.exe",
"0:/system/init.exe",
"1:/system/init.exe",
"/SystemRoot/apps/async_cancel.exe",
"/SystemRoot/system/init.exe",
"system/init.exe",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*handleOut = h;
if (pathOut)
@@ -63,7 +59,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
do { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } while (0);
do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } while (0);
}
}
@@ -105,11 +101,12 @@ int main(void) {
SYS_ASYNC_FILE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.FileHandle = file;
req.EventHandle = event;
req.Offset = 0;
req.Buffer = buffer;
req.Size = 512;
req.Length = 512;
req.Iosb = &iosb;
req.RequestHandleOut = &request;
@@ -23,18 +23,18 @@ static int16_t gAudibleFrames[TEST_AUDIBLE_FRAMES * 2u];
static uint32_t gOpenStream;
typedef char TEST_AUDIO_FORMAT_SIZE[(sizeof(SYS_AUDIO_FORMAT) == 8) ? 1 : -1];
typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 64) ? 1 : -1];
typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 60) ? 1 : -1];
typedef char TEST_AUDIO_ENUMERATE_REQUEST_SIZE[(sizeof(SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST) == 32) ? 1 : -1];
typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 40) ? 1 : -1];
typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 32) ? 1 : -1];
typedef char TEST_AUDIO_WRITE_REQUEST_SIZE[(sizeof(SYS_AUDIO_WRITE_REQUEST) == 32) ? 1 : -1];
typedef char TEST_AUDIO_ARM_WAIT_REQUEST_SIZE[(sizeof(SYS_AUDIO_ARM_WAIT_REQUEST) == 56) ? 1 : -1];
typedef char TEST_AUDIO_STREAM_INFO_SIZE[(sizeof(SYS_AUDIO_STREAM_INFO) == 128) ? 1 : -1];
typedef char TEST_AUDIO_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_VOLUME_REQUEST) == 24) ? 1 : -1];
typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 24) ? 1 : -1];
typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 20) ? 1 : -1];
// Writes one NUL-terminated string to the console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -52,7 +52,7 @@ static void print_u64(uint64_t value) {
}
while (count) {
char character = digits[--count];
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&character, 1) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&character, 1) };
(void)SysWriteConsole(&request);
}
}
@@ -187,7 +187,7 @@ static const SYS_AUDIO_ENDPOINT_INFO* select_endpoint(const SYS_AUDIO_ENDPOINT_I
}
// Queries one endpoint by its stable identifier.
static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint64_t endpointId) {
static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint32_t endpointId) {
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
@@ -238,7 +238,7 @@ static void test_master_volume(const SYS_AUDIO_ENDPOINT_INFO* endpoint) {
}
// Opens one stream format and checks the expected status.
static uint32_t open_stream_format(uint64_t endpointId,
static uint32_t open_stream_format(uint32_t endpointId,
uint32_t sampleRate,
uint16_t channelCount,
uint16_t sampleFormat,
@@ -263,7 +263,7 @@ static uint32_t open_stream_format(uint64_t endpointId,
}
// Proves the complete accepted and rejected source-format surface.
static void test_format_contract(uint64_t endpointId) {
static void test_format_contract(uint32_t endpointId) {
static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u };
for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) {
for (uint16_t channels = 1; channels <= 2; channels++) {
@@ -284,7 +284,7 @@ static void test_format_contract(uint64_t endpointId) {
}
// Opens the canonical test stream.
static uint32_t open_stream(uint64_t endpointId) {
static uint32_t open_stream(uint32_t endpointId) {
return open_stream_format(endpointId,
48000u,
2,
@@ -357,7 +357,7 @@ static void wait_stream(uint32_t stream, const char* operation) {
}
// Proves that closing the final handle retires an active stream with queued periods.
static void test_active_close_rundown(uint64_t endpointId) {
static void test_active_close_rundown(uint32_t endpointId) {
for (uint32_t iteration = 0;
iteration < TEST_CLOSE_RUNDOWN_ITERATIONS;
iteration++) {
@@ -1,147 +0,0 @@
// Current-directory PEB cache smoke test.
// Verifies that SysSetCwd updates the user-visible PEB cache without a getter syscall.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/syscall.h>
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer to the kernel console.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current test process.
static void exit_now(int code) {
while (true) SysExitProcess(code);
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
print("cwd_peb_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
exit_now(1);
}
// Fails the test with a simple message.
static void fail(const char* what) {
print("cwd_peb_smoke: ");
print(what);
print("\n");
exit_now(1);
}
// Copies a string view into a NUL-terminated buffer.
static int copy_view(char* dst, size_t cap, SYS_STRING_VIEW view) {
if (!dst || cap == 0 || !view.Data || view.Length + 1 > cap)
return 0;
memcpy(dst, view.Data, view.Length);
dst[view.Length] = '\0';
return 1;
}
// Computes the parent of one canonical native current-directory string.
static int parent_of_cwd(const char* cwd, char* out, size_t cap) {
if (!cwd || !out || cap == 0)
return 0;
size_t len = strlen(cwd);
if (len < 3 || cwd[1] != ':' || (cwd[2] != '/' && cwd[2] != '\\'))
return 0;
if (len + 1 > cap)
return 0;
memcpy(out, cwd, len + 1);
while (len > 3 && (out[len - 1] == '/' || out[len - 1] == '\\'))
out[--len] = '\0';
if (len <= 3) {
out[0] = cwd[0];
out[1] = ':';
out[2] = '/';
out[3] = '\0';
return 1;
}
size_t pos = len;
while (pos > 3 && out[pos - 1] != '/' && out[pos - 1] != '\\')
pos--;
if (pos <= 3) {
out[0] = cwd[0];
out[1] = ':';
out[2] = '/';
out[3] = '\0';
} else {
out[pos - 1] = '\0';
}
return 1;
}
// Sets the current directory to one native path.
static void set_cwd_or_fail(const char* path, const char* what) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
uint32_t status = SysSetCwd(&req);
if (status != NTLSTATUS_SUCCESS) fail_status(what, status);
}
// Verifies the current-directory PEB view against one native path.
static void expect_cwd(const char* expected) {
SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView();
size_t expectedLen = strlen(expected);
if (cwd.Length != expectedLen) fail("cwd length mismatch");
if (cwd.Length && !cwd.Data) fail("cwd data missing");
if (cwd.Length && memcmp(cwd.Data, expected, cwd.Length) != 0) fail("cwd value mismatch");
}
// Program entry point.
int main(void) {
char original[SYS_CWD_MAX];
char parent[SYS_CWD_MAX];
print("cwd_peb_smoke: start\n");
SYS_PEB* peb = SysGetPeb();
if (!peb) fail("peb missing");
if (peb->Magic != SYS_PEB_MAGIC) fail("peb magic mismatch");
if (peb->Version != SYS_ENV_VERSION) fail("peb version mismatch");
if (!copy_view(original, sizeof(original), SysGetCurrentDirectoryView()))
fail("initial cwd missing");
if (!parent_of_cwd(original, parent, sizeof(parent)))
fail("initial cwd malformed");
set_cwd_or_fail(".", "set dot cwd");
expect_cwd(original);
set_cwd_or_fail("..", "set parent cwd");
expect_cwd(parent);
set_cwd_or_fail(original, "restore original cwd");
expect_cwd(original);
print("cwd_peb_smoke: PASS\n");
exit_now(0);
return 0;
}
@@ -0,0 +1,117 @@
// Userspace current-directory smoke test.
// Verifies CWD inheritance and parent traversal clamped at the owning RSSRV resource binding.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/resource.h>
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(req), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
static void exit_now(int code) {
while (true) SysExitProcess(code);
}
static void fail_status(const char* what, uint32_t status) {
(void)status;
print("cwd_userspace_smoke: "); print(what); print("\n");
exit_now(1);
}
static void fail(const char* what) {
print("cwd_userspace_smoke: "); print(what); print("\n");
exit_now(1);
}
static void get_cwd_or_fail(char out[SYS_PATH_MAX + 1u]) {
size_t required = 0;
uint32_t status = NtlGetCurrentDirectory(out, SYS_PATH_MAX + 1u, &required);
if (status != NTLSTATUS_SUCCESS || required == 0 || required > SYS_PATH_MAX + 1u)
fail_status("get cwd", status);
}
static void set_cwd_or_fail(const char* path, const char* what) {
uint32_t status = NtlSetCurrentDirectory(SysSvFromCstr(path));
if (status != NTLSTATUS_SUCCESS) fail_status(what, status);
}
static void expect_cwd(const char* expected) {
char current[SYS_PATH_MAX + 1u];
get_cwd_or_fail(current);
if (strcmp(current, expected) != 0) fail("cwd value mismatch");
}
// Resolves the current absolute resource path and returns the owning namespace-prefix length.
static size_t resource_root_length_or_fail(const char* cwd) {
SYS_HANDLE session = SYS_HANDLE_INVALID;
uint32_t status = NtlResourceConnect(&session);
if (status != NTLSTATUS_SUCCESS) fail_status("resource connect", status);
SYS_HANDLE root = SYS_HANDLE_INVALID;
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
uint32_t resolvedLength = 0;
status = NtlResourceResolve(session, SysSvFromCstr(cwd), NTL_RESOURCE_FLAG_NONE,
&root, &kind, &resolvedLength);
(void)NtlResourceClose(session);
if (root != SYS_HANDLE_INVALID) (void)SysCloseHandle(root);
if (status != NTLSTATUS_SUCCESS) fail_status("resolve cwd root", status);
size_t length = strlen(cwd);
if (kind == NTL_RESOURCE_KIND_INVALID || resolvedLength < 2u || resolvedLength > length ||
(resolvedLength != length && cwd[resolvedLength] != '/'))
fail("initial cwd malformed");
return resolvedLength;
}
static void parent_of_cwd(const char* cwd, size_t rootLength, char* out, size_t cap) {
size_t length = strlen(cwd);
if (rootLength < 2u || rootLength > length || length + 1u > cap ||
(rootLength != length && cwd[rootLength] != '/'))
fail("initial cwd malformed");
memcpy(out, cwd, length + 1u);
if (length == rootLength) return;
while (length > rootLength && out[length - 1u] == '/') out[--length] = '\0';
while (length > rootLength && out[length - 1u] != '/') length--;
if (length <= rootLength + 1u) length = rootLength;
else length--;
out[length] = '\0';
}
int main(void) {
char original[SYS_PATH_MAX + 1u];
char parent[SYS_PATH_MAX + 1u];
char root[SYS_PATH_MAX + 1u];
print("cwd_userspace_smoke: start\n");
get_cwd_or_fail(original);
size_t rootLength = resource_root_length_or_fail(original);
parent_of_cwd(original, rootLength, parent, sizeof(parent));
memcpy(root, original, rootLength);
root[rootLength] = '\0';
// Refresh the cached binding boundary without changing the path.
set_cwd_or_fail(".", "set dot cwd");
expect_cwd(original);
set_cwd_or_fail("..", "set parent cwd");
expect_cwd(parent);
// Relative traversal stops at the actual bound resource, including multi-component roots.
set_cwd_or_fail(root, "set resource root cwd");
expect_cwd(root);
set_cwd_or_fail("..", "clamp parent at resource root");
expect_cwd(root);
set_cwd_or_fail(original, "restore original cwd");
expect_cwd(original);
print("cwd_userspace_smoke: PASS\n");
exit_now(0);
return 0;
}
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define PAGE_SIZE_BYTES 4096u
#define TEST_SIZE (PAGE_SIZE_BYTES * 4u)
@@ -16,7 +17,7 @@ static uint8_t gPatternB[TEST_SIZE];
static uint8_t gReadback[TEST_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -64,8 +65,7 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
// Deletes one test path.
static uint32_t delete_path(const char* path) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
return SysDeleteFile(&req);
return NtlDeleteFile(SysSvFromCstr(path));
}
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
@@ -74,20 +74,16 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
// 8.3 form so the test measures writeback rather than the host bridge.
static const char* paths[] = {
"dwrite.tmp",
"/dwrite.tmp",
"1:/dwrite.tmp",
"0:/dwrite.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
(void)delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
if (pathOut) *pathOut = paths[i];
@@ -100,12 +96,11 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
static uint32_t open_path(const char* path, uint32_t* fileOut) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(path),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS)
*fileOut = h;
return s;
@@ -8,7 +8,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -26,7 +26,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -6,6 +6,7 @@
#include <intrin.h>
#include <ntlite/exception.h>
#include <ntlite/syscall.h>
#include <ntlite/process_loader.h>
#define CHILD_DIVIDE_BY_ZERO "child-divide-by-zero"
#define CHILD_ACCESS_VIOLATION "child-access-violation"
@@ -15,7 +16,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -33,7 +34,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -96,12 +97,16 @@ static void run_child_expect_exit(const char* mode, uint32_t expectedExit) {
fail("missing image path");
uint32_t child = 0;
SYS_CREATE_PROCESS_REQUEST request = {
peb->ImagePath,
SysSvFromCstr(mode),
&child,
};
uint32_t status = SysCreateProcess(&request);
NTL_CREATE_SYSTEM_PROCESS_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
request.Path = peb->ImagePath;
request.CommandLine = SysSvFromCstr(mode);
request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
request.ResolveModuleName = NtlResolveSystemModuleName;
request.ProcessHandleOut = &child;
uint32_t status = NtlCreateSystemProcess(&request);
if (status != NTLSTATUS_SUCCESS)
fail_status("create child", status);
@@ -5,6 +5,8 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/process_loader.h>
#define LOCK_OFFSET 0u
#define LOCK_LENGTH 128u
@@ -14,7 +16,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -32,7 +34,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -62,19 +64,17 @@ static void fail(const char* what) {
// Deletes one path if it already exists.
static void delete_path(const char* path) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
(void)SysDeleteFile(&req);
(void)NtlDeleteFile(SysSvFromCstr(path));
}
// Opens one path with lock rights.
static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(path),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS)
*fileOut = h;
return s;
@@ -84,20 +84,16 @@ static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) {
static uint32_t create_temp_file(uint32_t* fileOut, const char** pathOut) {
static const char* paths[] = {
"file_lock_smoke.tmp",
"/file_lock_smoke.tmp",
"1:/file_lock_smoke.tmp",
"0:/file_lock_smoke.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
uint32_t h = 0;
delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
*pathOut = paths[i];
@@ -245,12 +241,16 @@ static void run_child_expect_success(const char* mode, const char* path) {
build_child_command(command, sizeof(command), mode, path);
uint32_t child = 0;
SYS_CREATE_PROCESS_REQUEST req = {
peb->ImagePath,
SysSvFromCstr(command),
&child,
};
uint32_t s = SysCreateProcess(&req);
NTL_CREATE_SYSTEM_PROCESS_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
req.Path = peb->ImagePath;
req.CommandLine = SysSvFromCstr(command);
req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
req.ResolveModuleName = NtlResolveSystemModuleName;
req.ProcessHandleOut = &child;
uint32_t s = NtlCreateSystemProcess(&req);
if (s != NTLSTATUS_SUCCESS) fail_status("create child", s);
s = SysWait(child, SYS_WAIT_FLAG_NONE);
@@ -348,8 +348,16 @@ int main(void) {
char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX];
build_child_command(command, sizeof(command), "excl-wait", path);
uint32_t child = 0;
SYS_CREATE_PROCESS_REQUEST createReq = { selfPeb->ImagePath, SysSvFromCstr(command), &child };
s = SysCreateProcess(&createReq);
NTL_CREATE_SYSTEM_PROCESS_REQUEST createReq;
memset(&createReq, 0, sizeof(createReq));
createReq.Size = sizeof(createReq);
createReq.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
createReq.Path = selfPeb->ImagePath;
createReq.CommandLine = SysSvFromCstr(command);
createReq.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
createReq.ResolveModuleName = NtlResolveSystemModuleName;
createReq.ProcessHandleOut = &child;
s = NtlCreateSystemProcess(&createReq);
if (s != NTLSTATUS_SUCCESS) fail_status("create blocking child", s);
SysSleep(30);
s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
@@ -5,13 +5,14 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
static char gSelfPath[SYS_PATH_MAX];
static char gSelfDir[SYS_PATH_MAX];
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -29,7 +30,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -105,12 +106,11 @@ static uint32_t open_self_image(uint32_t* fileOut, uint64_t* sizeOut) {
build_parent_dir(gSelfDir, sizeof(gSelfDir), gSelfPath);
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(gSelfPath),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(gSelfPath),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s != NTLSTATUS_SUCCESS)
return s;
@@ -178,8 +178,7 @@ static void test_file_stat(uint32_t file, uint64_t expectedSize) {
SYS_FILE_STAT byPath;
memset(&byPath, 0x5A, sizeof(byPath));
SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfPath), &byPath };
s = SysStatFile(&statReq);
s = NtlStatPath(SysSvFromCstr(gSelfPath), &byPath);
if (s != NTLSTATUS_SUCCESS) fail_status("path stat", s);
check_regular_stat(&byPath, expectedSize, "path stat");
@@ -193,8 +192,7 @@ static void test_file_stat(uint32_t file, uint64_t expectedSize) {
static void test_directory_stat(void) {
SYS_FILE_STAT stat;
memset(&stat, 0xCC, sizeof(stat));
SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfDir), &stat };
uint32_t s = SysStatFile(&statReq);
uint32_t s = NtlStatPath(SysSvFromCstr(gSelfDir), &stat);
if (s != NTLSTATUS_SUCCESS) fail_status("directory stat", s);
check_directory_stat(&stat);
}
@@ -202,15 +200,13 @@ static void test_directory_stat(void) {
// Checks failure status paths that stat callers depend on.
static void test_failure_paths(void) {
SYS_FILE_STAT stat;
SYS_STAT_FILE_REQUEST missingReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), &stat };
uint32_t s = SysStatFile(&missingReq);
uint32_t s = NtlStatPath(SYS_SV_LITERAL("file_stat_smoke_missing.nope"), &stat);
if (s != NTLSTATUS_NOT_FOUND) fail_status("missing path status", s);
s = SysGetFileStat(0xFFFFFFFFu, &stat);
if (s != NTLSTATUS_INVALID_HANDLE) fail_status("invalid handle status", s);
SYS_STAT_FILE_REQUEST badReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), 0 };
s = SysStatFile(&badReq);
s = NtlStatPath(SYS_SV_LITERAL("file_stat_smoke_missing.nope"), (SYS_FILE_STAT*)0);
if (s != NTLSTATUS_INVALID_PARAMETER) fail_status("null stat output status", s);
}
@@ -1,10 +1,14 @@
// Native handle duplication smoke test.
// Verifies explicit rights reduction, same-rights duplication, and invalid duplicate requests.
// Native capability-handle smoke test.
// Verifies monotonic identities, typed rights, derivation, aliasing, and revocation.
#include <stdint.h>
#include <stdbool.h>
#include <ntlite/syscall.h>
#define EVENT_AUTO_RESET 0u
#define HANDLE_STRESS_COUNT 512u
#define DERIVATION_SUCCESS_COUNT 15u
typedef struct TEST_QUEUE_MESSAGE {
uint32_t Type;
uint32_t Flags;
@@ -17,7 +21,7 @@ typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -35,7 +39,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -54,7 +58,7 @@ static void fail(const char* msg) {
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
static void fail_status(const char* what, KSTATUS status) {
print("handle_dup_smoke: ");
print(what);
print(" status=");
@@ -64,7 +68,7 @@ static void fail_status(const char* what, uint32_t status) {
}
// Requires one exact status value.
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
static void expect_status(const char* what, KSTATUS got, KSTATUS expected) {
if (got == expected) return;
print("handle_dup_smoke: ");
print(what);
@@ -77,8 +81,13 @@ static void expect_status(const char* what, uint32_t got, uint32_t expected) {
}
// Duplicates one handle into the current process.
static uint32_t duplicate_handle(uint32_t source, uint32_t process, uint32_t desiredRights, uint32_t flags, uint32_t* out) {
static KSTATUS duplicate_handle(SYS_HANDLE source,
SYS_HANDLE process,
SYS_RIGHTS desiredRights,
uint32_t flags,
SYS_HANDLE* out) {
SYS_DUPLICATE_HANDLE_REQUEST req = {
sizeof(SYS_DUPLICATE_HANDLE_REQUEST),
source,
process,
desiredRights,
@@ -88,8 +97,19 @@ static uint32_t duplicate_handle(uint32_t source, uint32_t process, uint32_t des
return SysDuplicateHandle(&req);
}
// Derives one child capability in the current process.
static KSTATUS derive_handle(SYS_HANDLE source, SYS_RIGHTS desiredRights, SYS_HANDLE* out) {
SYS_DERIVE_HANDLE_REQUEST req = {
sizeof(SYS_DERIVE_HANDLE_REQUEST),
source,
desiredRights,
out,
};
return SysDeriveHandle(&req);
}
// Sends one queue message.
static uint32_t send_message(uint32_t queue, uint32_t type, uint64_t value) {
static KSTATUS send_message(SYS_HANDLE queue, uint32_t type, uint64_t value) {
TEST_QUEUE_MESSAGE msg = {0};
msg.Type = type;
msg.Param[0] = value;
@@ -104,7 +124,7 @@ static uint32_t send_message(uint32_t queue, uint32_t type, uint64_t value) {
}
// Receives one queue message.
static uint32_t recv_message(uint32_t queue, TEST_QUEUE_MESSAGE* msg) {
static KSTATUS recv_message(SYS_HANDLE queue, TEST_QUEUE_MESSAGE* msg) {
SYS_QUEUE_RECV_REQUEST req = {
sizeof(req),
queue,
@@ -120,84 +140,244 @@ static void check_message(const TEST_QUEUE_MESSAGE* msg, uint32_t type, uint64_t
if (msg->Type != type || msg->Param[0] != value) fail("message payload mismatch");
}
// Runs the native handle duplication smoke test.
int main(void) {
print("handle_dup_smoke: start\n");
// Closes one handle and requires success.
static void close_handle(const char* what, SYS_HANDLE handle) {
KSTATUS status = SysCloseHandle(handle);
if (!KSUCCESS(status)) fail_status(what, status);
}
uint32_t currentPid = SysGetCurrentPid();
uint32_t process = 0;
uint32_t s = SysOpenProcess(currentPid, &process);
if (s != NTLSTATUS_SUCCESS) fail_status("open current process", s);
// Verifies that closed identities are not reused and stale values stay invalid.
static void test_monotonic_identity(void) {
SYS_HANDLE first = SYS_HANDLE_INVALID;
KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &first);
if (!KSUCCESS(status)) fail_status("create first monotonic event", status);
close_handle("close first monotonic event", first);
uint32_t queue = 0;
s = SysCreateQueue(&queue);
if (s != NTLSTATUS_SUCCESS) fail_status("create queue", s);
SYS_HANDLE second = SYS_HANDLE_INVALID;
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &second);
if (!KSUCCESS(status)) fail_status("create second monotonic event", status);
if (second <= first) fail("handle identity was reused or moved backwards");
uint32_t readOnly = 0;
s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &readOnly);
if (s != NTLSTATUS_SUCCESS) fail_status("duplicate read-only", s);
expect_status("close stale handle", SysCloseHandle(first), NTLSTATUS_INVALID_HANDLE);
close_handle("close second monotonic event", second);
}
uint32_t writeOnly = 0;
s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_WRITE, SYS_DUPLICATE_HANDLE_FLAG_NONE, &writeOnly);
if (s != NTLSTATUS_SUCCESS) fail_status("duplicate write-only", s);
// Verifies that live-handle capacity is no longer tied to the old 256-slot table.
static void test_dynamic_handle_capacity(void) {
SYS_HANDLE handles[HANDLE_STRESS_COUNT];
SYS_HANDLE previous = SYS_HANDLE_INVALID;
uint32_t sameRights = 0;
s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights);
if (s != NTLSTATUS_SUCCESS) fail_status("duplicate same rights", s);
for (uint32_t i = 0; i < HANDLE_STRESS_COUNT; ++i) {
handles[i] = SYS_HANDLE_INVALID;
KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &handles[i]);
if (!KSUCCESS(status)) fail_status("create stress event", status);
if (previous != SYS_HANDLE_INVALID && handles[i] <= previous)
fail("stress handles were not strictly monotonic");
previous = handles[i];
}
s = send_message(readOnly, 10, 100);
if (s == NTLSTATUS_SUCCESS) fail("read-only duplicate allowed send");
for (uint32_t i = HANDLE_STRESS_COUNT; i != 0; --i)
close_handle("close stress event", handles[i - 1]);
}
// Verifies explicit rights attenuation on duplicate aliases.
static void test_duplicate_rights(SYS_HANDLE process, SYS_HANDLE queue,
SYS_HANDLE* sameRightsOut,
SYS_HANDLE* receiveOnlyOut,
SYS_HANDLE* sendOnlyOut) {
SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID;
KSTATUS status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE,
SYS_DUPLICATE_HANDLE_FLAG_NONE, &receiveOnly);
if (!KSUCCESS(status)) fail_status("duplicate receive-only", status);
SYS_HANDLE sendOnly = SYS_HANDLE_INVALID;
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_SEND,
SYS_DUPLICATE_HANDLE_FLAG_NONE, &sendOnly);
if (!KSUCCESS(status)) fail_status("duplicate send-only", status);
SYS_HANDLE sameRights = SYS_HANDLE_INVALID;
status = duplicate_handle(queue, process, 0,
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights);
if (!KSUCCESS(status)) fail_status("duplicate same rights", status);
status = send_message(receiveOnly, 10, 100);
expect_status("receive-only send denied", status, NTLSTATUS_ACCESS_DENIED);
TEST_QUEUE_MESSAGE msg;
s = recv_message(writeOnly, &msg);
if (s == NTLSTATUS_SUCCESS) fail("write-only duplicate allowed receive");
status = recv_message(sendOnly, &msg);
expect_status("send-only receive denied", status, NTLSTATUS_ACCESS_DENIED);
s = send_message(queue, 11, 101);
if (s != NTLSTATUS_SUCCESS) fail_status("send original", s);
s = recv_message(readOnly, &msg);
if (s != NTLSTATUS_SUCCESS) fail_status("receive read-only", s);
status = send_message(queue, 11, 101);
if (!KSUCCESS(status)) fail_status("send original", status);
status = recv_message(receiveOnly, &msg);
if (!KSUCCESS(status)) fail_status("receive receive-only", status);
check_message(&msg, 11, 101);
s = send_message(writeOnly, 12, 102);
if (s != NTLSTATUS_SUCCESS) fail_status("send write-only", s);
s = recv_message(queue, &msg);
if (s != NTLSTATUS_SUCCESS) fail_status("receive original", s);
status = send_message(sendOnly, 12, 102);
if (!KSUCCESS(status)) fail_status("send send-only", status);
status = recv_message(queue, &msg);
if (!KSUCCESS(status)) fail_status("receive original", status);
check_message(&msg, 12, 102);
s = send_message(sameRights, 13, 103);
if (s != NTLSTATUS_SUCCESS) fail_status("send same-rights", s);
s = recv_message(sameRights, &msg);
if (s != NTLSTATUS_SUCCESS) fail_status("receive same-rights", s);
status = send_message(sameRights, 13, 103);
if (!KSUCCESS(status)) fail_status("send same-rights", status);
status = recv_message(sameRights, &msg);
if (!KSUCCESS(status)) fail_status("receive same-rights", status);
check_message(&msg, 13, 103);
uint32_t ignored = 0;
s = duplicate_handle(readOnly, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate non-duplicable handle", s, NTLSTATUS_ACCESS_DENIED);
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
status = duplicate_handle(receiveOnly, process, SYS_QUEUE_RIGHT_RECEIVE,
SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate non-duplicable handle", status, NTLSTATUS_ACCESS_DENIED);
s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ | 0x80000000u, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate unknown rights", s, NTLSTATUS_INVALID_PARAMETER);
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE | 0x80000000u,
SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate unknown rights", status, NTLSTATUS_INVALID_PARAMETER);
s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
expect_status("duplicate same-rights with desired rights", s, NTLSTATUS_INVALID_PARAMETER);
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE,
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
expect_status("duplicate same-rights with desired rights", status, NTLSTATUS_INVALID_PARAMETER);
s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate zero rights", s, NTLSTATUS_INVALID_PARAMETER);
status = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
expect_status("duplicate zero rights", status, NTLSTATUS_INVALID_PARAMETER);
SYS_DUPLICATE_HANDLE_REQUEST badOut = {
sizeof(SYS_DUPLICATE_HANDLE_REQUEST),
queue,
process,
SYS_HANDLE_RIGHT_READ,
SYS_QUEUE_RIGHT_RECEIVE,
SYS_DUPLICATE_HANDLE_FLAG_NONE,
0,
};
s = SysDuplicateHandle(&badOut);
expect_status("duplicate null output", s, NTLSTATUS_INVALID_PARAMETER);
status = SysDuplicateHandle(&badOut);
expect_status("duplicate null output", status, NTLSTATUS_INVALID_PARAMETER);
SysCloseHandle(sameRights);
SysCloseHandle(writeOnly);
SysCloseHandle(readOnly);
SysCloseHandle(queue);
SysCloseHandle(process);
*sameRightsOut = sameRights;
*receiveOnlyOut = receiveOnly;
*sendOnlyOut = sendOnly;
}
// Verifies child grants attenuate authority and can be revoked independently.
static void test_derived_revocation(SYS_HANDLE process, SYS_HANDLE queue) {
const SYS_RIGHTS childRights = SYS_QUEUE_RIGHT_RECEIVE |
SYS_RIGHT_DUPLICATE |
SYS_RIGHT_DERIVE |
SYS_RIGHT_REVOKE;
SYS_HANDLE child = SYS_HANDLE_INVALID;
KSTATUS status = derive_handle(queue, childRights, &child);
if (!KSUCCESS(status)) fail_status("derive child", status);
SYS_HANDLE childAlias = SYS_HANDLE_INVALID;
status = duplicate_handle(child, process, 0,
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &childAlias);
if (!KSUCCESS(status)) fail_status("duplicate child alias", status);
SYS_HANDLE grandchild = SYS_HANDLE_INVALID;
status = derive_handle(child, SYS_QUEUE_RIGHT_RECEIVE, &grandchild);
if (!KSUCCESS(status)) fail_status("derive grandchild", status);
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
status = derive_handle(child, SYS_QUEUE_RIGHT_SEND, &ignored);
expect_status("derive cannot amplify rights", status, NTLSTATUS_ACCESS_DENIED);
status = SysRevokeHandle(grandchild);
expect_status("revoke without revoke right", status, NTLSTATUS_ACCESS_DENIED);
TEST_QUEUE_MESSAGE msg;
status = send_message(queue, 20, 200);
if (!KSUCCESS(status)) fail_status("send for grandchild", status);
status = recv_message(grandchild, &msg);
if (!KSUCCESS(status)) fail_status("receive grandchild", status);
check_message(&msg, 20, 200);
status = SysRevokeHandle(child);
if (!KSUCCESS(status)) fail_status("revoke child", status);
ignored = SYS_HANDLE_INVALID;
status = duplicate_handle(childAlias, process, 0,
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
expect_status("duplicate revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED);
status = derive_handle(childAlias, SYS_QUEUE_RIGHT_RECEIVE, &ignored);
expect_status("derive revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED);
status = recv_message(grandchild, &msg);
expect_status("revoked grandchild receive", status, NTLSTATUS_CAPABILITY_REVOKED);
// Revoking a derived grant must not revoke its parent grant.
status = send_message(queue, 21, 201);
if (!KSUCCESS(status)) fail_status("parent send after child revoke", status);
status = recv_message(queue, &msg);
if (!KSUCCESS(status)) fail_status("parent receive after child revoke", status);
check_message(&msg, 21, 201);
close_handle("close grandchild", grandchild);
close_handle("close child alias", childAlias);
close_handle("close child", child);
}
// Verifies the bounded derivation chain used for revocation traversal.
static void test_derivation_depth(SYS_HANDLE queue) {
const SYS_RIGHTS rights = SYS_QUEUE_RIGHT_RECEIVE |
SYS_RIGHT_DERIVE |
SYS_RIGHT_REVOKE;
SYS_HANDLE chain[DERIVATION_SUCCESS_COUNT];
SYS_HANDLE parent = queue;
for (uint32_t i = 0; i < DERIVATION_SUCCESS_COUNT; ++i) {
chain[i] = SYS_HANDLE_INVALID;
KSTATUS status = derive_handle(parent, rights, &chain[i]);
if (!KSUCCESS(status)) fail_status("derive bounded chain", status);
parent = chain[i];
}
SYS_HANDLE tooDeep = SYS_HANDLE_INVALID;
KSTATUS status = derive_handle(parent, rights, &tooDeep);
expect_status("derive beyond depth bound", status, NTLSTATUS_LIMIT_REACHED);
for (uint32_t i = DERIVATION_SUCCESS_COUNT; i != 0; --i)
close_handle("close bounded derivation", chain[i - 1]);
}
// Runs the native capability-handle smoke test.
int main(void) {
print("handle_dup_smoke: start\n");
test_monotonic_identity();
test_dynamic_handle_capacity();
uint32_t currentPid = SysGetCurrentPid();
SYS_HANDLE process = SYS_HANDLE_INVALID;
KSTATUS status = SysOpenProcess(currentPid, SYS_PROCESS_RIGHT_INSERT_HANDLE | SYS_RIGHT_QUERY, &process);
if (!KSUCCESS(status)) fail_status("open current process", status);
SYS_HANDLE queue = SYS_HANDLE_INVALID;
status = SysCreateQueue(&queue);
if (!KSUCCESS(status)) fail_status("create queue", status);
SYS_HANDLE sameRights = SYS_HANDLE_INVALID;
SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID;
SYS_HANDLE sendOnly = SYS_HANDLE_INVALID;
test_duplicate_rights(process, queue, &sameRights, &receiveOnly, &sendOnly);
test_derived_revocation(process, queue);
test_derivation_depth(queue);
// A duplicate is an alias of the same grant, so revocation through either
// alias revokes the original grant as well.
status = SysRevokeHandle(sameRights);
if (!KSUCCESS(status)) fail_status("revoke duplicate alias", status);
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
status = duplicate_handle(queue, process, 0,
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
expect_status("duplicate root after alias revoke", status, NTLSTATUS_CAPABILITY_REVOKED);
close_handle("close same-rights", sameRights);
close_handle("close send-only", sendOnly);
close_handle("close receive-only", receiveOnly);
close_handle("close revoked queue", queue);
close_handle("close process", process);
print("handle_dup_smoke: PASS\n");
exit_now(0);
@@ -7,7 +7,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -25,7 +25,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -6,7 +6,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -24,7 +24,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
+20 -25
View File
@@ -4,13 +4,14 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define TEST_MAP_SIZE 8192u
static uint8_t gExpected[TEST_MAP_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -27,7 +28,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -58,24 +59,19 @@ static void fail_msg(const char* msg) {
static uint32_t open_first_existing(uint32_t* fileOut, const char** pathOut) {
static const char* candidates[] = {
"mmap_smoke.exe",
"/apps/mmap_smoke.exe",
"0:/apps/mmap_smoke.exe",
"1:/apps/mmap_smoke.exe",
"/SystemRoot/apps/mmap_smoke.exe",
"/system/init.exe",
"0:/system/init.exe",
"1:/system/init.exe",
"/SystemRoot/system/init.exe",
"system/init.exe",
};
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
uint32_t file = 0;
SYS_OPEN_FILE_REQUEST openReq = SysMakeOpenFileRequest(SysSvFromCstr(candidates[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&file);
uint32_t s = SysOpenFile(&openReq);
uint32_t s = NtlOpenFile(SysSvFromCstr(candidates[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&file);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = file;
if (pathOut)
@@ -113,11 +109,11 @@ int main(void) {
print("mmap_smoke: baseline read OK\n");
uint32_t mapping = 0;
s = SysCreateFileMapping(file,
SYS_VM_READ,
SYS_MAP_FLAG_NONE,
sizeof(gExpected),
&mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ,
SYS_MAP_FLAG_NONE, sizeof(gExpected), &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS)
fail_status("create file mapping", s);
@@ -131,12 +127,11 @@ int main(void) {
print("mmap_smoke: source handle closed\n");
void* base = 0;
s = SysMapFile(mapping,
0,
sizeof(gExpected),
SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE,
&base);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, sizeof(gExpected), &base
};
s = SysMapFile(&mapFile);
SysCloseHandle(mapping);
mapping = 0;
@@ -1,155 +1,127 @@
// User-mode smoke test for public dynamic module loading.
// User-mode smoke test for process-local userspace module loading.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/module_loader.h>
#include <ntlite/resource.h>
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
if (v == 0) { print("0"); return; }
while (v && i < (int)sizeof(buf)) { buf[i++] = (char)('0' + (v % 10)); v /= 10; }
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(req), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current process.
static void exit_now(int32_t code) {
while (true) SysExitProcess(code);
}
// Fails the test with one message.
static void fail(const char* message) {
print("module_load_smoke: ");
print(message);
print("\n");
exit_now(1);
}
// Fails the test with one status value.
static void exit_now(int32_t code) { while (true) SysExitProcess(code); }
static void fail(const char* message) { print("module_load_smoke: "); print(message); print("\n"); exit_now(1); }
static void fail_status(const char* what, uint32_t status) {
print("module_load_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
exit_now(1);
print("module_load_smoke: "); print(what); print(" status="); print_u64(status); print("\n"); exit_now(1);
}
// Checks whether one public module snapshot has the expected shape.
static void check_module_info(const SYS_MODULE_INFO* info) {
if (!info) fail("null module info");
if (info->Size != sizeof(*info)) fail("module info size mismatch");
if (info->Version != SYS_MODULE_INFO_VERSION) fail("module info version mismatch");
if (info->Base == 0 || info->ImageSize == 0) fail("module has empty mapping");
if ((info->Flags & SYS_MODULE_FLAG_PUBLIC_REF) == 0) fail("module missing public reference flag");
if (info->PublicRefCount == 0 || info->TotalRefCount == 0) fail("module reference counts missing");
static void check_module_info(const NTL_MODULE_INFO* info) {
if (!info || !info->Base || !info->Size) fail("module has empty mapping");
if (info->PublicRefCount == 0) fail("module public reference missing");
if (strcmp(info->Name, "posix.dll") != 0) fail("module name mismatch");
}
// Returns whether the current module list contains the specified base address.
static int module_list_contains(uintptr_t base) {
size_t count = 0;
uint32_t status = SysGetModuleList(0, 0, &count);
if (status != NTLSTATUS_SUCCESS) fail_status("query module count", status);
uint32_t status = NtlGetModuleList(0, 0, &count);
if (status != NTLSTATUS_BUFFER_TOO_SMALL && status != NTLSTATUS_SUCCESS)
fail_status("query module count", status);
if (count == 0) return 0;
if (count > 32) fail("module count unexpectedly large");
SYS_MODULE_INFO modules[32];
memset(modules, 0, sizeof(modules));
status = SysGetModuleList(modules, count, &count);
if (status != NTLSTATUS_SUCCESS) fail_status("query module list", status);
for (size_t i = 0; i < count; i++) {
if (modules[i].Base == base)
return 1;
}
return 0;
if (count > SIZE_MAX / sizeof(NTL_MODULE_INFO)) fail("module count overflow");
size_t bytes = count * sizeof(NTL_MODULE_INFO);
size_t pages = (bytes + 4095u) / 4096u;
void* memory = 0;
SYS_ALLOC_VM_REQUEST alloc;
memset(&alloc, 0, sizeof(alloc));
alloc.Size = sizeof(alloc);
alloc.Protect = SYS_VM_READWRITE;
alloc.Flags = SYS_VM_FLAG_NONE;
alloc.PageCount = pages;
alloc.BaseOut = &memory;
status = SysAllocVm(&alloc);
if (status != NTLSTATUS_SUCCESS) fail_status("allocate module list", status);
NTL_MODULE_INFO* modules = (NTL_MODULE_INFO*)memory;
memset(modules, 0, bytes);
size_t copied = count;
status = NtlGetModuleList(modules, count, &copied);
if (status != NTLSTATUS_SUCCESS) { (void)SysFreeVm(memory); fail_status("query module list", status); }
int found = 0;
for (size_t i = 0; i < copied; ++i) if (modules[i].Base == base) { found = 1; break; }
(void)SysFreeVm(memory);
return found;
}
// Verifies export lookup through a module handle.
static void check_export_lookup(void) {
uint32_t handle = 0;
SYS_LOAD_MODULE_REQUEST req;
// Loads one module through the userspace registry with the specified filename policy.
static NTL_MODULE_HANDLE load_module(SYS_HANDLE root, const char* path,
NTL_MODULE_NAME_RESOLVER resolver, NTL_MODULE_INFO* info) {
NTL_MODULE_HANDLE handle = NTL_MODULE_HANDLE_INVALID;
NTL_LOAD_MODULE_AT_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Path = SysSvFromCstr("syscall.dll");
req.Flags = NTL_MODULE_LOAD_FLAG_NONE;
req.RootHandle = root;
req.Path = SysSvFromCstr(path);
req.ModuleRootHandle = root;
req.ModuleDirectory = SYS_SV_LITERAL("system");
req.ResolveModuleName = resolver;
req.ModuleHandleOut = &handle;
uint32_t status = SysLoadModule(&req);
if (status != NTLSTATUS_SUCCESS) fail_status("load syscall.dll", status);
if (handle == 0) fail("syscall load returned null handle");
uintptr_t address = 0;
SYS_GET_MODULE_EXPORT_REQUEST exportReq;
memset(&exportReq, 0, sizeof(exportReq));
exportReq.Size = sizeof(exportReq);
exportReq.ModuleHandle = handle;
exportReq.Name = SysSvFromCstr("SysGetCurrentPid");
exportReq.AddressOut = &address;
status = SysGetModuleExport(&exportReq);
if (status != NTLSTATUS_SUCCESS) fail_status("lookup SysGetCurrentPid", status);
if (address == 0) fail("export address is zero");
status = SysUnloadModule(handle);
if (status != NTLSTATUS_SUCCESS) fail_status("unload syscall.dll handle", status);
req.InfoOut = info;
uint32_t status = NtlLoadModuleAt(&req);
if (status != NTLSTATUS_SUCCESS) fail_status("load module", status);
if (handle == NTL_MODULE_HANDLE_INVALID) fail("load returned null handle");
return handle;
}
// Runs the public dynamic module loader smoke test.
// Runs the userspace process-module loader smoke test.
int main(void) {
print("module_load_smoke: start\n");
check_export_lookup();
SYS_HANDLE root = SYS_HANDLE_INVALID;
uint32_t status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &root);
if (status != NTLSTATUS_SUCCESS) fail_status("open system root", status);
uint32_t handle = 0;
SYS_MODULE_INFO info;
NTL_MODULE_HANDLE syscallModule = load_module(root, "system/syscall.dll",
NtlResolveModuleNameIdentity, 0);
uintptr_t address = 0;
status = NtlGetModuleExport(syscallModule, SysSvFromCstr("SysGetCurrentPid"), &address);
if (status != NTLSTATUS_SUCCESS || !address) fail_status("lookup SysGetCurrentPid", status);
status = NtlUnloadModule(syscallModule);
if (status != NTLSTATUS_SUCCESS) fail_status("unload syscall.dll", status);
NTL_MODULE_INFO info;
memset(&info, 0, sizeof(info));
SYS_LOAD_MODULE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Path = SysSvFromCstr("posix.dll");
req.ModuleHandleOut = &handle;
req.InfoOut = &info;
uint32_t status = SysLoadModule(&req);
if (status != NTLSTATUS_SUCCESS) fail_status("load posix.dll", status);
if (handle == 0) fail("load returned null handle");
NTL_MODULE_HANDLE posix = load_module(root, "system/msvcrt.dll",
NtlResolveSystemModuleName, &info);
check_module_info(&info);
if (!module_list_contains(info.Base)) fail("loaded module absent from list");
SYS_MODULE_INFO byHandle;
NTL_MODULE_INFO byHandle;
memset(&byHandle, 0, sizeof(byHandle));
status = SysGetModuleInfo(handle, &byHandle);
status = NtlGetModuleInfo(posix, &byHandle);
if (status != NTLSTATUS_SUCCESS) fail_status("get module info", status);
check_module_info(&byHandle);
if (byHandle.Base != info.Base) fail("module handle base mismatch");
status = SysUnloadModule(handle);
if (status != NTLSTATUS_SUCCESS) fail_status("unload module", status);
memset(&byHandle, 0, sizeof(byHandle));
status = SysGetModuleInfo(handle, &byHandle);
status = NtlUnloadModule(posix);
if (status != NTLSTATUS_SUCCESS) fail_status("unload posix.dll", status);
status = NtlGetModuleInfo(posix, &byHandle);
if (status != NTLSTATUS_INVALID_HANDLE) fail_status("query unloaded handle", status);
(void)SysCloseHandle(root);
print("module_load_smoke: PASS\n");
exit_now(0);
return 0;
@@ -20,7 +20,7 @@ typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -38,7 +38,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -83,6 +83,7 @@ static void expect_status(const char* what, uint32_t got, uint32_t expected) {
static uint32_t publish_queue(const char* name, uint32_t queue) {
SYS_PUBLISH_QUEUE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Name = SysSvFromCstr(name);
req.QueueHandle = queue;
return SysPublishQueue(&req);
@@ -92,6 +93,7 @@ static uint32_t publish_queue(const char* name, uint32_t queue) {
static uint32_t unpublish_queue(const char* name) {
SYS_UNPUBLISH_QUEUE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Name = SysSvFromCstr(name);
return SysUnpublishQueue(&req);
}
@@ -100,6 +102,7 @@ static uint32_t unpublish_queue(const char* name) {
static uint32_t open_named_queue(const char* name, uint32_t* out) {
SYS_OPEN_NAMED_QUEUE_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Name = SysSvFromCstr(name);
req.HandleOut = out;
return SysOpenNamedQueue(&req);
@@ -10,7 +10,7 @@ static volatile uint32_t gContinueHits;
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -28,7 +28,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -11,7 +11,7 @@ static volatile uint32_t gHandlerHits;
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -29,7 +29,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -11,7 +11,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -29,7 +29,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -89,6 +89,7 @@ static SYS_SOCKET_ADDRESS make_ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
static void set_u32_option(uint32_t socket, uint32_t option, uint32_t value) {
SYS_SOCKET_OPTION_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Option = option;
req.Value = &value;
req.ValueSize = sizeof(value);
@@ -135,6 +136,7 @@ static void check_nonblocking_receive(void) {
SYS_SOCKET_RECV_FROM_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Buffer = buffer;
req.Length = sizeof(buffer);
req.RemoteAddressOut = &remote;
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define TEST_SIZE 8192u
#define TRUNC_SIZE 1024u
@@ -14,7 +15,7 @@ static uint8_t gReplacement[4096];
static uint8_t gReadback[TEST_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -58,27 +59,22 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
}
static void delete_path(const char* path) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
(void)SysDeleteFile(&req);
(void)NtlDeleteFile(SysSvFromCstr(path));
}
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
static const char* paths[] = {
"pcache_invalidate.tmp",
"/pcache_invalidate.tmp",
"1:/pcache_invalidate.tmp",
"0:/pcache_invalidate.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
if (pathOut) *pathOut = paths[i];
@@ -169,11 +165,19 @@ int main(void) {
if (afterWrite.DirtyMarks <= afterSecond.DirtyMarks) fail("rewrite did not dirty cached page");
uint32_t mapping = 0;
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ,
SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping after rewrite", s);
void* mappedBase = 0;
s = SysMapFile(mapping, 0, sizeof(gReplacement), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &mappedBase);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, sizeof(gReplacement), &mappedBase
};
s = SysMapFile(&mapFile);
SysCloseHandle(mapping);
if (s != NTLSTATUS_SUCCESS) fail_status("map after rewrite", s);
if (!mappedBase) fail("map after rewrite returned null");
@@ -5,13 +5,14 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define TEST_SIZE 8192u
static uint8_t gReadback[TEST_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -57,27 +58,20 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
static uint32_t open_test_file(uint32_t* out, const char** pathOut) {
static const char* paths[] = {
"pcache_mmap_read_reuse.exe",
"/apps/pcache_mmap_read_reuse.exe",
"1:/apps/pcache_mmap_read_reuse.exe",
"0:/apps/pcache_mmap_read_reuse.exe",
"/SystemRoot/apps/pcache_mmap_read_reuse.exe",
"pcache_smoke.exe",
"/apps/pcache_smoke.exe",
"1:/apps/pcache_smoke.exe",
"0:/apps/pcache_smoke.exe",
"/system/init.exe",
"0:/system/init.exe",
"1:/system/init.exe",
"/SystemRoot/apps/pcache_smoke.exe",
"/SystemRoot/system/init.exe",
"system/init.exe",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*out = h;
if (pathOut) *pathOut = paths[i];
@@ -114,11 +108,19 @@ int main(void) {
print("\n");
uint32_t mapping = 0;
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, mapSize, &mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ,
SYS_MAP_FLAG_NONE, mapSize, &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s);
void* base = 0;
s = SysMapFile(mapping, 0, mapSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, mapSize, &base
};
s = SysMapFile(&mapFile);
SysCloseHandle(mapping);
if (s != NTLSTATUS_SUCCESS) fail_status("map file", s);
if (!base) fail("map returned null");
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define THREAD_COUNT 6u
#define ITERATIONS 16u
@@ -22,7 +23,7 @@ typedef struct THREAD_CTX {
static THREAD_CTX gThreads[THREAD_COUNT];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -66,27 +67,22 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
}
static void delete_path(const char* path) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
(void)SysDeleteFile(&req);
(void)NtlDeleteFile(SysSvFromCstr(path));
}
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
static const char* paths[] = {
"pcache_multithread.tmp",
"/pcache_multithread.tmp",
"1:/pcache_multithread.tmp",
"0:/pcache_multithread.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
if (pathOut) *pathOut = paths[i];
@@ -116,8 +112,11 @@ static void thread_main(uintptr_t arg) {
for (uint32_t iter = 0; iter < ITERATIONS; ++iter) {
void* base = 0;
uint32_t s = SysMapFile(ctx->Mapping, 0, TEST_SIZE,
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), ctx->Mapping, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, TEST_SIZE, &base
};
uint32_t s = SysMapFile(&mapFile);
if (s != NTLSTATUS_SUCCESS || !base) {
ctx->Status = s ? s : NTLSTATUS_INVALID_PARAMETER;
SysExitThread(1);
@@ -161,7 +160,11 @@ int main(void) {
if (written != sizeof(gPattern)) fail("short write");
uint32_t mapping = 0;
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ,
SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s);
SYS_FILE_CACHE_STATS before;
@@ -179,7 +182,7 @@ int main(void) {
gThreads[i].Mapping = mapping;
gThreads[i].Status = NTLSTATUS_PENDING;
SYS_CREATE_THREAD_REQUEST req = {
(uintptr_t)thread_main,
sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)thread_main,
(uintptr_t)&gThreads[i],
1,
0,
@@ -6,6 +6,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define READ_CHUNK 4096u
#define MIN_PRESSURE_BYTES (3u * 1024u * 1024u)
@@ -14,7 +15,7 @@
static uint8_t gBuf[READ_CHUNK];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -76,12 +77,11 @@ static uint32_t open_self_image(uint32_t* fileOut, const char** pathOut, uint64_
return NTLSTATUS_INVALID_PARAMETER;
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(gSelfPath),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(gSelfPath),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s != NTLSTATUS_SUCCESS)
return s;
+20 -17
View File
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define TEST_SIZE 8192u
@@ -12,7 +13,7 @@ static uint8_t gA[TEST_SIZE];
static uint8_t gB[TEST_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -26,7 +27,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -55,23 +56,17 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
static uint32_t open_self(uint32_t* out) {
static const char* paths[] = {
"pcache_smoke.exe",
"/apps/pcache_smoke.exe",
"Y:/apps/pcache_smoke.exe",
"0:/apps/pcache_smoke.exe",
"1:/apps/pcache_smoke.exe",
"/apps/shell_native.exe",
"0:/apps/shell_native.exe",
"1:/apps/shell_native.exe",
"/SystemRoot/apps/pcache_smoke.exe",
"/SystemRoot/apps/shell_native.exe",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*out = h;
print("pcache_smoke: using "); print(paths[i]); print("\n");
@@ -123,13 +118,21 @@ int main(void) {
fail("second read did not hit file cache");
uint32_t mapping = 0;
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gA), &mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ,
SYS_MAP_FLAG_NONE, sizeof(gA), &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s);
SysCloseHandle(file);
file = 0;
void* base = 0;
s = SysMapFile(mapping, 0, sizeof(gA), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, sizeof(gA), &base
};
s = SysMapFile(&mapFile);
SysCloseHandle(mapping);
if (s != NTLSTATUS_SUCCESS) fail_status("map file", s);
if (!base) fail("map returned null");
@@ -0,0 +1,501 @@
// Port IPC smoke test. This validates copied messaging, service discovery, waitability, and capability transfer.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/process_loader.h>
#include <ntlite/port.h>
#include <ntlite/service.h>
#define EVENT_AUTO_RESET 0u
#define TEST_TIMEOUT_MS 2000u
// Writes one string to the console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Writes one unsigned integer without requiring formatted libc helpers.
static void print_u32(uint32_t value) {
char digits[16];
uint32_t count = 0;
do {
digits[count++] = (char)('0' + value % 10u);
value /= 10u;
} while (value && count < sizeof(digits));
while (count) {
char one[2] = { digits[--count], 0 };
print(one);
}
}
// Fails the current process with one visible status diagnostic.
static int fail(const char* operation, KSTATUS status) {
print("port_ipc_smoke: ");
print(operation);
print(" status=");
print_u32(status);
print("\n");
return 1;
}
// Reads Port transport diagnostics through the privileged kernel-stats interface.
static KSTATUS query_port_stats(SYS_PORT_STATS* stats) {
if (!stats) return NTLSTATUS_INVALID_PARAMETER;
memset(stats, 0, sizeof(*stats));
return SysQueryKernelStats(SYS_KERNEL_STATS_PORT, stats, sizeof(*stats), (size_t*)0);
}
// Returns a newly created listener Port.
static KSTATUS create_listener(uint32_t backlog, SYS_HANDLE* handleOut) {
SYS_CREATE_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Backlog = backlog;
request.HandleOut = handleOut;
return SysCreatePort(&request);
}
// Connects directly to one listener handle.
static KSTATUS connect_listener(SYS_HANDLE listener, SYS_HANDLE* endpointOut) {
SYS_CONNECT_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.ListenerHandle = listener;
request.EndpointHandleOut = endpointOut;
return SysConnectPort(&request);
}
// Accepts one pending connection from a listener.
static KSTATUS accept_listener(SYS_HANDLE listener,
uint64_t timeoutMilliseconds,
SYS_HANDLE* endpointOut,
SYS_PORT_CONNECTION_INFO* infoOut) {
SYS_ACCEPT_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.ListenerHandle = listener;
request.TimeoutMilliseconds = timeoutMilliseconds;
request.EndpointHandleOut = endpointOut;
request.ConnectionInfoOut = infoOut;
if (infoOut) {
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
}
return SysAcceptPort(&request);
}
// Sends one notification without transferred handles.
static KSTATUS send_notification(SYS_HANDLE endpoint,
const void* payload,
uint32_t payloadLength,
uint64_t timeoutMilliseconds) {
SYS_SEND_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.EndpointHandle = endpoint;
request.Payload = payload;
request.PayloadLength = payloadLength;
request.TimeoutMilliseconds = timeoutMilliseconds;
return SysSendPort(&request);
}
// Receives one Port message into caller-provided storage.
static KSTATUS receive_message(SYS_HANDLE endpoint,
void* payload,
uint32_t payloadCapacity,
SYS_HANDLE* handles,
uint32_t handleCapacity,
uint64_t timeoutMilliseconds,
SYS_PORT_MESSAGE_INFO* infoOut) {
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
SYS_RECEIVE_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.EndpointHandle = endpoint;
request.Payload = payload;
request.PayloadCapacity = payloadCapacity;
request.HandlesOut = handles;
request.HandleCapacity = handleCapacity;
request.InfoOut = infoOut;
request.TimeoutMilliseconds = timeoutMilliseconds;
return SysReceivePort(&request);
}
typedef struct RECEIVE_WORKER_CONTEXT {
SYS_HANDLE Endpoint;
SYS_HANDLE ReadyEvent;
KSTATUS Status;
SYS_PORT_MESSAGE_INFO Info;
char Payload[16];
} RECEIVE_WORKER_CONTEXT;
// Blocks one worker on a shared endpoint to validate multiple concurrent receivers.
static void receive_worker(uintptr_t argument) {
RECEIVE_WORKER_CONTEXT* context = (RECEIVE_WORKER_CONTEXT*)argument;
if (!context) SysExitThread(1);
memset(context->Payload, 0, sizeof(context->Payload));
if (!KSUCCESS(SysSignalEvent(context->ReadyEvent)))
SysExitThread(2);
context->Status = receive_message(context->Endpoint,
context->Payload,
(uint32_t)sizeof(context->Payload),
(SYS_HANDLE*)0,
0,
TEST_TIMEOUT_MS,
&context->Info);
SysExitThread(KSUCCESS(context->Status) ? 0 : 1);
}
// Verifies listener backlog and pending-connection teardown semantics.
static int run_connection_lifetime_test(void) {
SYS_HANDLE listener = SYS_HANDLE_INVALID;
KSTATUS status = create_listener(1, &listener);
if (!KSUCCESS(status)) return fail("create lifetime listener", status);
SYS_HANDLE client = SYS_HANDLE_INVALID;
status = connect_listener(listener, &client);
if (!KSUCCESS(status)) return fail("connect pending client", status);
SYS_HANDLE overflow = SYS_HANDLE_INVALID;
status = connect_listener(listener, &overflow);
if (status != NTLSTATUS_LIMIT_REACHED)
return fail("listener backlog", status);
status = SysCloseHandle(listener);
if (!KSUCCESS(status)) return fail("close pending listener", status);
SYS_PORT_MESSAGE_INFO info;
status = receive_message(client, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info);
if (status != NTLSTATUS_PORT_DISCONNECTED)
return fail("pending connection disconnect", status);
(void)SysCloseHandle(client);
return 0;
}
// Exercises same-process connection semantics, waitability, buffering, and queue quotas.
static int run_local_transport_test(void) {
SYS_HANDLE listener = SYS_HANDLE_INVALID;
KSTATUS status = create_listener(4, &listener);
if (!KSUCCESS(status)) return fail("create local listener", status);
SYS_HANDLE client = SYS_HANDLE_INVALID;
status = connect_listener(listener, &client);
if (!KSUCCESS(status)) return fail("connect local listener", status);
status = SysWaitSingle(listener, SYS_WAIT_FLAG_NONE, 0);
if (!KSUCCESS(status)) return fail("listener waitability", status);
SYS_PORT_CONNECTION_INFO connectionInfo;
SYS_HANDLE server = SYS_HANDLE_INVALID;
status = accept_listener(listener, 0, &server, &connectionInfo);
if (!KSUCCESS(status)) return fail("accept local connection", status);
if (connectionInfo.ClientProcessId != SysGetCurrentPid() ||
connectionInfo.ClientThreadId != SysGetCurrentTid() ||
connectionInfo.ClientSecurityId == 0)
return fail("local connection identity", NTLSTATUS_FAILURE);
SYS_PORT_STATS directBefore;
status = query_port_stats(&directBefore);
if (!KSUCCESS(status)) return fail("query direct stats before", status);
RECEIVE_WORKER_CONTEXT workers[2];
memset(workers, 0, sizeof(workers));
SYS_HANDLE workerHandles[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
for (uint32_t i = 0; i < 2; ++i) {
workers[i].Endpoint = server;
workers[i].ReadyEvent = SYS_HANDLE_INVALID;
workers[i].Status = NTLSTATUS_PENDING;
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &workers[i].ReadyEvent);
if (!KSUCCESS(status)) return fail("create receive ready event", status);
uint32_t threadId = 0;
SYS_CREATE_THREAD_REQUEST createThread = {
sizeof(createThread), (uintptr_t)receive_worker, (uintptr_t)&workers[i],
1, SYS_CREATE_THREAD_FLAG_NONE, &workerHandles[i], &threadId
};
status = SysCreateThread(&createThread);
if (!KSUCCESS(status)) return fail("create receive worker", status);
}
for (uint32_t i = 0; i < 2; ++i) {
status = SysWaitSingle(workers[i].ReadyEvent, SYS_WAIT_FLAG_NONE, TEST_TIMEOUT_MS);
if (!KSUCCESS(status)) return fail("wait receive ready event", status);
}
// The ready event is signaled immediately before ReceivePort; give each worker one scheduling turn to block.
(void)SysSleep(20);
static const char workerPayloadA[] = "worker-a";
static const char workerPayloadB[] = "worker-b";
status = send_notification(client, workerPayloadA, (uint32_t)sizeof(workerPayloadA), TEST_TIMEOUT_MS);
if (!KSUCCESS(status)) return fail("send worker payload a", status);
status = send_notification(client, workerPayloadB, (uint32_t)sizeof(workerPayloadB), TEST_TIMEOUT_MS);
if (!KSUCCESS(status)) return fail("send worker payload b", status);
for (uint32_t i = 0; i < 2; ++i) {
status = SysWait(workerHandles[i], SYS_WAIT_FLAG_NONE);
if (!KSUCCESS(status)) return fail("wait receive worker", status);
(void)SysCloseHandle(workerHandles[i]);
(void)SysCloseHandle(workers[i].ReadyEvent);
if (!KSUCCESS(workers[i].Status) || workers[i].Info.Type != SYS_PORT_MESSAGE_NOTIFICATION ||
workers[i].Info.SenderProcessId != SysGetCurrentPid())
return fail("receive worker result", workers[i].Status);
}
bool firstIsA = memcmp(workers[0].Payload, workerPayloadA, sizeof(workerPayloadA)) == 0;
bool firstIsB = memcmp(workers[0].Payload, workerPayloadB, sizeof(workerPayloadB)) == 0;
bool secondIsA = memcmp(workers[1].Payload, workerPayloadA, sizeof(workerPayloadA)) == 0;
bool secondIsB = memcmp(workers[1].Payload, workerPayloadB, sizeof(workerPayloadB)) == 0;
if (!((firstIsA && secondIsB) || (firstIsB && secondIsA)))
return fail("multiple receive payloads", NTLSTATUS_FAILURE);
SYS_PORT_STATS directAfter;
status = query_port_stats(&directAfter);
if (!KSUCCESS(status)) return fail("query direct stats after", status);
if (directAfter.DirectDeliveries < directBefore.DirectDeliveries + 2u ||
directAfter.QueuedMessages != directBefore.QueuedMessages)
return fail("direct rendezvous counters", NTLSTATUS_FAILURE);
char payload[] = "buffer-too-small";
static const char expectedPayload[] = "buffer-too-small";
status = send_notification(client, payload, (uint32_t)sizeof(payload), 0);
if (!KSUCCESS(status)) return fail("queue local notification", status);
payload[0] = 'X';
char tiny[4];
SYS_PORT_MESSAGE_INFO info;
status = receive_message(server, tiny, (uint32_t)sizeof(tiny), (SYS_HANDLE*)0, 0, 0, &info);
if (status != NTLSTATUS_BUFFER_TOO_SMALL ||
info.PayloadLength != sizeof(expectedPayload) || info.Type != SYS_PORT_MESSAGE_NOTIFICATION)
return fail("buffer-too-small contract", status);
char received[32];
memset(received, 0, sizeof(received));
status = receive_message(server, received, (uint32_t)sizeof(received), (SYS_HANDLE*)0, 0, 0, &info);
if (!KSUCCESS(status)) return fail("retry local receive", status);
if (info.SenderProcessId != SysGetCurrentPid() ||
info.SenderThreadId != SysGetCurrentTid() ||
info.SenderSecurityId == 0 ||
info.PayloadLength != sizeof(expectedPayload) ||
memcmp(received, expectedPayload, sizeof(expectedPayload)) != 0)
return fail("queued message snapshot", NTLSTATUS_FAILURE);
SYS_HANDLE sourceEvent = SYS_HANDLE_INVALID;
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &sourceEvent);
if (!KSUCCESS(status)) return fail("create queued transfer event", status);
SYS_PORT_HANDLE_TRANSFER queuedTransfer = {
sourceEvent,
SYS_RIGHT_WAIT,
SYS_PORT_TRANSFER_FLAG_DERIVE
};
SYS_SEND_PORT_REQUEST transferSend;
memset(&transferSend, 0, sizeof(transferSend));
transferSend.Size = sizeof(transferSend);
transferSend.EndpointHandle = client;
transferSend.Handles = &queuedTransfer;
transferSend.HandleCount = 1;
status = SysSendPort(&transferSend);
if (!KSUCCESS(status)) return fail("queue capability transfer", status);
status = SysCloseHandle(sourceEvent);
if (!KSUCCESS(status)) return fail("close transferred source", status);
SYS_HANDLE receivedEvent = SYS_HANDLE_INVALID;
status = receive_message(server,
(void*)0,
0,
&receivedEvent,
1,
0,
&info);
if (!KSUCCESS(status) || info.HandleCount != 1)
return fail("receive queued capability transfer", status);
status = SysWaitSingle(receivedEvent, SYS_WAIT_FLAG_NONE, 0);
if (status != NTLSTATUS_TIMEOUT)
return fail("queued transfer wait right", status);
status = SysSignalEvent(receivedEvent);
if (status != NTLSTATUS_ACCESS_DENIED)
return fail("queued transfer attenuation", status);
(void)SysCloseHandle(receivedEvent);
SYS_SEND_PORT_REQUEST oversized;
memset(&oversized, 0, sizeof(oversized));
oversized.Size = sizeof(oversized);
oversized.EndpointHandle = client;
oversized.Payload = (const void*)1;
oversized.PayloadLength = SYS_PORT_MAX_INLINE_BYTES + 1u;
status = SysSendPort(&oversized);
if (status != NTLSTATUS_INVALID_PARAMETER)
return fail("inline payload bound", status);
for (uint32_t i = 0; i < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++i) {
status = send_notification(client, (const void*)0, 0, 0);
if (!KSUCCESS(status)) return fail("fill local queue", status);
}
status = send_notification(client, (const void*)0, 0, 0);
if (status != NTLSTATUS_WOULD_BLOCK)
return fail("local queue backpressure", status);
for (uint32_t i = 0; i < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++i) {
status = receive_message(server, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info);
if (!KSUCCESS(status)) return fail("drain local queue", status);
}
// One process cannot multiply endpoint queue limits into unbounded kernel IPC storage.
SYS_HANDLE quotaClients[5] = { client, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
SYS_HANDLE quotaServers[5] = { server, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
for (uint32_t i = 1; i < 5; ++i) {
status = connect_listener(listener, &quotaClients[i]);
if (!KSUCCESS(status)) return fail("connect quota endpoint", status);
status = accept_listener(listener, 0, &quotaServers[i], (SYS_PORT_CONNECTION_INFO*)0);
if (!KSUCCESS(status)) return fail("accept quota endpoint", status);
}
SYS_PORT_STATS quotaBefore;
status = query_port_stats(&quotaBefore);
if (!KSUCCESS(status)) return fail("query quota stats before", status);
bool processQuotaHit = false;
for (uint32_t i = 0; i < 5 && !processQuotaHit; ++i) {
for (uint32_t j = 0; j < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++j) {
status = send_notification(quotaClients[i], (const void*)0, 0, 0);
if (status == NTLSTATUS_LIMIT_REACHED) {
processQuotaHit = true;
break;
}
if (!KSUCCESS(status)) return fail("fill process quota", status);
}
}
if (!processQuotaHit)
return fail("process ipc quota", NTLSTATUS_FAILURE);
SYS_PORT_STATS quotaAfter;
status = query_port_stats(&quotaAfter);
if (!KSUCCESS(status)) return fail("query quota stats after", status);
if (quotaAfter.ProcessQuotaRejects <= quotaBefore.ProcessQuotaRejects)
return fail("process quota counter", NTLSTATUS_FAILURE);
for (uint32_t i = 0; i < 5; ++i) {
while (true) {
status = receive_message(quotaServers[i], (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info);
if (status == NTLSTATUS_WOULD_BLOCK)
break;
if (!KSUCCESS(status)) return fail("drain process quota", status);
}
}
for (uint32_t i = 1; i < 5; ++i) {
(void)SysCloseHandle(quotaServers[i]);
(void)SysCloseHandle(quotaClients[i]);
}
status = SysDisconnectPort(client);
if (!KSUCCESS(status)) return fail("disconnect local endpoint", status);
status = receive_message(server, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info);
if (status != NTLSTATUS_PORT_DISCONNECTED)
return fail("local peer disconnect", status);
(void)SysCloseHandle(server);
(void)SysCloseHandle(client);
(void)SysCloseHandle(listener);
return 0;
}
// Verifies that registration privilege alone cannot publish a claimed well-known service.
static int run_claimless_client(void) {
SYS_SECURITY_CONTEXT_INFO contextInfo;
memset(&contextInfo, 0, sizeof(contextInfo));
contextInfo.Size = sizeof(contextInfo);
KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &contextInfo);
if (!KSUCCESS(status)) return 10;
if (contextInfo.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER ||
contextInfo.ServiceClaims != 0)
return 11;
SYS_HANDLE listener = SYS_HANDLE_INVALID;
status = create_listener(1, &listener);
if (!KSUCCESS(status)) return 12;
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener);
(void)SysCloseHandle(listener);
return status == NTLSTATUS_ACCESS_DENIED ? 0 : 13;
}
// Verifies that the live resource service owns its well-known registry slot and remains connectable.
static int run_service_test(void) {
SYS_HANDLE listener = SYS_HANDLE_INVALID;
KSTATUS status = create_listener(1, &listener);
if (!KSUCCESS(status)) return fail("create service probe listener", status);
status = SysRegisterSystemService(SYS_SERVICE_MAX + 1u, listener);
if (status != NTLSTATUS_INVALID_PARAMETER)
return fail("invalid service id", status);
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener);
if (status != NTLSTATUS_ALREADY_EXISTS)
return fail("resource service must already be registered", status);
SYS_HANDLE claimlessContext = SYS_HANDLE_INVALID;
SYS_DERIVE_SECURITY_CONTEXT_REQUEST deriveContext;
memset(&deriveContext, 0, sizeof(deriveContext));
deriveContext.Size = sizeof(deriveContext);
deriveContext.SourceContext = SYS_SECURITY_CONTEXT_CURRENT;
deriveContext.EffectivePrivileges = SYS_PRIV_SYSTEM_SERVICE_REGISTER;
deriveContext.ContextHandleOut = &claimlessContext;
status = SysDeriveSecurityContext(&deriveContext);
if (!KSUCCESS(status)) return fail("derive claimless service context", status);
SYS_PEB* peb = SysGetPeb();
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0)
return fail("missing image path", NTLSTATUS_FAILURE);
SYS_HANDLE claimlessChild = SYS_HANDLE_INVALID;
NTL_CREATE_SYSTEM_PROCESS_REQUEST claimlessCreate;
memset(&claimlessCreate, 0, sizeof(claimlessCreate));
claimlessCreate.Size = sizeof(claimlessCreate);
claimlessCreate.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
claimlessCreate.Path = peb->ImagePath;
claimlessCreate.CommandLine = SysSvFromCstr("claimless");
claimlessCreate.SecurityContext = claimlessContext;
claimlessCreate.ResolveModuleName = NtlResolveSystemModuleName;
claimlessCreate.ProcessHandleOut = &claimlessChild;
status = NtlCreateSystemProcess(&claimlessCreate);
(void)SysCloseHandle(claimlessContext);
if (!KSUCCESS(status)) return fail("create claimless service client", status);
status = SysWait(claimlessChild, SYS_WAIT_FLAG_NONE);
if (!KSUCCESS(status)) return fail("wait claimless service client", status);
int32_t claimlessExit = -1;
status = SysGetProcessExitCode(claimlessChild, &claimlessExit);
(void)SysCloseHandle(claimlessChild);
if (!KSUCCESS(status)) return fail("query claimless service client", status);
if (claimlessExit != 0) return fail("service claim enforcement", (KSTATUS)claimlessExit);
status = SysUnregisterSystemService(SYS_SERVICE_RESOURCE, listener);
if (status != NTLSTATUS_ACCESS_DENIED)
return fail("foreign listener cannot unregister service", status);
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
SYS_CONNECT_SYSTEM_SERVICE_REQUEST connect;
memset(&connect, 0, sizeof(connect));
connect.Size = sizeof(connect);
connect.ServiceId = SYS_SERVICE_RESOURCE;
connect.EndpointHandleOut = &endpoint;
status = SysConnectSystemService(&connect);
if (!KSUCCESS(status)) return fail("connect live resource service", status);
(void)SysDisconnectPort(endpoint);
(void)SysCloseHandle(endpoint);
(void)SysCloseHandle(listener);
return 0;
}
// Runs either the privileged parent or claimless child half.
int main(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb) return 2;
if (peb->CommandLine.Length == 9 &&
memcmp(peb->CommandLine.Data, "claimless", 9) == 0)
return run_claimless_client();
print("port_ipc_smoke: start\n");
if (run_connection_lifetime_test() != 0) return 1;
if (run_local_transport_test() != 0) return 1;
if (run_service_test() != 0) return 1;
print("port_ipc_smoke: PASS\n");
return 0;
}
@@ -0,0 +1,139 @@
// Process-construction smoke test. The same import-free PE is loaded a second time entirely by userspace.
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/pe_loader.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#define TEST_STARTUP_EVENT_TAG 0x50425445u
static void exit_now(int code) {
for (;;) SysExitProcess(code);
}
// Writes one string through the native console syscall.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Writes one 32-bit value as hexadecimal without requiring a formatting runtime.
static void print_hex32(uint32_t value) {
static const char digits[] = "0123456789ABCDEF";
char buffer[11] = "0x00000000";
for (uint32_t i = 0; i < 8; ++i)
buffer[2 + i] = digits[(value >> ((7u - i) * 4u)) & 0xFu];
print(buffer);
}
static int is_child(void) {
SYS_PEB* peb = SysGetPeb();
static const char marker[] = "process-builder-child";
return peb && peb->CommandLine.Length == sizeof(marker) - 1 &&
memcmp(peb->CommandLine.Data, marker, sizeof(marker) - 1) == 0;
}
static SYS_HANDLE find_startup(uint32_t tag) {
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION)
return SYS_HANDLE_INVALID;
for (uint32_t i = 0; i < peb->StartupCapabilityCount; ++i) {
if (peb->StartupCapabilities[i].Tag == tag)
return peb->StartupCapabilities[i].Handle;
}
return SYS_HANDLE_INVALID;
}
int main(void) {
if (is_child()) {
SYS_HANDLE event = find_startup(TEST_STARTUP_EVENT_TAG);
if (event == SYS_HANDLE_INVALID) exit_now(10);
if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11);
exit_now(0);
}
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION)
exit_now(1);
SYS_HANDLE file = SYS_HANDLE_INVALID;
KSTATUS s = NtlOpenFile(peb->ImagePath,
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&file);
if (!KSUCCESS(s)) exit_now(2);
SYS_HANDLE child = SYS_HANDLE_INVALID;
SYS_CREATE_PROCESS_OBJECT_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Flags = SYS_CREATE_PROCESS_OBJECT_FLAG_NONE;
create.ImagePath = peb->ImagePath;
create.CommandLine = SYS_SV_LITERAL("process-builder-child");
create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
create.ProcessHandleOut = &child;
s = SysCreateProcessObject(&create);
if (!KSUCCESS(s)) exit_now(3);
NTL_PE_IMAGE image;
memset(&image, 0, sizeof(image));
NTL_PE_LOAD_REQUEST load;
memset(&load, 0, sizeof(load));
load.Size = sizeof(load);
load.ProcessHandle = child;
load.FileHandle = file;
load.Flags = NTL_PE_LOAD_FLAG_NONE;
load.ImageOut = &image;
s = NtlLoadPeImage(&load);
SysCloseHandle(file);
if (!KSUCCESS(s) || !image.Base || !image.EntryPoint) {
print("process_builder_smoke: NtlLoadPeImage failed status=");
print_hex32(s);
print("\n");
exit_now(4);
}
SYS_HANDLE event = SYS_HANDLE_INVALID;
s = SysCreateEvent(0, 0, &event);
if (!KSUCCESS(s)) exit_now(5);
SYS_HANDLE childEvent = SYS_HANDLE_INVALID;
SYS_DUPLICATE_HANDLE_REQUEST duplicate;
memset(&duplicate, 0, sizeof(duplicate));
duplicate.Size = sizeof(duplicate);
duplicate.SourceHandle = event;
duplicate.TargetProcessHandle = child;
duplicate.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY;
duplicate.Flags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
duplicate.TargetHandleOut = &childEvent;
s = SysDuplicateHandle(&duplicate);
if (!KSUCCESS(s)) exit_now(6);
SYS_STARTUP_CAPABILITY startup = { TEST_STARTUP_EVENT_TAG, childEvent };
SYS_START_PROCESS_REQUEST start;
memset(&start, 0, sizeof(start));
start.Size = sizeof(start);
start.ProcessHandle = child;
start.Flags = SYS_START_PROCESS_FLAG_NONE;
start.MainImageBase = image.Base;
start.MainImageSize = image.Size;
start.EntryPoint = image.EntryPoint;
start.StartupCapabilities = &startup;
start.StartupCapabilityCount = 1;
s = SysStartProcess(&start);
if (!KSUCCESS(s)) exit_now(7);
s = SysWait(event, SYS_WAIT_FLAG_NONE);
if (!KSUCCESS(s)) exit_now(8);
s = SysWait(child, SYS_WAIT_FLAG_NONE);
if (!KSUCCESS(s)) exit_now(9);
int32_t exitCode = -1;
s = SysGetProcessExitCode(child, &exitCode);
SysCloseHandle(event);
SysCloseHandle(child);
if (!KSUCCESS(s) || exitCode != 0) exit_now(12);
return 0;
}
@@ -0,0 +1,129 @@
// Whole-process exit regression test. A blocked sibling must not keep the process alive.
#include <stdint.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/process_loader.h>
#define EVENT_AUTO_RESET 0u
#define READY_TIMEOUT_MS 1000u
#define BLOCK_SETTLE_MS 50u
#define CHILD_EXIT_TIMEOUT_MS 1000u
#define CHILD_EXIT_CODE 37
typedef struct WORKER_CONTEXT {
SYS_HANDLE ReadyEvent;
SYS_HANDLE BlockEvent;
} WORKER_CONTEXT;
static WORKER_CONTEXT gWorker;
// Writes one test message to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Terminates the current test process after one failure.
static void fail(const char* message) {
print("process_exit_multithread_smoke: ");
print(message);
print("\n");
while (true) (void)SysExitProcess(1);
}
// Returns whether one string view exactly matches one NUL-terminated string.
static int view_equals_cstr(SYS_STRING_VIEW view, const char* text) {
size_t length = strlen(text);
return view.Data && view.Length == length && memcmp(view.Data, text, length) == 0;
}
// Blocks forever so only whole-process termination can remove this sibling promptly.
static void blocked_worker(uintptr_t argument) {
WORKER_CONTEXT* context = (WORKER_CONTEXT*)argument;
if (!context)
while (true) (void)SysExitProcess(2);
if (SysSignalEvent(context->ReadyEvent) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
(void)SysWaitSingle(context->BlockEvent, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE);
// A normal wait return is impossible in the test. If it happens, fail the process.
while (true) (void)SysExitProcess(2);
}
// Runs the child half that exits while one sibling is blocked indefinitely.
static void run_child(void) {
if (SysCreateEvent(EVENT_AUTO_RESET, 0, &gWorker.ReadyEvent) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
if (SysCreateEvent(EVENT_AUTO_RESET, 0, &gWorker.BlockEvent) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
SYS_HANDLE thread = SYS_HANDLE_INVALID;
SYS_CREATE_THREAD_REQUEST request = {
sizeof(SYS_CREATE_THREAD_REQUEST),
(uintptr_t)blocked_worker,
(uintptr_t)&gWorker,
1,
SYS_CREATE_THREAD_FLAG_NONE,
&thread,
(uint32_t*)0
};
if (SysCreateThread(&request) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
if (SysWaitSingle(gWorker.ReadyEvent, SYS_WAIT_FLAG_NONE, READY_TIMEOUT_MS) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
// Give the worker a scheduling window to enter its infinite wait before
// exercising whole-process exit. This avoids testing only the syscall gate.
if (SysSleep(BLOCK_SETTLE_MS) != NTLSTATUS_SUCCESS)
while (true) (void)SysExitProcess(2);
while (true) (void)SysExitProcess(CHILD_EXIT_CODE);
}
// Launches the child half and requires whole-process exit to finish promptly.
int main(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION)
fail("invalid PEB");
if (view_equals_cstr(peb->CommandLine, "--child"))
run_child();
print("process_exit_multithread_smoke: begin\n");
SYS_HANDLE process = 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.Path = peb->ImagePath;
request.CommandLine = SysSvFromCstr("--child");
request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
request.ResolveModuleName = NtlResolveSystemModuleName;
request.ProcessHandleOut = &process;
KSTATUS status = NtlCreateSystemProcess(&request);
if (!KSUCCESS(status) || process == SYS_HANDLE_INVALID)
fail("child launch failed");
status = SysWaitSingle(process, SYS_WAIT_FLAG_NONE, CHILD_EXIT_TIMEOUT_MS);
if (status != NTLSTATUS_SUCCESS) {
(void)SysKillProcess(process, 1);
(void)SysWait(process, SYS_WAIT_FLAG_NONE);
(void)SysCloseHandle(process);
fail("child did not exit promptly");
}
int32_t exitCode = 0;
status = SysGetProcessExitCode(process, &exitCode);
(void)SysCloseHandle(process);
if (status != NTLSTATUS_SUCCESS || exitCode != CHILD_EXIT_CODE)
fail("child exit code mismatch");
print("process_exit_multithread_smoke: PASS\n");
while (true) (void)SysExitProcess(0);
}
@@ -9,7 +9,7 @@
// Prints one NUL-terminated string to the console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -30,7 +30,7 @@ static void print_u32(uint32_t value) {
while (length) {
char digit = buffer[--length];
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&digit, 1) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&digit, 1) };
(void)SysWriteConsole(&request);
}
}
@@ -53,7 +53,7 @@ static void fail_status(const char* operation, uint32_t status) {
// Verifies one protected process remains alive after a kill attempt.
static void verify_protected_process(const SYS_PROCESS_INFO* process) {
uint32_t handle = 0;
uint32_t status = SysOpenProcess(process->ProcessId, &handle);
uint32_t status = SysOpenProcess(process->ProcessId, SYS_PROCESS_RIGHT_TERMINATE | SYS_RIGHT_WAIT | SYS_RIGHT_QUERY, &handle);
if (status != NTLSTATUS_SUCCESS)
fail_status("open protected process", status);
+36 -136
View File
@@ -1,175 +1,75 @@
// Realpath ABI smoke test.
// Verifies lexical canonicalization, size reporting, and root clamping.
// Userspace realpath smoke test.
// Verifies native resource-path canonicalization and size reporting.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(req), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer to the kernel console.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current test process.
static void exit_now(int code) {
while (true) SysExitProcess(code);
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
print("realpath_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
(void)status;
print("realpath_smoke: "); print(what); print("\n");
exit_now(1);
}
// Fails the test with one plain message.
static void fail(const char* what) {
print("realpath_smoke: ");
print(what);
print("\n");
print("realpath_smoke: "); print(what); print("\n");
exit_now(1);
}
// Appends one string to a fixed test buffer.
static void append(char* out, size_t cap, const char* suffix) {
size_t len = strlen(out);
size_t add = strlen(suffix);
if (len + add + 1 > cap) fail("test buffer too small");
memcpy(out + len, suffix, add + 1);
}
// Builds the current drive root path.
static void get_current_drive_root(char* out, size_t cap) {
char current[SYS_CWD_MAX];
size_t required = 0;
SYS_REAL_PATH_REQUEST req = {
sizeof(req),
SYS_REAL_PATH_FLAG_NONE,
SysSvFromCstr("."),
current,
sizeof(current),
&required,
};
uint32_t status = SysRealPath(&req);
if (status != NTLSTATUS_SUCCESS) fail_status("query current path", status);
if (required < 4 || current[0] < '0' || current[0] > '7' || current[1] != ':' || current[2] != '/')
fail("current path is not native drive-rooted");
if (cap < 4) fail("drive root output too small");
out[0] = current[0];
out[1] = ':';
out[2] = '/';
out[3] = '\0';
}
// Resolves a path and compares the exact native string.
static void expect_realpath(const char* input, const char* expected) {
char out[SYS_CWD_MAX];
char out[SYS_PATH_MAX + 1u];
size_t required = 0;
SYS_REAL_PATH_REQUEST req = {
sizeof(req),
SYS_REAL_PATH_FLAG_NONE,
SysSvFromCstr(input),
out,
sizeof(out),
&required,
};
uint32_t status = SysRealPath(&req);
uint32_t status = NtlRealPath(SysSvFromCstr(input), out, sizeof(out), &required);
if (status != NTLSTATUS_SUCCESS) fail_status("realpath", status);
if (required != strlen(expected) + 1) fail("required size mismatch");
if (required != strlen(expected) + 1u) fail("required size mismatch");
if (strcmp(out, expected) != 0) fail("resolved path mismatch");
}
// Verifies size-only and too-small buffer behavior.
static void check_size_reporting(const char* input, const char* expected) {
size_t required = 0;
SYS_REAL_PATH_REQUEST query = {
sizeof(query),
SYS_REAL_PATH_FLAG_NONE,
SysSvFromCstr(input),
(char*)0,
0,
&required,
};
uint32_t status = SysRealPath(&query);
if (status != NTLSTATUS_SUCCESS) fail_status("size query", status);
if (required != strlen(expected) + 1) fail("size query required mismatch");
char small[4];
size_t smallRequired = 0;
SYS_REAL_PATH_REQUEST smallReq = {
sizeof(smallReq),
SYS_REAL_PATH_FLAG_NONE,
SysSvFromCstr(input),
small,
sizeof(small),
&smallRequired,
};
status = SysRealPath(&smallReq);
if (status != NTLSTATUS_BUFFER_TOO_SMALL) fail_status("small buffer", status);
if (smallRequired != required) fail("small required mismatch");
}
// Program entry point.
int main(void) {
print("realpath_smoke: start\n");
char root[4];
get_current_drive_root(root, sizeof(root));
expect_realpath("/SystemRoot//foo///bar/./", "/SystemRoot/foo/bar");
char input[SYS_CWD_MAX];
char expected[SYS_CWD_MAX];
char cwd[SYS_PATH_MAX + 1u];
size_t cwdRequired = 0;
uint32_t status = NtlGetCurrentDirectory(cwd, sizeof(cwd), &cwdRequired);
if (status != NTLSTATUS_SUCCESS) fail_status("get cwd", status);
memcpy(input, root, sizeof(root));
append(input, sizeof(input), "//foo///bar\\baz/.");
memcpy(expected, root, sizeof(root));
append(expected, sizeof(expected), "foo/bar/baz");
expect_realpath(input, expected);
check_size_reporting(input, expected);
char expected[SYS_PATH_MAX + 1u];
size_t cwdLength = strlen(cwd);
if (cwdLength + sizeof("/missing_file") > sizeof(expected)) fail("expected buffer too small");
memcpy(expected, cwd, cwdLength);
memcpy(expected + cwdLength, "/missing_file", sizeof("/missing_file"));
expect_realpath("missing_realpath_dir/../missing_file", expected);
memcpy(input, root, sizeof(root));
append(input, sizeof(input), "../../root_escape");
memcpy(expected, root, sizeof(root));
append(expected, sizeof(expected), "root_escape");
expect_realpath(input, expected);
size_t required = 0;
status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/system/init.exe"), (char*)0, 0, &required);
if (status != NTLSTATUS_SUCCESS || required != sizeof("/SystemRoot/system/init.exe"))
fail_status("size query", status);
char small[4];
size_t smallRequired = 0;
status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/system/init.exe"), small, sizeof(small), &smallRequired);
if (status != NTLSTATUS_BUFFER_TOO_SMALL || smallRequired != required)
fail_status("small buffer", status);
SYS_REAL_PATH_REQUEST currentReq = {
sizeof(currentReq),
SYS_REAL_PATH_FLAG_NONE,
SysSvFromCstr("."),
expected,
sizeof(expected),
(size_t*)0,
};
uint32_t status = SysRealPath(&currentReq);
if (status != NTLSTATUS_SUCCESS) fail_status("current realpath", status);
expect_realpath("/SystemRoot/foo/../bar", "/SystemRoot/bar");
memcpy(input, "missing_realpath_dir/../missing_file", sizeof("missing_realpath_dir/../missing_file"));
append(expected, sizeof(expected), expected[strlen(expected) - 1] == '/' ? "missing_file" : "/missing_file");
expect_realpath(input, expected);
status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/../../escape"), expected, sizeof(expected), (size_t*)0);
if (status != NTLSTATUS_ACCESS_DENIED) fail_status("resource-root traversal", status);
status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/foo\\bar"), expected, sizeof(expected), (size_t*)0);
if (status != NTLSTATUS_INVALID_PARAMETER) fail_status("native backslash", status);
print("realpath_smoke: PASS\n");
exit_now(0);
@@ -0,0 +1,200 @@
// Resource-service smoke test. Proves public RSSRV lookup is capability-based and namespace administration is private.
#include <ntlite/resource.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <stdint.h>
#include <string.h>
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Builds one /Device/HarddiskVolumeN resource path.
static SYS_STRING_VIEW make_volume_path(uint32_t index, char* buffer, size_t capacity) {
static const char prefix[] = "/Device/HarddiskVolume";
size_t prefixLength = sizeof(prefix) - 1u;
if (!index || !buffer || capacity <= prefixLength)
return SysSvFromParts((const char*)0, 0);
char digits[10];
size_t digitCount = 0;
do {
digits[digitCount++] = (char)('0' + (index % 10u));
index /= 10u;
} while (index && digitCount < sizeof(digits));
if (prefixLength + digitCount + 1u > capacity)
return SysSvFromParts((const char*)0, 0);
memcpy(buffer, prefix, prefixLength);
for (size_t i = 0; i < digitCount; ++i)
buffer[prefixLength + i] = digits[digitCount - 1u - i];
buffer[prefixLength + digitCount] = '\0';
return SysSvFromParts(buffer, prefixLength + digitCount);
}
static int fail(const char* what, KSTATUS status) {
(void)status;
print("resource_service_smoke: FAIL ");
print(what);
print("\n");
return 1;
}
int main(void) {
SYS_HANDLE session = SYS_HANDLE_INVALID;
KSTATUS status = NtlResourceConnect(&session);
if (!KSUCCESS(status)) return fail("connect", status);
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
status = NtlResourceQuery(session, SYS_SV_LITERAL("/SystemRoot"), &kind);
if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME)
return fail("query /SystemRoot", status);
// RSSRV assigns its own monotonic /Device/HarddiskVolumeN names to mounted IO_VOLUME instances.
uint32_t publishedVolumes = 0;
uint32_t systemVolumePublished = 0;
for (uint32_t index = 1; index <= 32; ++index) {
char volumePathBuffer[48];
SYS_STRING_VIEW volumePath = make_volume_path(index, volumePathBuffer, sizeof(volumePathBuffer));
if (!volumePath.Data)
return fail("build dynamic volume path", NTLSTATUS_FAILURE);
SYS_HANDLE published = SYS_HANDLE_INVALID;
status = NtlResourceLookup(session, volumePath, SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &published);
if (status == NTLSTATUS_NOT_FOUND)
continue;
if (!KSUCCESS(status))
return fail("lookup dynamic volume", status);
publishedVolumes++;
SYS_HANDLE probe = SYS_HANDLE_INVALID;
SYS_OPEN_FILE_AT_REQUEST probeOpen;
memset(&probeOpen, 0, sizeof(probeOpen));
probeOpen.Size = sizeof(probeOpen);
probeOpen.RootHandle = published;
probeOpen.Path = SYS_SV_LITERAL("system/init.exe");
probeOpen.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY;
probeOpen.Disposition = SYS_FILE_OPEN_EXISTING;
probeOpen.Flags = SYS_FILE_FLAG_NONE;
probeOpen.HandleOut = &probe;
KSTATUS probeStatus = SysOpenFileAt(&probeOpen);
if (KSUCCESS(probeStatus)) {
systemVolumePublished = 1;
(void)SysCloseHandle(probe);
} else if (probeStatus != NTLSTATUS_NOT_FOUND) {
(void)SysCloseHandle(published);
return fail("probe dynamic volume", probeStatus);
}
(void)SysCloseHandle(published);
}
if (!publishedVolumes || !systemVolumePublished)
return fail("dynamic volume publication", NTLSTATUS_NOT_FOUND);
SYS_HANDLE volume = SYS_HANDLE_INVALID;
status = NtlResourceLookup(session, SYS_SV_LITERAL("/SystemRoot"),
SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &volume);
if (!KSUCCESS(status)) return fail("lookup /SystemRoot", status);
SYS_HANDLE file = SYS_HANDLE_INVALID;
SYS_OPEN_FILE_AT_REQUEST open;
memset(&open, 0, sizeof(open));
open.Size = sizeof(open);
open.RootHandle = volume;
open.Path = SYS_SV_LITERAL("system/init.exe");
open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY;
open.Disposition = SYS_FILE_OPEN_EXISTING;
open.Flags = SYS_FILE_FLAG_NONE;
open.HandleOut = &file;
status = SysOpenFileAt(&open);
if (!KSUCCESS(status)) return fail("open through resource volume", status);
(void)SysCloseHandle(file);
(void)SysCloseHandle(volume);
// RESOLVE returns the longest bound resource prefix and leaves only a relative tail for I/O.
SYS_HANDLE resolvedRoot = SYS_HANDLE_INVALID;
uint32_t resolvedLength = 0;
status = NtlResourceResolve(session, SYS_SV_LITERAL("/SystemRoot/system/init.exe"),
NTL_RESOURCE_FLAG_NONE, &resolvedRoot, &kind, &resolvedLength);
if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME ||
resolvedLength != sizeof("/SystemRoot") - 1u)
return fail("resolve longest prefix", status);
file = SYS_HANDLE_INVALID;
memset(&open, 0, sizeof(open));
open.Size = sizeof(open);
open.RootHandle = resolvedRoot;
open.Path = SYS_SV_LITERAL("system/init.exe");
open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY;
open.Disposition = SYS_FILE_OPEN_EXISTING;
open.Flags = SYS_FILE_FLAG_NONE;
open.HandleOut = &file;
status = SysOpenFileAt(&open);
if (!KSUCCESS(status)) return fail("open through resolved prefix", status);
(void)SysCloseHandle(file);
(void)SysCloseHandle(resolvedRoot);
// MODIFY resolution returns write authority without exporting RSSRV's private TRANSFER right.
resolvedRoot = SYS_HANDLE_INVALID;
resolvedLength = 0;
status = NtlResourceResolve(session, SYS_SV_LITERAL("/SystemRoot/tests/resource_f.tmp"),
NTL_RESOURCE_RESOLVE_FLAG_MODIFY,
&resolvedRoot, &kind, &resolvedLength);
if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME ||
resolvedLength != sizeof("/SystemRoot") - 1u)
return fail("resolve modify authority", status);
file = SYS_HANDLE_INVALID;
memset(&open, 0, sizeof(open));
open.Size = sizeof(open);
open.RootHandle = resolvedRoot;
open.Path = SYS_SV_LITERAL("tests/resource_f.tmp");
open.Access = SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY;
open.Disposition = SYS_FILE_CREATE_ALWAYS;
open.Flags = SYS_FILE_FLAG_NONE;
open.HandleOut = &file;
status = SysOpenFileAt(&open);
if (!KSUCCESS(status)) return fail("create through resolved modify root", status);
(void)SysCloseHandle(file);
SYS_PATH_AT_REQUEST remove = { sizeof(remove), resolvedRoot, SYS_SV_LITERAL("tests/resource_f.tmp") };
status = SysDeleteAt(&remove);
if (!KSUCCESS(status)) return fail("delete through resolved modify root", status);
(void)SysCloseHandle(resolvedRoot);
SYS_HANDLE overbroad = SYS_HANDLE_INVALID;
status = NtlResourceLookup(session, SYS_SV_LITERAL("/SystemRoot"),
SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_VOLUME_RIGHT_OPEN, &overbroad);
if (status != NTLSTATUS_ACCESS_DENIED || overbroad != SYS_HANDLE_INVALID)
return fail("export-rights ceiling", status);
SYS_HANDLE missing = SYS_HANDLE_INVALID;
status = NtlResourceLookup(session, SYS_SV_LITERAL("/Missing"), SYS_RIGHT_QUERY, &missing);
if (status != NTLSTATUS_NOT_FOUND || missing != SYS_HANDLE_INVALID)
return fail("missing lookup", status);
SYS_HANDLE event = SYS_HANDLE_INVALID;
status = SysCreateEvent(0, 0, &event);
if (!KSUCCESS(status)) return fail("create bind probe", status);
status = NtlResourceBind(session, SYS_SV_LITERAL("/PublicBindMustFail"),
NTL_RESOURCE_KIND_CAPABILITY, event,
SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY, SYS_RIGHT_QUERY);
(void)SysCloseHandle(event);
if (status != NTLSTATUS_ACCESS_DENIED)
return fail("public bind authority", status);
status = NtlResourceQuery(session, SYS_SV_LITERAL("/PublicBindMustFail"), &kind);
if (status != NTLSTATUS_NOT_FOUND)
return fail("rejected bind visibility", status);
status = NtlResourceQuery(session, SYS_SV_LITERAL("/SystemRoot/../Bad"), &kind);
if (status != NTLSTATUS_INVALID_PARAMETER)
return fail("canonical path rejection", status);
status = NtlResourceClose(session);
if (!KSUCCESS(status)) return fail("close session", status);
print("resource_service_smoke: PASS\n");
return 0;
}
@@ -0,0 +1,319 @@
// Security-context smoke test. This validates authority attenuation, assignment, and ambient privilege enforcement.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/process_loader.h>
// Writes one string from the privileged parent process.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Writes one unsigned integer from the privileged parent process.
static void print_u32(uint32_t value) {
char digits[16];
uint32_t count = 0;
if (value == 0) {
print("0");
return;
}
while (value && count < sizeof(digits)) {
digits[count++] = (char)('0' + value % 10u);
value /= 10u;
}
while (count) {
char one[2] = { digits[--count], 0 };
print(one);
}
}
// Exits without relying on a C runtime return path.
static void exit_now(int32_t code) {
while (true) (void)SysExitProcess(code);
}
// Fails the privileged parent with a visible diagnostic.
static void fail_status(const char* operation, KSTATUS status) {
print("security_smoke: ");
print(operation);
print(" status=");
print_u32(status);
print("\n");
exit_now(1);
}
// Parses one unsigned decimal value without requiring formatted libc helpers.
static bool parse_u32(SYS_STRING_VIEW text, size_t start, uint32_t* valueOut) {
if (!valueOut || start >= text.Length) return false;
uint32_t value = 0;
bool any = false;
for (size_t i = start; i < text.Length; i++) {
char c = text.Data[i];
if (c < '0' || c > '9') return false;
uint32_t digit = (uint32_t)(c - '0');
if (value > (UINT32_MAX - digit) / 10u) return false;
value = value * 10u + digit;
any = true;
}
if (!any) return false;
*valueOut = value;
return true;
}
// Appends one unsigned decimal value to a bounded command string.
static bool append_u32(char* buffer, size_t capacity, size_t* lengthInOut, uint32_t value) {
if (!buffer || !lengthInOut || *lengthInOut >= capacity) return false;
char reverse[16];
size_t count = 0;
do {
reverse[count++] = (char)('0' + value % 10u);
value /= 10u;
} while (value && count < sizeof(reverse));
if (*lengthInOut + count + 1 > capacity) return false;
while (count) buffer[(*lengthInOut)++] = reverse[--count];
buffer[*lengthInOut] = '\0';
return true;
}
// Runs inside the stripped child and returns a distinct failure code for each leaked ambient authority.
static int run_restricted_child(uint32_t parentPid) {
SYS_SECURITY_CONTEXT_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info);
if (!KSUCCESS(status)) return 10;
if (info.EffectivePrivileges || info.DelegablePrivileges ||
info.ServiceClaims || info.DelegableServiceClaims)
return 11;
SYS_HANDLE process = SYS_HANDLE_INVALID;
status = SysOpenProcess(parentPid, SYS_RIGHT_QUERY, &process);
if (status != NTLSTATUS_ACCESS_DENIED) return 12;
size_t processCount = 0;
status = SysGetProcessList((SYS_PROCESS_INFO*)0, 0, &processCount);
if (status != NTLSTATUS_ACCESS_DENIED) return 13;
size_t required = 0;
status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, (void*)0, 0, &required);
if (status != NTLSTATUS_ACCESS_DENIED) return 14;
SYS_HANDLE inputQueue = SYS_HANDLE_INVALID;
status = SysOpenInputQueue(0, &inputQueue);
if (status != NTLSTATUS_ACCESS_DENIED) return 15;
SYS_DISPLAY_INFO displayInfo;
memset(&displayInfo, 0, sizeof(displayInfo));
status = SysQueryDisplayInfo(&displayInfo);
if (status != NTLSTATUS_ACCESS_DENIED) return 16;
SYS_HANDLE socketHandle = SYS_HANDLE_INVALID;
status = SysCreateSocket(0, 0, 0, &socketHandle);
if (status != NTLSTATUS_ACCESS_DENIED) return 17;
SYS_PRINT_DEBUG_REQUEST debugRequest = { sizeof(debugRequest), SysSvFromParts((const char*)0, 0) };
status = SysPrintDebug(&debugRequest);
if (status != NTLSTATUS_ACCESS_DENIED) return 18;
SYS_WRITE_CONSOLE_REQUEST consoleRequest = { sizeof(consoleRequest), SysSvFromParts((const char*)0, 0) };
status = SysWriteConsole(&consoleRequest);
if (status != NTLSTATUS_ACCESS_DENIED) return 19;
SYS_PEB* peb = SysGetPeb();
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) return 20;
SYS_HANDLE file = SYS_HANDLE_INVALID;
status = NtlOpenFile(peb->ImagePath,
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&file);
if (!KSUCCESS(status)) return 21;
(void)SysCloseHandle(file);
uint32_t endpointCount = 0;
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST enumerateAudio;
memset(&enumerateAudio, 0, sizeof(enumerateAudio));
enumerateAudio.Size = sizeof(enumerateAudio);
enumerateAudio.CountOut = &endpointCount;
status = SysAudioEnumerateEndpoints(&enumerateAudio);
if (status != NTLSTATUS_ACCESS_DENIED) return 22;
SYS_HANDLE queue = SYS_HANDLE_INVALID;
status = SysCreateQueue(&queue);
if (!KSUCCESS(status)) return 23;
SYS_PUBLISH_QUEUE_REQUEST publishQueue = {
sizeof(publishQueue), SysSvFromCstr("security_smoke.restricted"), queue
};
status = SysPublishQueue(&publishQueue);
(void)SysCloseHandle(queue);
if (status != NTLSTATUS_ACCESS_DENIED) return 24;
SYS_HANDLE listener = SYS_HANDLE_INVALID;
SYS_CREATE_PORT_REQUEST createPort;
memset(&createPort, 0, sizeof(createPort));
createPort.Size = sizeof(createPort);
createPort.HandleOut = &listener;
status = SysCreatePort(&createPort);
if (!KSUCCESS(status)) return 25;
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener);
(void)SysCloseHandle(listener);
if (status != NTLSTATUS_ACCESS_DENIED) return 26;
uint32_t selfPid = SysGetCurrentPid();
status = SysOpenProcess(selfPid, SYS_RIGHT_QUERY, &process);
if (!KSUCCESS(status)) return 27;
status = SysCloseHandle(process);
if (!KSUCCESS(status)) return 28;
return 0;
}
// Creates one zero-authority security context from the caller's current delegable root.
static SYS_HANDLE derive_stripped_context(void) {
SYS_HANDLE handle = SYS_HANDLE_INVALID;
SYS_DERIVE_SECURITY_CONTEXT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT;
request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE;
request.ContextHandleOut = &handle;
KSTATUS status = SysDeriveSecurityContext(&request);
if (!KSUCCESS(status)) fail_status("derive stripped context", status);
return handle;
}
// Runs the privileged parent half of the security-context smoke test.
static int run_parent(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0)
fail_status("missing image path", NTLSTATUS_FAILURE);
SYS_SECURITY_CONTEXT_INFO rootInfo;
memset(&rootInfo, 0, sizeof(rootInfo));
rootInfo.Size = sizeof(rootInfo);
KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &rootInfo);
if (!KSUCCESS(status)) fail_status("query current context", status);
if (rootInfo.EffectivePrivileges != SYS_PRIV_ALL ||
rootInfo.DelegablePrivileges != SYS_PRIV_ALL ||
rootInfo.ServiceClaims != SYS_SERVICE_CLAIM_ALL ||
rootInfo.DelegableServiceClaims != SYS_SERVICE_CLAIM_ALL)
fail_status("bootstrap context authority", NTLSTATUS_FAILURE);
SYS_HANDLE restricted = derive_stripped_context();
SYS_SECURITY_CONTEXT_INFO restrictedInfo;
memset(&restrictedInfo, 0, sizeof(restrictedInfo));
restrictedInfo.Size = sizeof(restrictedInfo);
status = SysQuerySecurityContext(restricted, &restrictedInfo);
if (!KSUCCESS(status)) fail_status("query stripped context", status);
if (restrictedInfo.SecurityId <= rootInfo.SecurityId ||
restrictedInfo.EffectivePrivileges || restrictedInfo.DelegablePrivileges ||
restrictedInfo.ServiceClaims || restrictedInfo.DelegableServiceClaims)
fail_status("stripped context contents", NTLSTATUS_FAILURE);
SYS_HANDLE illegal = SYS_HANDLE_INVALID;
SYS_DERIVE_SECURITY_CONTEXT_REQUEST amplify;
memset(&amplify, 0, sizeof(amplify));
amplify.Size = sizeof(amplify);
amplify.SourceContext = restricted;
amplify.EffectivePrivileges = SYS_PRIV_PROCESS_OPEN_ANY;
amplify.ContextHandleOut = &illegal;
status = SysDeriveSecurityContext(&amplify);
if (status != NTLSTATUS_ACCESS_DENIED)
fail_status("blocked privilege amplification", status);
memset(&amplify, 0, sizeof(amplify));
amplify.Size = sizeof(amplify);
amplify.SourceContext = restricted;
amplify.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE;
amplify.ContextHandleOut = &illegal;
status = SysDeriveSecurityContext(&amplify);
if (status != NTLSTATUS_ACCESS_DENIED)
fail_status("blocked service-claim amplification", status);
SYS_HANDLE queryOnly = SYS_HANDLE_INVALID;
SYS_DERIVE_HANDLE_REQUEST deriveHandle = {
sizeof(deriveHandle), restricted, SYS_RIGHT_QUERY, &queryOnly
};
status = SysDeriveHandle(&deriveHandle);
if (!KSUCCESS(status)) fail_status("derive query-only context handle", status);
SYS_HANDLE rejectedChild = SYS_HANDLE_INVALID;
NTL_CREATE_SYSTEM_PROCESS_REQUEST rejectedCreate;
memset(&rejectedCreate, 0, sizeof(rejectedCreate));
rejectedCreate.Size = sizeof(rejectedCreate);
rejectedCreate.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
rejectedCreate.Path = peb->ImagePath;
rejectedCreate.CommandLine = SysSvFromCstr("restricted:1");
rejectedCreate.SecurityContext = queryOnly;
rejectedCreate.ResolveModuleName = NtlResolveSystemModuleName;
rejectedCreate.ProcessHandleOut = &rejectedChild;
status = NtlCreateSystemProcess(&rejectedCreate);
if (status != NTLSTATUS_ACCESS_DENIED)
fail_status("context assignment right", status);
(void)SysCloseHandle(queryOnly);
char command[48] = "restricted:";
size_t commandLength = strlen(command);
if (!append_u32(command, sizeof(command), &commandLength, SysGetCurrentPid()))
fail_status("build child command", NTLSTATUS_BUFFER_TOO_SMALL);
SYS_HANDLE child = 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.Path = peb->ImagePath;
create.CommandLine = SysSvFromCstr(command);
create.SecurityContext = restricted;
create.ResolveModuleName = NtlResolveSystemModuleName;
create.ProcessHandleOut = &child;
status = NtlCreateSystemProcess(&create);
if (!KSUCCESS(status)) fail_status("create restricted child", status);
status = SysWait(child, SYS_WAIT_FLAG_NONE);
if (!KSUCCESS(status)) fail_status("wait restricted child", status);
int32_t childExit = -1;
status = SysGetProcessExitCode(child, &childExit);
if (!KSUCCESS(status)) fail_status("query restricted child", status);
(void)SysCloseHandle(child);
if (childExit != 0) {
print("security_smoke: restricted child failure=");
print_u32((uint32_t)childExit);
print("\n");
return 1;
}
status = SysCloseHandle(restricted);
if (!KSUCCESS(status)) fail_status("close stripped context", status);
SYS_HANDLE later = derive_stripped_context();
SYS_SECURITY_CONTEXT_INFO laterInfo;
memset(&laterInfo, 0, sizeof(laterInfo));
laterInfo.Size = sizeof(laterInfo);
status = SysQuerySecurityContext(later, &laterInfo);
if (!KSUCCESS(status)) fail_status("query later context", status);
if (laterInfo.SecurityId <= restrictedInfo.SecurityId)
fail_status("security id reuse", NTLSTATUS_FAILURE);
(void)SysCloseHandle(later);
print("security_smoke: PASS\n");
return 0;
}
// Runs either the privileged parent or the stripped child based on the process command line.
int main(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb) return 2;
const char prefix[] = "restricted:";
if (peb->CommandLine.Length > sizeof(prefix) - 1u &&
memcmp(peb->CommandLine.Data, prefix, sizeof(prefix) - 1u) == 0) {
uint32_t parentPid = 0;
if (!parse_u32(peb->CommandLine, sizeof(prefix) - 1u, &parentPid)) return 3;
return run_restricted_child(parentPid);
}
return run_parent();
}
+155 -30
View File
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#define PAGE_SIZE_BYTES 4096u
#define TEST_SIZE (PAGE_SIZE_BYTES * 3u)
@@ -14,7 +15,7 @@ static uint8_t gExpected[TEST_SIZE];
static uint8_t gReadback[TEST_SIZE];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -32,6 +33,45 @@ static void print_u64(uint64_t v) {
}
}
// Prints one pointer-sized value in hexadecimal for VM alias diagnostics.
static void print_hex_uintptr(uintptr_t value) {
static const char digits[] = "0123456789abcdef";
char text[2 + sizeof(uintptr_t) * 2 + 1];
size_t pos = 0;
text[pos++] = '0';
text[pos++] = 'x';
for (int shift = (int)(sizeof(uintptr_t) * 8u - 4u); shift >= 0; shift -= 4)
text[pos++] = digits[(value >> (uint32_t)shift) & 0xFu];
text[pos] = 0;
print(text);
}
// Prints the VM region containing one address without requiring physical-address disclosure.
static void print_vm_region(const char* name, const void* address) {
SYS_VM_REGION_INFO info;
memset(&info, 0, sizeof(info));
uint32_t status = SysQueryVm((void*)address, &info);
print("shared_mmap: vm ");
print(name);
print(" address=");
print_hex_uintptr((uintptr_t)address);
if (status != NTLSTATUS_SUCCESS) {
print(" query-status=");
print_u64(status);
print("\n");
return;
}
print(" base=");
print_hex_uintptr((uintptr_t)info.Base);
print(" pages=");
print_u64(info.PageCount);
print(" type=");
print_u64(info.Type);
print("\n");
}
static void exit_now(int code) {
while (true) SysExitProcess(code);
}
@@ -52,33 +92,48 @@ static void fail_status(const char* what, uint32_t status) {
exit_now(1);
}
// Reports the first byte mismatch before terminating the regression test.
static void fail_mismatch(const char* what, const uint8_t* actual, const uint8_t* expected, size_t size) {
for (size_t i = 0; i < size; i++) {
uint8_t actualByte = actual[i];
uint8_t expectedByte = expected[i];
if (actualByte == expectedByte) continue;
print("shared_mmap: ");
print(what);
print(" offset=");
print_u64(i);
print(" expected=");
print_u64(expectedByte);
print(" actual=");
print_u64(actualByte);
print("\n");
exit_now(1);
}
fail(what);
}
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
memset(out, 0, sizeof(*out));
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
}
static void delete_path(const char* path) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
(void)SysDeleteFile(&req);
(void)NtlDeleteFile(SysSvFromCstr(path));
}
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
static const char* paths[] = {
"shared_mmap.tmp",
"/shared_mmap.tmp",
"1:/shared_mmap.tmp",
"0:/shared_mmap.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
uint32_t h = 0;
delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
if (pathOut) *pathOut = paths[i];
@@ -90,12 +145,11 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
static uint32_t open_path(const char* path, uint32_t* fileOut) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
uint32_t s = NtlOpenFile(SysSvFromCstr(path),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS)
*fileOut = h;
return s;
@@ -123,9 +177,54 @@ static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOu
if (gotOut) *gotOut = got;
}
// Returns the deterministic initial byte used to seed the mapped file.
static uint8_t initial_byte(size_t index) {
return (uint8_t)(0x20u + (uint8_t)(index * 7u + (index >> 5)));
}
// Returns the expected byte after the first shared-mapping write range.
static uint8_t expected_after_first_mapped_write(size_t index) {
if (index >= 128u && index < 128u + 3000u)
return (uint8_t)(0xA0u + (uint8_t)index);
return initial_byte(index);
}
// Returns the expected byte after both shared-mapping write ranges.
static uint8_t expected_after_mapped_writes(size_t index) {
if (index >= 128u && index < 128u + 3000u)
return (uint8_t)(0xA0u + (uint8_t)index);
if (index >= PAGE_SIZE_BYTES + 17u && index < PAGE_SIZE_BYTES + 17u + 2048u)
return (uint8_t)(0x50u + (uint8_t)(index >> 1));
return initial_byte(index);
}
// Verifies the mutable expected buffer against the deterministic write pattern.
static void verify_expected_buffer(const char* stage, int secondRangeWritten) {
for (size_t i = 0; i < TEST_SIZE; ++i) {
uint8_t actualByte = gExpected[i];
uint8_t expectedByte = secondRangeWritten
? expected_after_mapped_writes(i)
: expected_after_first_mapped_write(i);
if (actualByte == expectedByte)
continue;
print("shared_mmap: expected buffer corrupted ");
print(stage);
print(" offset=");
print_u64(i);
print(" expected=");
print_u64(expectedByte);
print(" actual=");
print_u64(actualByte);
print("\n");
exit_now(1);
}
}
// Fills the initial file data and its comparison buffer.
static void fill_initial(void) {
for (size_t i = 0; i < TEST_SIZE; ++i) {
gInitial[i] = (uint8_t)(0x20u + (uint8_t)(i * 7u + (i >> 5)));
gInitial[i] = initial_byte(i);
gExpected[i] = gInitial[i];
}
}
@@ -148,31 +247,53 @@ int main(void) {
if (s != NTLSTATUS_SUCCESS) fail_status("initial flush", s);
uint32_t mapping = 0;
s = SysCreateFileMapping(file, SYS_VM_READWRITE, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping);
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READWRITE,
SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping
};
s = SysCreateFileMapping(&createMapping);
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s);
uint8_t* view = 0;
s = SysMapFile(mapping, 0, TEST_SIZE, SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE,
SYS_MAP_FLAG_NONE, (void**)&view);
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mapping,
SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE, SYS_MAP_FLAG_NONE,
0, TEST_SIZE, (void**)&view
};
s = SysMapFile(&mapFile);
if (s != NTLSTATUS_SUCCESS) fail_status("map shared write", s);
print_vm_region("view", view);
print_vm_region("expected", gExpected);
print_vm_region("readback", gReadback);
// Keep the source value live across the faulting mapped store. This also
// verifies that a repaired page fault preserves the interrupted SIMD/GPR state.
for (size_t i = 128; i < 128 + 3000; ++i) {
view[i] = (uint8_t)(0xA0u + (uint8_t)i);
gExpected[i] = view[i];
uint8_t value = (uint8_t)(0xA0u + (uint8_t)i);
view[i] = value;
gExpected[i] = value;
}
verify_expected_buffer("after first mapped write range", 0);
for (size_t i = PAGE_SIZE_BYTES + 17; i < PAGE_SIZE_BYTES + 17 + 2048; ++i) {
view[i] = (uint8_t)(0x50u + (uint8_t)(i >> 1));
gExpected[i] = view[i];
uint8_t value = (uint8_t)(0x50u + (uint8_t)(i >> 1));
view[i] = value;
gExpected[i] = value;
}
verify_expected_buffer("after mapped writes", 1);
SYS_FILE_CACHE_STATS beforeFlush;
SYS_FILE_CACHE_STATS afterFlush;
if (query_cache(&beforeFlush) != NTLSTATUS_SUCCESS) fail("pre-flush stats failed");
verify_expected_buffer("after pre-flush stats", 1);
s = SysFlushFile(file);
if (s != NTLSTATUS_SUCCESS) fail_status("flush mapped writes", s);
verify_expected_buffer("after mapped flush", 1);
if (query_cache(&afterFlush) != NTLSTATUS_SUCCESS) fail("post-flush stats failed");
verify_expected_buffer("after post-flush stats", 1);
if (afterFlush.MappedDirtyPages <= beforeFlush.MappedDirtyPages)
fail("mapped dirty pages were not harvested");
if (afterFlush.WritebackBytes <= beforeFlush.WritebackBytes)
@@ -181,12 +302,15 @@ int main(void) {
checked_seek(file, 0, "seek after mapped flush");
size_t got = 0;
checked_read(file, gReadback, TEST_SIZE, &got, "read after mapped flush");
verify_expected_buffer("after mapped-flush read", 1);
if (got != TEST_SIZE) fail("read after mapped flush size mismatch");
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("mapped flush data mismatch");
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0)
fail_mismatch("mapped flush data mismatch", gReadback, gExpected, TEST_SIZE);
for (size_t i = PAGE_SIZE_BYTES * 2 + 3; i < PAGE_SIZE_BYTES * 2 + 900; ++i) {
view[i] = (uint8_t)(0xC0u ^ (uint8_t)i);
gExpected[i] = view[i];
uint8_t value = (uint8_t)(0xC0u ^ (uint8_t)i);
view[i] = value;
gExpected[i] = value;
}
s = SysFreeVm(view);
@@ -206,7 +330,8 @@ int main(void) {
checked_read(file, gReadback, TEST_SIZE, &got, "read after close");
if (got != TEST_SIZE) fail("read after close size mismatch");
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("unmap/close data mismatch");
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0)
fail_mismatch("unmap/close data mismatch", gReadback, gExpected, TEST_SIZE);
s = SysCloseHandle(file);
file = 0;
@@ -8,7 +8,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -26,7 +26,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -67,6 +67,7 @@ static void check_ipv4_resolve(void) {
SYS_RESOLVE_HOST_REQUEST req;
memset(&address, 0, sizeof(address));
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Name = SysSvFromCstr("127.0.0.1");
req.Family = SYS_AF_INET;
req.AddressOut = &address;
@@ -87,6 +88,7 @@ static void check_ipv6_resolve(void) {
SYS_RESOLVE_HOST_REQUEST req;
memset(&address, 0, sizeof(address));
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Name = SysSvFromCstr("::1");
req.Family = SYS_AF_INET6;
req.AddressOut = &address;
@@ -8,7 +8,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -26,7 +26,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -92,11 +92,34 @@ static void check_fixed_stats(void) {
if (filesystem.Size != sizeof(filesystem)) fail("filesystem size field mismatch");
if (filesystem.Version != SYS_STATS_VERSION) fail("filesystem version mismatch");
SYS_VOLUME_STATS volumeStats;
status = SysQueryKernelStats(SYS_KERNEL_STATS_VOLUME, &volumeStats, sizeof(volumeStats), 0);
if (status != NTLSTATUS_SUCCESS) fail_status("volume exact", status);
if (volumeStats.Size != sizeof(volumeStats)) fail("volume size field mismatch");
if (volumeStats.Version != SYS_STATS_VERSION) fail("volume version mismatch");
if (volumeStats.CreatedVolumes == 0) fail("volume created count is zero");
if (volumeStats.MountedVolumes == 0) fail("volume mounted count is zero");
if (volumeStats.ProviderDestroys > volumeStats.RemovedVolumes) fail("volume destroy count exceeds removals");
SYS_VOLUME_MANAGER_STATS volumeManagerStats;
status = SysQueryKernelStats(SYS_KERNEL_STATS_VOLUME_MANAGER, &volumeManagerStats, sizeof(volumeManagerStats), 0);
if (status != NTLSTATUS_SUCCESS) fail_status("volume manager exact", status);
if (volumeManagerStats.Size != sizeof(volumeManagerStats)) fail("volume manager size field mismatch");
if (volumeManagerStats.Version != SYS_STATS_VERSION) fail("volume manager version mismatch");
if (volumeManagerStats.Snapshots == 0) fail("volume manager snapshot count is zero");
if (volumeManagerStats.VolumeOpens == 0) fail("volume manager open count is zero");
SYS_EXCEPTION_STATS exceptionStats;
status = SysQueryKernelStats(SYS_KERNEL_STATS_EXCEPTION, &exceptionStats, sizeof(exceptionStats), 0);
if (status != NTLSTATUS_SUCCESS) fail_status("exception exact", status);
if (exceptionStats.Size != sizeof(exceptionStats)) fail("exception size field mismatch");
if (exceptionStats.Version != SYS_STATS_VERSION) fail("exception version mismatch");
SYS_PORT_STATS portStats;
status = SysQueryKernelStats(SYS_KERNEL_STATS_PORT, &portStats, sizeof(portStats), 0);
if (status != NTLSTATUS_SUCCESS) fail_status("port exact", status);
if (portStats.Size != sizeof(portStats)) fail("port size field mismatch");
if (portStats.Version != SYS_STATS_VERSION) fail("port version mismatch");
}
// Verifies CPU stats use a size-reported trailing-record layout.
@@ -10,7 +10,7 @@
#define SURFACE_H 24u
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -24,7 +24,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -23,7 +23,7 @@ typedef struct WORKER_STATE {
static WORKER_STATE gWorkers[THREAD_COUNT];
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -37,7 +37,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -126,7 +126,7 @@ int main(void) {
gWorkers[i].Pitch = pitch;
gWorkers[i].Status = NTLSTATUS_PENDING;
SYS_CREATE_THREAD_REQUEST req = {
(uintptr_t)worker_main,
sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)worker_main,
(uintptr_t)&gWorkers[i],
1,
0,
@@ -10,7 +10,7 @@
#define SURFACE_H 48u
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -24,7 +24,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -1,15 +1,16 @@
// Native syscall contract smoke test.
// Verifies public tick units and process exit-code round trips.
// Verifies public syscall ABI contracts, tick units, and process exit-code round trips.
#include <stdint.h>
#include <stddef.h>
#include <ntlite/syscall.h>
#include <ntlite/process_loader.h>
#define CHILD_EXIT_CODE ((int32_t)-1234567)
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -27,7 +28,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -89,13 +90,17 @@ static void check_exit_code_contract(void) {
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) fail("missing image path");
uint32_t child = 0;
SYS_CREATE_PROCESS_REQUEST req = {
peb->ImagePath,
SysSvFromCstr("child"),
&child,
};
NTL_CREATE_SYSTEM_PROCESS_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
req.Path = peb->ImagePath;
req.CommandLine = SysSvFromCstr("child");
req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
req.ResolveModuleName = NtlResolveSystemModuleName;
req.ProcessHandleOut = &child;
uint32_t s = SysCreateProcess(&req);
uint32_t s = NtlCreateSystemProcess(&req);
if (s != NTLSTATUS_SUCCESS) fail_status("create child", s);
s = SysWait(child, SYS_WAIT_FLAG_NONE);
@@ -109,6 +114,22 @@ static void check_exit_code_contract(void) {
SysCloseHandle(child);
}
// Verifies request structures reject stale layouts instead of accepting compatibility prefixes.
static void check_request_size_contract(void) {
SYS_PRINT_DEBUG_REQUEST request = {
sizeof(SYS_PRINT_DEBUG_REQUEST) - 1u,
SysSvFromCstr("size check must not print"),
};
uint32_t s = SysPrintDebug(&request);
if (s != NTLSTATUS_INVALID_PARAMETER)
fail_status("undersized request was accepted", s);
request.Size = sizeof(SYS_PRINT_DEBUG_REQUEST) + 8u;
s = SysPrintDebug(&request);
if (s != NTLSTATUS_INVALID_PARAMETER)
fail_status("oversized request was accepted", s);
}
// Runs the syscall ABI contract smoke test.
int main(void) {
SYS_PEB* peb = SysGetPeb();
@@ -118,6 +139,7 @@ int main(void) {
print("syscall_contract_smoke: start\n");
check_tick_contract();
check_request_size_contract();
check_exit_code_contract();
print("syscall_contract_smoke: PASS\n");
exit_now(0);
+2 -2
View File
@@ -6,7 +6,7 @@
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -24,7 +24,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -47,7 +47,7 @@ static uint32_t gWorkerFailure;
// Writes one test message to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -16,7 +16,7 @@ static volatile uint32_t gRaisedHits;
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -34,7 +34,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -13,7 +13,7 @@ __declspec(dllimport) void* GetProcAddress(HANDLE module, const char* procedureN
// Writes one test message to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -36,7 +36,7 @@ static BOOL gWorkerRelease;
// Writes one test message to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -29,7 +29,7 @@ static volatile uint32_t gVehHits;
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -47,7 +47,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -30,7 +30,7 @@ static volatile long gRelease;
// Writes one test message to the native console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -133,7 +133,7 @@ static volatile long gCloseCallbacks;
// Writes one NUL-terminated string to the kernel console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
@@ -49,7 +49,7 @@ static uint32_t recv_queue(uint32_t queue, TEST_QUEUE_MESSAGE* msg, uint64_t tim
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
@@ -67,7 +67,7 @@ static void print_u64(uint64_t v) {
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
@@ -172,7 +172,7 @@ static void test_queue_infinite_receive_wakeup(void) {
uint32_t threadHandle = 0;
uint32_t threadId = 0;
SYS_CREATE_THREAD_REQUEST req = {
(uintptr_t)sender_thread,
sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)sender_thread,
(uintptr_t)&ctx,
1,
0,