212 lines
8.1 KiB
C
212 lines
8.1 KiB
C
// Native syscall contract smoke test.
|
|
// Verifies public syscall ABI contracts, tick units, and process exit-code round trips.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <ntlite/process_loader.h>
|
|
|
|
#define CHILD_EXIT_CODE ((int32_t)-1234567)
|
|
|
|
static void print(const char* s) {
|
|
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
|
|
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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
}
|
|
|
|
static void exit_now(int32_t code) {
|
|
while (true) SysExitProcess(code);
|
|
}
|
|
|
|
// Checks whether a string view equals a NUL-terminated string.
|
|
static bool view_equals_cstr(SYS_STRING_VIEW view, const char* text) {
|
|
size_t n = 0;
|
|
while (text[n]) n++;
|
|
if (view.Length != n || (!view.Data && n != 0)) return false;
|
|
for (size_t i = 0; i < n; i++) {
|
|
if (view.Data[i] != text[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void fail(const char* msg) {
|
|
print("syscall_contract_smoke: ");
|
|
print(msg);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
static void fail_status(const char* what, KSTATUS status) {
|
|
print("syscall_contract_smoke: ");
|
|
print(what);
|
|
print(" status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Verifies the documented tick unit without relying on wall-clock precision.
|
|
static void check_tick_contract(void) {
|
|
uint64_t frequency = 0;
|
|
KSTATUS s = SysGetTickFrequency(&frequency);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("query tick frequency", s);
|
|
if (!frequency) fail("tick frequency was zero");
|
|
|
|
uint64_t t0 = SysGetTick();
|
|
s = SysSleep(1);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("sleep", s);
|
|
|
|
uint64_t t1 = SysGetTick();
|
|
if (t1 < t0) fail("tick moved backwards across sleep");
|
|
if (SysTicksToMicroseconds(t1 - t0, frequency) < 1000ULL)
|
|
fail("tick delta was smaller than one millisecond");
|
|
}
|
|
|
|
// Verifies process exit-code storage, wait, and query agree on int32_t.
|
|
static void check_exit_code_contract(void) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) fail("missing image path");
|
|
|
|
uint32_t child = 0;
|
|
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;
|
|
|
|
KSTATUS s = NtlCreateSystemProcess(&req);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create child", s);
|
|
|
|
s = SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("wait child", s);
|
|
|
|
int32_t exitCode = 0;
|
|
s = SysGetProcessExitCode(child, &exitCode);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("query child exit code", s);
|
|
if (exitCode != CHILD_EXIT_CODE) fail("child exit code mismatch");
|
|
|
|
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"),
|
|
};
|
|
KSTATUS 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);
|
|
}
|
|
|
|
// Exercises strict Unicode scalar boundaries independent of the filesystem.
|
|
static void check_utf8_contract(void) {
|
|
static const char valid[] = { 'A', (char)0xC3, (char)0xA9,
|
|
(char)0xE2, (char)0x82, (char)0xAC,
|
|
(char)0xF4, (char)0x8F, (char)0xBF, (char)0xBF };
|
|
static const char overlong[] = { (char)0xC0, (char)0xAF };
|
|
static const char surrogate[] = { (char)0xED, (char)0xA0, (char)0x80 };
|
|
static const char tooLarge[] = { (char)0xF4, (char)0x90, (char)0x80, (char)0x80 };
|
|
static const char truncated[] = { (char)0xE2, (char)0x82 };
|
|
static const char nul[] = { 'A', 0, 'B' };
|
|
SYS_STRING_VIEW good = SysSvFromParts(valid, sizeof(valid));
|
|
if (!SysSvIsUtf8Text(good)) { print("utf8: valid rejected\n"); exit_now(1); }
|
|
if (SysSvIsUtf8(SysSvFromParts(overlong, sizeof(overlong))) ||
|
|
SysSvIsUtf8(SysSvFromParts(surrogate, sizeof(surrogate))) ||
|
|
SysSvIsUtf8(SysSvFromParts(tooLarge, sizeof(tooLarge))) ||
|
|
SysSvIsUtf8(SysSvFromParts(truncated, sizeof(truncated))) ||
|
|
SysSvIsUtf8Text(SysSvFromParts(nul, sizeof(nul)))) {
|
|
print("utf8: invalid accepted\n"); exit_now(1);
|
|
}
|
|
if (!SysSvIsUtf8(SysSvFromParts(nul, sizeof(nul))) ||
|
|
!SysSvIsUtf8Text(SYS_SV_NULL) ||
|
|
SysSvIsUtf8(SysSvFromParts((const char*)0, 1u))) {
|
|
print("utf8: byte span mismatch\n"); exit_now(1);
|
|
}
|
|
// ext4 enumeration exposes lossy UTF-8 only for display; no raw byte
|
|
// sequence or partial multibyte scalar may leak into a directory entry.
|
|
static const char invalidName[] = { 'a', (char)0xFF, 'b' };
|
|
static const char displayName[] = { 'a', (char)0xEF, (char)0xBF, (char)0xBD, 'b', 0 };
|
|
char converted[16];
|
|
size_t copied = SysSvCopyUtf8WithReplacement(
|
|
SysSvFromParts(invalidName, sizeof(invalidName)), converted, sizeof(converted));
|
|
if (copied != sizeof(displayName) - 1u || memcmp(converted, displayName, sizeof(displayName)) != 0 ||
|
|
!SysSvIsUtf8Text(SysSvFromParts(converted, copied))) {
|
|
print("utf8: replacement projection mismatch\n"); exit_now(1);
|
|
}
|
|
char shortName[5];
|
|
copied = SysSvCopyUtf8WithReplacement(
|
|
SysSvFromParts(invalidName, sizeof(invalidName)), shortName, sizeof(shortName));
|
|
if (copied != 4u || shortName[4] != '\0' ||
|
|
!SysSvIsUtf8Text(SysSvFromParts(shortName, copied))) {
|
|
print("utf8: replacement truncation split a scalar\n"); exit_now(1);
|
|
}
|
|
static const char truncatedScalar[] = { 'x', (char)0xE2, (char)0x82 };
|
|
copied = SysSvCopyUtf8WithReplacement(
|
|
SysSvFromParts(truncatedScalar, sizeof(truncatedScalar)), converted, sizeof(converted));
|
|
if (copied != 7u || !SysSvIsUtf8Text(SysSvFromParts(converted, copied))) {
|
|
print("utf8: truncated sequence escaped replacement\n"); exit_now(1);
|
|
}
|
|
copied = SysSvCopyUtf8WithReplacement(good, converted, sizeof(converted));
|
|
if (copied != sizeof(valid) || memcmp(converted, valid, sizeof(valid)) != 0) {
|
|
print("utf8: valid scalars modified by replacement\n"); exit_now(1);
|
|
}
|
|
static const char embeddedNul[] = { 'a', 0, 'b' };
|
|
copied = SysSvCopyUtf8WithReplacement(
|
|
SysSvFromParts(embeddedNul, sizeof(embeddedNul)), converted, sizeof(converted));
|
|
if (copied != 5u || !SysSvIsUtf8Text(SysSvFromParts(converted, copied))) {
|
|
print("utf8: embedded NUL escaped replacement\n"); exit_now(1);
|
|
}
|
|
static const char composed[] = { (char)0xC3, (char)0xA9 };
|
|
static const char decomposed[] = { 'e', (char)0xCC, (char)0x81 };
|
|
if (!SysSvIsUtf8Text(SysSvFromParts(decomposed, sizeof(decomposed))) ||
|
|
SysSvEqual(SysSvFromParts(composed, sizeof(composed)),
|
|
SysSvFromParts(decomposed, sizeof(decomposed)))) {
|
|
print("utf8: identity was normalized\n"); exit_now(1);
|
|
}
|
|
}
|
|
|
|
// Runs the syscall ABI contract smoke test.
|
|
int main(void) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (peb && view_equals_cstr(peb->CommandLine, "child")) {
|
|
exit_now(CHILD_EXIT_CODE);
|
|
}
|
|
|
|
print("syscall_contract_smoke: start\n");
|
|
check_utf8_contract();
|
|
check_tick_contract();
|
|
check_request_size_contract();
|
|
check_exit_code_contract();
|
|
print("syscall_contract_smoke: PASS\n");
|
|
exit_now(0);
|
|
return 0;
|
|
}
|