replace ad hoc driver loading with a proper driver manager, new DDK, new PnP manager with device matching, asynchronous startup, resource ownership and removal, add shared PCI capability helpers, replace typed, rigid services with a proper service manager, implement proper system shutdown and restart, shutting down unloads drivers and shuts down services, add simple power button, remove heavyweight boot self-tests and SMP stress runs, add monotonic timestamps to kernel debug output, fruitless USB and PS/2 fixes, make test builds opt in.
This commit is contained in:
1 parent
c0ad63c27f
commit
d36f19509c
160 files changed
+6563
-7366
No files matched your search
@@ -29,6 +29,7 @@ int main(void) {
|
||||
NTL_CONSOLE_INFO console;
|
||||
SYS_MEMORY_STATUS mem;
|
||||
char metricsBuf[128];
|
||||
size_t previousLength = 0;
|
||||
|
||||
while (true) {
|
||||
if (NtlConsoleQueryInfo(SYS_HANDLE_INVALID, &console) == NTLSTATUS_SUCCESS &&
|
||||
@@ -52,22 +53,22 @@ int main(void) {
|
||||
strcat(metricsBuf, "K ]");
|
||||
|
||||
size_t textLen = strlen(metricsBuf);
|
||||
// Erase any trailing characters left by a longer previous sample.
|
||||
if (previousLength > textLen && previousLength < sizeof(metricsBuf)) {
|
||||
memset(metricsBuf + textLen, ' ', previousLength - textLen);
|
||||
textLen = previousLength;
|
||||
}
|
||||
int targetX = columns - (int)textLen;
|
||||
int targetY = 0;
|
||||
|
||||
if (targetX >= 0) {
|
||||
size_t written = 0;
|
||||
|
||||
// Loop through the string and use the console's dedicated text filling API
|
||||
// This utilizes the console's native font configuration instead of drawing raw screen bits
|
||||
for (size_t i = 0; i < textLen; i++) {
|
||||
(void)NtlConsoleFillCharacter(SYS_HANDLE_INVALID, metricsBuf[i],
|
||||
targetX + (int)i, targetY, 1, &written);
|
||||
}
|
||||
if (NtlConsoleWriteAt(SYS_HANDLE_INVALID, targetX, 0,
|
||||
metricsBuf, textLen, &written) == NTLSTATUS_SUCCESS &&
|
||||
written == textLen)
|
||||
previousLength = textLen;
|
||||
}
|
||||
}
|
||||
|
||||
SysSleep(500);
|
||||
SysSleep(2000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -863,6 +863,8 @@ static void cmd_help() {
|
||||
" del <file> - Delete a file\n"
|
||||
" write <file> <tx> - Create a file with text\n"
|
||||
" clear - Clear the screen\n"
|
||||
" shutdown - Request system shutdown\n"
|
||||
" reboot - Request system restart\n"
|
||||
" exit - Close the shell\n");
|
||||
}
|
||||
|
||||
@@ -2203,6 +2205,13 @@ static KSTATUS waitForegroundProcess(uint32_t hProc) {
|
||||
// Run one command.
|
||||
static bool runCommand(const char* cmd) {
|
||||
if (!cmd || *cmd == '\0') return false;
|
||||
if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) {
|
||||
uint32_t action = strcmp(cmd, "reboot") == 0
|
||||
? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN;
|
||||
KSTATUS st = SysRequestSystemPower(action);
|
||||
if (!KSUCCESS(st)) print("power: request failed\n");
|
||||
return false;
|
||||
}
|
||||
if (strcmp(cmd, "exit") == 0) return true;
|
||||
if (strcmp(cmd, "help") == 0) { cmd_help(); return false; }
|
||||
if (strcmp(cmd, "clear") == 0) { (void)NtlConsoleClear(SYS_HANDLE_INVALID, 0x012456); return false; }
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ntlite/posix_extensions.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define MAX_CMD 256
|
||||
|
||||
@@ -80,6 +81,8 @@ static void cmd_help() {
|
||||
" del <file> - Delete a file\n"
|
||||
" write <file> <txt> - Create a file with text\n"
|
||||
" clear - Clear the screen\n"
|
||||
" shutdown - Request system shutdown\n"
|
||||
" reboot - Request system restart\n"
|
||||
" exit - Close the shell\n");
|
||||
}
|
||||
|
||||
@@ -196,6 +199,13 @@ static void cmd_type(const char* filename) {
|
||||
// Runs command.
|
||||
static bool runCommand(const char* cmd) {
|
||||
if (!cmd || *cmd == '\0') return false;
|
||||
if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) {
|
||||
uint32_t action = strcmp(cmd, "reboot") == 0
|
||||
? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN;
|
||||
KSTATUS st = SysRequestSystemPower(action);
|
||||
if (!KSUCCESS(st)) print("power: request failed\n");
|
||||
return false;
|
||||
}
|
||||
if (strcmp(cmd, "exit") == 0) return true;
|
||||
if (strcmp(cmd, "help") == 0) { cmd_help(); return false; }
|
||||
if (strcmp(cmd, "clear") == 0) { clearscreen(); return false; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// User application entry points for shell_win32.
|
||||
|
||||
#include <windows.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
@@ -76,6 +77,8 @@ static void cmd_help() {
|
||||
" del <file> - Delete a file\n"
|
||||
" write <file> <tx> - Create a file with text\n"
|
||||
" clear - Clear the screen\n"
|
||||
" shutdown - Request system shutdown\n"
|
||||
" reboot - Request system restart\n"
|
||||
" exit - Close the shell\n");
|
||||
}
|
||||
|
||||
@@ -211,6 +214,13 @@ static void cmd_type(const char* filename) {
|
||||
// Runs command.
|
||||
static bool runCommand(const char* cmd) {
|
||||
if (!cmd || *cmd == '\0') return false;
|
||||
if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) {
|
||||
uint32_t action = strcmp(cmd, "reboot") == 0
|
||||
? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN;
|
||||
KSTATUS st = SysRequestSystemPower(action);
|
||||
if (!KSUCCESS(st)) print("power: request failed\n");
|
||||
return false;
|
||||
}
|
||||
if (strcmp(cmd, "exit") == 0) return true;
|
||||
if (strcmp(cmd, "help") == 0) { cmd_help(); return false; }
|
||||
if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return false; }
|
||||
|
||||
@@ -247,8 +247,7 @@ static KSTATUS PosixProcessDeriveSecurityContext(SYS_HANDLE* contextOut) {
|
||||
info.Size = sizeof(info);
|
||||
KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
if ((info.EffectivePrivileges & ~info.DelegablePrivileges) != 0 ||
|
||||
(info.ServiceClaims & ~info.DelegableServiceClaims) != 0)
|
||||
if ((info.EffectivePrivileges & ~info.DelegablePrivileges) != 0)
|
||||
return NTLSTATUS_ACCESS_DENIED;
|
||||
|
||||
SYS_DERIVE_SECURITY_CONTEXT_REQUEST derive;
|
||||
@@ -257,8 +256,6 @@ static KSTATUS PosixProcessDeriveSecurityContext(SYS_HANDLE* contextOut) {
|
||||
derive.SourceContext = SYS_SECURITY_CONTEXT_CURRENT;
|
||||
derive.EffectivePrivileges = info.EffectivePrivileges;
|
||||
derive.DelegablePrivileges = info.DelegablePrivileges;
|
||||
derive.ServiceClaims = info.ServiceClaims;
|
||||
derive.DelegableServiceClaims = info.DelegableServiceClaims;
|
||||
derive.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE;
|
||||
derive.ContextHandleOut = contextOut;
|
||||
return SysDeriveSecurityContext(&derive);
|
||||
|
||||
@@ -152,6 +152,27 @@ static KSTATUS NtlConsoleSimpleCall(SYS_HANDLE session, NTL_CONSOLE_REQUEST* req
|
||||
return status;
|
||||
}
|
||||
|
||||
// Sends one positioned text span in a single console request.
|
||||
KSTATUS NtlConsoleWriteAt(SYS_HANDLE session, int32_t x, int32_t y,
|
||||
const char* text, size_t length, size_t* writtenOut) {
|
||||
if (writtenOut) *writtenOut = 0;
|
||||
if (!text || length > NTL_CONSOLE_WRITE_MAX - 8u || x < 0 || y < 0)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
NTL_CONSOLE_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.Version = NTL_CONSOLE_PROTOCOL_VERSION;
|
||||
request.Operation = NTL_CONSOLE_OP_WRITE_AT;
|
||||
request.Data.WriteAt.X = x;
|
||||
request.Data.WriteAt.Y = y;
|
||||
request.Data.WriteAt.Length = (uint32_t)length;
|
||||
memcpy(request.Data.WriteAt.Data, text, length);
|
||||
NTL_CONSOLE_REPLY reply;
|
||||
KSTATUS status = NtlConsoleCall(session, &request, &reply);
|
||||
if (writtenOut) *writtenOut = reply.Written;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Updates the logical console cursor.
|
||||
KSTATUS NtlConsoleSetCursor(SYS_HANDLE session, int32_t x, int32_t y) {
|
||||
NTL_CONSOLE_REQUEST request;
|
||||
|
||||
@@ -219,12 +219,12 @@ KSTATUS NtlPrepareProcess(const NTL_PREPARE_PROCESS_REQUEST* request,
|
||||
memset(state->ChildCapabilities, 0, sizeof(state->ChildCapabilities));
|
||||
for (uint32_t i = 0; i < request->StartupCapabilityCount; ++i) {
|
||||
const NTL_PROCESS_STARTUP_CAPABILITY* source = &request->StartupCapabilities[i];
|
||||
if (!source->Tag || source->SourceHandle == SYS_HANDLE_INVALID) {
|
||||
if (!SysStartupCapabilityNameValid(source->Name) || source->SourceHandle == SYS_HANDLE_INVALID) {
|
||||
status = NTLSTATUS_INVALID_PARAMETER;
|
||||
goto fail;
|
||||
}
|
||||
for (uint32_t j = 0; j < i; ++j) {
|
||||
if (state->ChildCapabilities[j].Tag == source->Tag) {
|
||||
if (strcmp(state->ChildCapabilities[j].Name, source->Name) == 0) {
|
||||
status = NTLSTATUS_INVALID_PARAMETER;
|
||||
goto fail;
|
||||
}
|
||||
@@ -240,7 +240,7 @@ KSTATUS NtlPrepareProcess(const NTL_PREPARE_PROCESS_REQUEST* request,
|
||||
duplicate.TargetHandleOut = &childHandle;
|
||||
status = SysDuplicateHandle(&duplicate);
|
||||
if (!KSUCCESS(status)) goto fail;
|
||||
state->ChildCapabilities[i].Tag = source->Tag;
|
||||
memcpy(state->ChildCapabilities[i].Name, source->Name, sizeof(source->Name));
|
||||
state->ChildCapabilities[i].Handle = childHandle;
|
||||
}
|
||||
state->StartupCapabilityCount = request->StartupCapabilityCount;
|
||||
|
||||
@@ -160,6 +160,33 @@ KSTATUS NtlResourceConnectListener(SYS_HANDLE listenerHandle, SYS_HANDLE* sessio
|
||||
return SysConnectPort(&request);
|
||||
}
|
||||
|
||||
// Delegates the authority to bind or unbind exactly one resource path.
|
||||
KSTATUS NtlResourceCreatePublisher(SYS_HANDLE adminSession, SYS_STRING_VIEW path,
|
||||
SYS_HANDLE* listenerOut) {
|
||||
if (!listenerOut) return NTLSTATUS_INVALID_PARAMETER;
|
||||
*listenerOut = SYS_HANDLE_INVALID;
|
||||
uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX];
|
||||
uint32_t length = 0;
|
||||
KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload),
|
||||
NTL_RESOURCE_OP_CREATE_PUBLISHER, path,
|
||||
SysSvFromParts((const char*)0, 0), 0, 0,
|
||||
NTL_RESOURCE_KIND_INVALID, &length);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
NTL_RESOURCE_REPLY reply;
|
||||
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
||||
status = NtlCallResource(adminSession, payload, length, (const SYS_PORT_HANDLE_TRANSFER*)0,
|
||||
0, &reply, &handle);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
if (reply.Operation != NTL_RESOURCE_OP_CREATE_PUBLISHER ||
|
||||
!KSUCCESS(reply.Status) || handle == SYS_HANDLE_INVALID) {
|
||||
if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle);
|
||||
return reply.Operation == NTL_RESOURCE_OP_CREATE_PUBLISHER && !KSUCCESS(reply.Status)
|
||||
? reply.Status : NTLSTATUS_FAILURE;
|
||||
}
|
||||
*listenerOut = handle;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Looks up one resource and requests an attenuated alias of its bound capability.
|
||||
KSTATUS NtlResourceLookup(SYS_HANDLE session,
|
||||
SYS_STRING_VIEW path,
|
||||
|
||||
@@ -186,4 +186,9 @@ SYS_SET_SOCKET_OPTION EQU 159
|
||||
SYS_GET_SOCKET_OPTION EQU 160
|
||||
SYS_POLL EQU 161
|
||||
|
||||
SYS_MAX EQU 162
|
||||
SYS_OPEN_POWER_EVENT EQU 162
|
||||
SYS_REQUEST_SYSTEM_POWER EQU 163
|
||||
SYS_QUERY_SYSTEM_POWER EQU 164
|
||||
SYS_COMPLETE_SYSTEM_POWER EQU 165
|
||||
|
||||
SYS_MAX EQU 166
|
||||
@@ -38,6 +38,10 @@ ENDM
|
||||
; ---------------------------------------------------------------------------
|
||||
; Process & thread lifecycle
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB OpenPowerEvent, SYS_OPEN_POWER_EVENT
|
||||
SYSCALL_STUB RequestSystemPower, SYS_REQUEST_SYSTEM_POWER
|
||||
SYSCALL_STUB QuerySystemPower, SYS_QUERY_SYSTEM_POWER
|
||||
SYSCALL_STUB CompleteSystemPower, SYS_COMPLETE_SYSTEM_POWER
|
||||
SYSCALL_STUB ExitProcess, SYS_EXIT_PROCESS
|
||||
SYSCALL_STUB ExitThread, SYS_EXIT_THREAD
|
||||
SYSCALL_STUB CreateProcessObject, SYS_CREATE_PROCESS_OBJECT
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Native Session 0 desktop/window manager. DWM owns window policy, composition, and visible cursor presentation.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ntlite/bootstrap_capabilities.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/desktop.h>
|
||||
#include <ntlite/resource.h>
|
||||
#include <ntlite/exception.h>
|
||||
#include <ntlite/internal/session_control.h>
|
||||
#include <ntlite/port.h>
|
||||
@@ -1156,10 +1158,11 @@ static KSTATUS DwmSendReady(SYS_HANDLE endpoint, SYS_HANDLE desktopListener) {
|
||||
message.Operation = NTL_SERVICE_CONTROL_READY;
|
||||
message.ExportCount = 1u;
|
||||
message.Status = NTLSTATUS_SUCCESS;
|
||||
if (!NtlServiceControlNameExport(&message, 0u, "desktop")) return NTLSTATUS_FAILURE;
|
||||
|
||||
SYS_PORT_HANDLE_TRANSFER transfer;
|
||||
transfer.Handle = desktopListener;
|
||||
transfer.DesiredRights = SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfer.DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
||||
|
||||
SYS_SEND_PORT_REQUEST send;
|
||||
@@ -1196,8 +1199,7 @@ static void DwmControlLoop(SYS_HANDLE endpoint) {
|
||||
if (status == NTLSTATUS_PORT_DISCONNECTED || !KSUCCESS(status)) return;
|
||||
if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 ||
|
||||
info.PayloadLength != sizeof(message) || message.Size != sizeof(message) ||
|
||||
message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 ||
|
||||
message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0)
|
||||
!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u))
|
||||
continue;
|
||||
if (message.Operation == NTL_SERVICE_CONTROL_STOP) return;
|
||||
}
|
||||
@@ -1255,15 +1257,15 @@ void _start(void) {
|
||||
memset(&security, 0, sizeof(security));
|
||||
security.Size = sizeof(security);
|
||||
status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security);
|
||||
if (!KSUCCESS(status) || security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0 ||
|
||||
security.ServiceClaims != 0 || security.DelegableServiceClaims != 0)
|
||||
if (!KSUCCESS(status) || security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0)
|
||||
DwmExit(2);
|
||||
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL);
|
||||
SYS_HANDLE displayManager = SysGetStartupCapability(SYS_STARTUP_CAP_DISPLAY_MANAGER);
|
||||
SYS_HANDLE sessionListener = SysGetStartupCapability(SYS_STARTUP_CAP_SESSION_CONTROL);
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability("service.control");
|
||||
SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_PRESENT);
|
||||
SYS_HANDLE sessionListener = SysGetStartupCapability("rssrv.session-control");
|
||||
SYS_HANDLE publicationListener = SysGetStartupCapability("rssrv.desktop-publisher");
|
||||
if (controlListener == SYS_HANDLE_INVALID || displayManager == SYS_HANDLE_INVALID ||
|
||||
sessionListener == SYS_HANDLE_INVALID)
|
||||
sessionListener == SYS_HANDLE_INVALID || publicationListener == SYS_HANDLE_INVALID)
|
||||
DwmExit(3);
|
||||
|
||||
memset(&gDwm, 0, sizeof(gDwm));
|
||||
@@ -1322,10 +1324,21 @@ void _start(void) {
|
||||
DwmUnlock();
|
||||
}
|
||||
|
||||
SYS_HANDLE publication = SYS_HANDLE_INVALID;
|
||||
status = NtlResourceConnectListener(publicationListener, &publication);
|
||||
(void)SysCloseHandle(publicationListener);
|
||||
if (!KSUCCESS(status)) DwmExit(8);
|
||||
status = NtlResourceBind(publication, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH),
|
||||
NTL_RESOURCE_KIND_CAPABILITY, gDwm.DesktopListener,
|
||||
SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT,
|
||||
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT);
|
||||
if (!KSUCCESS(status)) DwmExit(8);
|
||||
status = DwmSendReady(controlEndpoint, gDwm.DesktopListener);
|
||||
if (!KSUCCESS(status)) DwmExit(8);
|
||||
|
||||
DwmControlLoop(controlEndpoint);
|
||||
(void)NtlResourceUnbind(publication, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH));
|
||||
(void)NtlResourceClose(publication);
|
||||
gDwm.Running = 0u;
|
||||
(void)SysSignalEvent(gDwm.DirtyEvent);
|
||||
(void)SysDisconnectPort(gDwm.SessionEndpoint);
|
||||
|
||||
+347
-714
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,27 @@
|
||||
// Service restart budget: three automatic attempts in any sliding five-minute interval.
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct INIT_RESTART_BUDGET {
|
||||
uint64_t Attempts[3];
|
||||
uint32_t Count;
|
||||
} INIT_RESTART_BUDGET;
|
||||
|
||||
static inline bool InitConsumeRestartBudget(INIT_RESTART_BUDGET* budget,
|
||||
uint64_t now, uint64_t frequency) {
|
||||
if (!budget || !frequency || budget->Count > 3u ||
|
||||
frequency > UINT64_MAX / 300u) return false;
|
||||
uint64_t window = frequency * 300u;
|
||||
uint32_t keep = 0;
|
||||
for (uint32_t i = 0; i < budget->Count; ++i) {
|
||||
uint64_t started = budget->Attempts[i];
|
||||
if (now >= started && now - started < window)
|
||||
budget->Attempts[keep++] = started;
|
||||
}
|
||||
budget->Count = keep;
|
||||
if (keep == 3u) return false;
|
||||
budget->Attempts[budget->Count++] = now;
|
||||
return true;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
// Init service-configuration parser. Malformed or ambiguous service policy is rejected at boot.
|
||||
|
||||
#include "service_config.h"
|
||||
#include <ntlite/bootstrap_capabilities.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/status.h>
|
||||
|
||||
#define INIT_SERVICE_KEY_TYPE 0x00000001u
|
||||
#define INIT_SERVICE_KEY_IMAGE 0x00000002u
|
||||
#define INIT_SERVICE_KEY_START 0x00000004u
|
||||
#define INIT_SERVICE_KEY_CRITICAL 0x00000008u
|
||||
@@ -14,7 +14,9 @@
|
||||
#define INIT_SERVICE_KEY_READY_TIMEOUT 0x00000020u
|
||||
#define INIT_SERVICE_KEY_STOP_TIMEOUT 0x00000040u
|
||||
#define INIT_SERVICE_KEY_REQUIRES 0x00000080u
|
||||
#define INIT_SERVICE_REQUIRED_KEYS 0x000000FFu
|
||||
#define INIT_SERVICE_KEY_GRANTS 0x00000100u
|
||||
#define INIT_SERVICE_KEY_EXPORTS 0x00000200u
|
||||
#define INIT_SERVICE_REQUIRED_KEYS 0x000003FEu
|
||||
|
||||
typedef struct INIT_PARSED_SERVICE {
|
||||
INIT_SERVICE_DESCRIPTOR Descriptor;
|
||||
@@ -155,17 +157,65 @@ static bool parse_requires(char* value, INIT_PARSED_SERVICE* service) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses explicit capability-source names without associating them with service roles.
|
||||
static bool parse_grants(char* value, INIT_PARSED_SERVICE* service) {
|
||||
service->Descriptor.GrantCount = 0;
|
||||
if (!*value) return true;
|
||||
char* cursor = value;
|
||||
for (;;) {
|
||||
char* comma = strchr(cursor, ',');
|
||||
if (comma) *comma = '\0';
|
||||
char* name = trim_left(cursor);
|
||||
trim_right(name);
|
||||
size_t length = strlen(name);
|
||||
if (length == 0 || length > INIT_SERVICE_GRANT_NAME_MAX ||
|
||||
service->Descriptor.GrantCount >= INIT_SERVICE_GRANT_MAX) return false;
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
if (!is_service_id_char(name[i])) return false;
|
||||
if (!strchr(name, '.') || name[0] == '.' || name[length - 1u] == '.') return false;
|
||||
if (strcmp(name, "service.control") == 0 || strncmp(name, "publish.", 8u) == 0)
|
||||
return false;
|
||||
for (uint32_t i = 0; i < service->Descriptor.GrantCount; ++i)
|
||||
if (strcmp(service->Descriptor.Grants[i], name) == 0) return false;
|
||||
if (!copy_bounded(service->Descriptor.Grants[service->Descriptor.GrantCount],
|
||||
sizeof(service->Descriptor.Grants[0]), name)) return false;
|
||||
++service->Descriptor.GrantCount;
|
||||
if (!comma) break;
|
||||
cursor = comma + 1;
|
||||
if (!*cursor) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads the declared READY export names, which are an exact startup contract.
|
||||
static bool parse_exports(char* value, INIT_PARSED_SERVICE* service) {
|
||||
service->Descriptor.ExportCount = 0;
|
||||
if (!*value) return true;
|
||||
char* cursor = value;
|
||||
for (;;) {
|
||||
char* comma = strchr(cursor, ',');
|
||||
if (comma) *comma = '\0';
|
||||
char* name = trim_left(cursor);
|
||||
trim_right(name);
|
||||
if (!NtlServiceExportNameValid(name) ||
|
||||
service->Descriptor.ExportCount >= INIT_SERVICE_EXPORT_MAX) return false;
|
||||
for (uint32_t i = 0; i < service->Descriptor.ExportCount; ++i)
|
||||
if (strcmp(name, service->Descriptor.Exports[i]) == 0) return false;
|
||||
if (!copy_bounded(service->Descriptor.Exports[service->Descriptor.ExportCount],
|
||||
sizeof(service->Descriptor.Exports[0]), name)) return false;
|
||||
++service->Descriptor.ExportCount;
|
||||
if (!comma) break;
|
||||
cursor = comma + 1;
|
||||
if (!*cursor) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Applies one known key to the current service and rejects duplicates or invalid values.
|
||||
static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* value) {
|
||||
uint32_t bit = 0;
|
||||
|
||||
if (strcmp(key, "type") == 0) {
|
||||
bit = INIT_SERVICE_KEY_TYPE;
|
||||
if (strcmp(value, "resource") == 0) service->Descriptor.Type = InitServiceTypeResource;
|
||||
else if (strcmp(value, "dwm") == 0) service->Descriptor.Type = InitServiceTypeDwm;
|
||||
else if (strcmp(value, "generic") == 0) service->Descriptor.Type = InitServiceTypeGeneric;
|
||||
else return false;
|
||||
} else if (strcmp(key, "image") == 0) {
|
||||
if (strcmp(key, "image") == 0) {
|
||||
bit = INIT_SERVICE_KEY_IMAGE;
|
||||
if (!validate_image_path(value) ||
|
||||
!copy_bounded(service->Descriptor.ImagePath, sizeof(service->Descriptor.ImagePath), value))
|
||||
@@ -192,6 +242,12 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val
|
||||
} else if (strcmp(key, "requires") == 0) {
|
||||
bit = INIT_SERVICE_KEY_REQUIRES;
|
||||
if (!parse_requires(value, service)) return false;
|
||||
} else if (strcmp(key, "grants") == 0) {
|
||||
bit = INIT_SERVICE_KEY_GRANTS;
|
||||
if (!parse_grants(value, service)) return false;
|
||||
} else if (strcmp(key, "exports") == 0) {
|
||||
bit = INIT_SERVICE_KEY_EXPORTS;
|
||||
if (!parse_exports(value, service)) return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -270,7 +326,8 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
line = saved ? end + 1u : end;
|
||||
}
|
||||
|
||||
if (serviceCount == 0) return NTLSTATUS_INVALID_PARAMETER;
|
||||
if (serviceCount == 0)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
*serviceCountOut = serviceCount;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
@@ -279,14 +336,59 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
static KSTATUS validate_services(uint32_t serviceCount,
|
||||
INIT_SERVICE_CONFIG* configOut,
|
||||
uint32_t* errorLineOut) {
|
||||
uint32_t resourceCount = 0;
|
||||
uint16_t resourceIndex = 0;
|
||||
uint32_t dwmCount = 0;
|
||||
uint16_t dwmIndex = UINT16_MAX;
|
||||
|
||||
memset(configOut, 0, sizeof(*configOut));
|
||||
configOut->ResourceServiceIndex = UINT16_MAX;
|
||||
configOut->DwmServiceIndex = UINT16_MAX;
|
||||
|
||||
static const char* const bootGrants[] = {
|
||||
NTL_BOOT_CAP_NAMESPACE_ROOT, NTL_BOOT_CAP_VOLUME_MANAGER, NTL_BOOT_CAP_INPUT_MANAGER,
|
||||
NTL_BOOT_CAP_DISPLAY_POLICY, NTL_BOOT_CAP_DISPLAY_PRESENT, NTL_BOOT_CAP_RESOURCE_REGISTRATION
|
||||
};
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
INIT_SERVICE_DESCRIPTOR* descriptor = &gParsedServices[i].Descriptor;
|
||||
for (uint32_t g = 0; g < descriptor->GrantCount; ++g) {
|
||||
const char* name = descriptor->Grants[g];
|
||||
bool known = false;
|
||||
for (uint32_t b = 0; b < sizeof(bootGrants) / sizeof(bootGrants[0]); ++b)
|
||||
if (strcmp(name, bootGrants[b]) == 0) known = true;
|
||||
if (known) continue;
|
||||
if (strncmp(name, "boot.", 5u) == 0) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
// Service export sources require an explicit dependency; the longest
|
||||
// matching service ID owns a qualified export name.
|
||||
int32_t provider = -1;
|
||||
size_t prefixLength = 0;
|
||||
for (uint32_t j = 0; j < serviceCount; ++j) {
|
||||
const char* id = gParsedServices[j].Descriptor.Id;
|
||||
size_t length = strlen(id);
|
||||
if (length > prefixLength && strncmp(name, id, length) == 0 &&
|
||||
name[length] == '.' && name[length + 1u] != '\0') {
|
||||
provider = (int32_t)j;
|
||||
prefixLength = length;
|
||||
}
|
||||
}
|
||||
bool declaredDependency = false;
|
||||
if (provider >= 0 && (uint32_t)provider != i &&
|
||||
gParsedServices[provider].Descriptor.StartPolicy == InitServiceStartAuto) {
|
||||
for (uint32_t d = 0; d < gParsedServices[i].DependencyCount; ++d)
|
||||
if (strcmp(gParsedServices[i].DependencyIds[d],
|
||||
gParsedServices[provider].Descriptor.Id) == 0)
|
||||
declaredDependency = true;
|
||||
}
|
||||
if (declaredDependency) {
|
||||
const char* exportName = name + prefixLength + 1u;
|
||||
bool declaredExport = false;
|
||||
const INIT_SERVICE_DESCRIPTOR* source = &gParsedServices[provider].Descriptor;
|
||||
for (uint32_t e = 0; e < source->ExportCount; ++e)
|
||||
if (strcmp(source->Exports[e], exportName) == 0) declaredExport = true;
|
||||
if (!declaredExport) declaredDependency = false;
|
||||
}
|
||||
if (!declaredDependency) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
INIT_PARSED_SERVICE* parsed = &gParsedServices[i];
|
||||
@@ -297,6 +399,13 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
|
||||
INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i];
|
||||
*descriptor = parsed->Descriptor;
|
||||
for (uint32_t e = 0; e < descriptor->ExportCount; ++e) {
|
||||
if (strlen(descriptor->Id) + 1u + strlen(descriptor->Exports[e]) >
|
||||
INIT_SERVICE_GRANT_NAME_MAX) {
|
||||
if (errorLineOut) *errorLineOut = parsed->SectionLine;
|
||||
return NTLSTATUS_NAME_TOO_LONG;
|
||||
}
|
||||
}
|
||||
descriptor->DependencyCount = parsed->DependencyCount;
|
||||
for (uint32_t d = 0; d < parsed->DependencyCount; ++d) {
|
||||
int32_t dependency = find_parsed_service(gParsedServices, serviceCount, parsed->DependencyIds[d]);
|
||||
@@ -306,48 +415,6 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
descriptor->Dependencies[d] = (uint16_t)dependency;
|
||||
}
|
||||
|
||||
if (descriptor->Type == InitServiceTypeResource) {
|
||||
++resourceCount;
|
||||
resourceIndex = (uint16_t)i;
|
||||
} else if (descriptor->Type == InitServiceTypeDwm) {
|
||||
++dwmCount;
|
||||
dwmIndex = (uint16_t)i;
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceCount != 1u || dwmCount > 1u) return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
INIT_SERVICE_DESCRIPTOR* resource = &configOut->Services[resourceIndex];
|
||||
if (resource->StartPolicy != InitServiceStartAuto || !resource->Critical ||
|
||||
resource->RestartPolicy != InitServiceRestartNever || resource->DependencyCount != 0u) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[resourceIndex].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (dwmCount == 1u) {
|
||||
INIT_SERVICE_DESCRIPTOR* dwm = &configOut->Services[dwmIndex];
|
||||
bool requiresResource = false;
|
||||
for (uint32_t d = 0; d < dwm->DependencyCount; ++d)
|
||||
if (dwm->Dependencies[d] == resourceIndex) requiresResource = true;
|
||||
if (dwm->StartPolicy == InitServiceStartAuto &&
|
||||
(dwm->Critical || dwm->RestartPolicy != InitServiceRestartNever || !requiresResource)) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[dwmIndex].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
// The generic adapter publishes READY listeners through RSSRV, so automatic generic services require it.
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
INIT_SERVICE_DESCRIPTOR* generic = &configOut->Services[i];
|
||||
if (generic->Type != InitServiceTypeGeneric || generic->StartPolicy != InitServiceStartAuto) continue;
|
||||
bool requiresResource = false;
|
||||
for (uint32_t d = 0; d < generic->DependencyCount; ++d)
|
||||
if (generic->Dependencies[d] == resourceIndex) requiresResource = true;
|
||||
if (generic->Critical || generic->RestartPolicy != InitServiceRestartNever || !requiresResource) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
@@ -406,8 +473,8 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
|
||||
configOut->ServiceCount = serviceCount;
|
||||
configOut->ResourceServiceIndex = resourceIndex;
|
||||
configOut->DwmServiceIndex = dwmIndex;
|
||||
if (configOut->StartupCount + 2u > SYS_WAIT_MANY_MAX_HANDLES)
|
||||
return NTLSTATUS_LIMIT_REACHED;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,16 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include <ntlite/service_control.h>
|
||||
|
||||
#define INIT_SERVICES_PATH "system/services.ini"
|
||||
#define INIT_SERVICES_MAX_FILE_BYTES (64u * 1024u)
|
||||
#define INIT_SERVICE_MAX_COUNT 32u
|
||||
#define INIT_SERVICE_ID_MAX 31u
|
||||
#define INIT_SERVICE_DEPENDENCY_MAX 8u
|
||||
|
||||
typedef enum INIT_SERVICE_TYPE {
|
||||
InitServiceTypeResource = 0,
|
||||
InitServiceTypeDwm,
|
||||
InitServiceTypeGeneric,
|
||||
} INIT_SERVICE_TYPE;
|
||||
#define INIT_SERVICE_GRANT_MAX 8u
|
||||
#define INIT_SERVICE_GRANT_NAME_MAX SYS_STARTUP_CAPABILITY_NAME_MAX
|
||||
#define INIT_SERVICE_EXPORT_MAX 8u
|
||||
|
||||
typedef enum INIT_SERVICE_START_POLICY {
|
||||
InitServiceStartAuto = 0,
|
||||
@@ -31,7 +29,6 @@ typedef enum INIT_SERVICE_RESTART_POLICY {
|
||||
typedef struct INIT_SERVICE_DESCRIPTOR {
|
||||
char Id[INIT_SERVICE_ID_MAX + 1u];
|
||||
char ImagePath[SYS_PATH_MAX];
|
||||
INIT_SERVICE_TYPE Type;
|
||||
INIT_SERVICE_START_POLICY StartPolicy;
|
||||
bool Critical;
|
||||
INIT_SERVICE_RESTART_POLICY RestartPolicy;
|
||||
@@ -39,6 +36,10 @@ typedef struct INIT_SERVICE_DESCRIPTOR {
|
||||
uint32_t StopTimeoutMilliseconds;
|
||||
uint32_t DependencyCount;
|
||||
uint16_t Dependencies[INIT_SERVICE_DEPENDENCY_MAX];
|
||||
uint32_t GrantCount;
|
||||
char Grants[INIT_SERVICE_GRANT_MAX][INIT_SERVICE_GRANT_NAME_MAX + 1u];
|
||||
uint32_t ExportCount;
|
||||
char Exports[INIT_SERVICE_EXPORT_MAX][NTL_SERVICE_EXPORT_NAME_MAX + 1u];
|
||||
} INIT_SERVICE_DESCRIPTOR;
|
||||
|
||||
typedef struct INIT_SERVICE_CONFIG {
|
||||
@@ -46,8 +47,6 @@ typedef struct INIT_SERVICE_CONFIG {
|
||||
uint16_t StartupOrder[INIT_SERVICE_MAX_COUNT];
|
||||
uint32_t ServiceCount;
|
||||
uint32_t StartupCount;
|
||||
uint16_t ResourceServiceIndex;
|
||||
uint16_t DwmServiceIndex;
|
||||
} INIT_SERVICE_CONFIG;
|
||||
|
||||
// Loads, strictly parses, and validates services.ini beneath the bootstrap system volume.
|
||||
|
||||
@@ -1682,8 +1682,7 @@ static KSTATUS PosixsrvValidateSecurity(void) {
|
||||
security.Size = sizeof(security);
|
||||
KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
if (security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0 ||
|
||||
security.ServiceClaims != 0 || security.DelegableServiceClaims != 0)
|
||||
if (security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0)
|
||||
return NTLSTATUS_ACCESS_DENIED;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
@@ -5081,11 +5080,12 @@ static KSTATUS PosixsrvSendReady(SYS_HANDLE controlEndpoint, SYS_HANDLE publicLi
|
||||
message.Operation = NTL_SERVICE_CONTROL_READY;
|
||||
message.ExportCount = 1u;
|
||||
message.Status = NTLSTATUS_SUCCESS;
|
||||
if (!NtlServiceControlNameExport(&message, 0u, "public")) return NTLSTATUS_FAILURE;
|
||||
|
||||
SYS_PORT_HANDLE_TRANSFER transfer;
|
||||
memset(&transfer, 0, sizeof(transfer));
|
||||
transfer.Handle = publicListener;
|
||||
transfer.DesiredRights = SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfer.DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
||||
|
||||
SYS_SEND_PORT_REQUEST send;
|
||||
@@ -5122,8 +5122,7 @@ static void PosixsrvControlLoop(SYS_HANDLE endpoint) {
|
||||
if (status == NTLSTATUS_PORT_DISCONNECTED || !KSUCCESS(status)) return;
|
||||
if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 ||
|
||||
info.PayloadLength != sizeof(message) || message.Size != sizeof(message) ||
|
||||
message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 ||
|
||||
message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0)
|
||||
!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u))
|
||||
continue;
|
||||
if (message.Operation == NTL_SERVICE_CONTROL_STOP) return;
|
||||
}
|
||||
@@ -5141,8 +5140,9 @@ void _start(void) {
|
||||
status = SysCreateMutex(&gProcessRegistryMutex);
|
||||
if (!KSUCCESS(status)) PosixsrvExit(3);
|
||||
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL);
|
||||
if (controlListener == SYS_HANDLE_INVALID) PosixsrvExit(4);
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability("service.control");
|
||||
SYS_HANDLE publicationListener = SysGetStartupCapability("rssrv.posix-publisher");
|
||||
if (controlListener == SYS_HANDLE_INVALID || publicationListener == SYS_HANDLE_INVALID) PosixsrvExit(4);
|
||||
|
||||
SYS_HANDLE publicListener = SYS_HANDLE_INVALID;
|
||||
status = PosixsrvCreateListener(&publicListener);
|
||||
@@ -5163,10 +5163,21 @@ void _start(void) {
|
||||
status = SysCreateThread(&create);
|
||||
if (!KSUCCESS(status)) PosixsrvExit(7);
|
||||
|
||||
SYS_HANDLE publication = SYS_HANDLE_INVALID;
|
||||
status = NtlResourceConnectListener(publicationListener, &publication);
|
||||
(void)SysCloseHandle(publicationListener);
|
||||
if (!KSUCCESS(status)) PosixsrvExit(8);
|
||||
status = NtlResourceBind(publication, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH),
|
||||
NTL_RESOURCE_KIND_CAPABILITY, publicListener,
|
||||
SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT,
|
||||
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT);
|
||||
if (!KSUCCESS(status)) PosixsrvExit(8);
|
||||
status = PosixsrvSendReady(controlEndpoint, publicListener);
|
||||
if (!KSUCCESS(status)) PosixsrvExit(8);
|
||||
|
||||
PosixsrvControlLoop(controlEndpoint);
|
||||
(void)NtlResourceUnbind(publication, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH));
|
||||
(void)NtlResourceClose(publication);
|
||||
(void)SysDisconnectPort(controlEndpoint);
|
||||
(void)SysCloseHandle(controlEndpoint);
|
||||
(void)SysCloseHandle(publicListener);
|
||||
|
||||
@@ -112,6 +112,37 @@ static KSTATUS RssrvConsoleWrite(RSSRV_CONSOLE_STATE* console,
|
||||
return status;
|
||||
}
|
||||
|
||||
// Writes one positioned status line, retaining the console cursor under the
|
||||
// same RSSRV session lock so ordinary writers keep their cursor position.
|
||||
static KSTATUS RssrvConsoleWriteAt(RSSRV_CONSOLE_STATE* console,
|
||||
int32_t x, int32_t y, const char* data,
|
||||
uint32_t length, uint32_t* writtenOut) {
|
||||
if (writtenOut) *writtenOut = 0;
|
||||
if (!console || !data || x < 0 || y < 0 || length > NTL_CONSOLE_WRITE_MAX - 8u)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
for (uint32_t i = 0; i < length; ++i)
|
||||
if ((uint8_t)data[i] > 0x7fu || (uint8_t)data[i] < 0x20u)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
KSTATUS status = RssrvConsoleLock(console);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
SYS_CONSOLE_INFO original;
|
||||
memset(&original, 0, sizeof(original));
|
||||
status = RssrvDisplayQueryConsoleInfo(console->Display, &original);
|
||||
if (KSUCCESS(status))
|
||||
status = RssrvDisplaySetConsoleCursor(console->Display, x, y);
|
||||
if (KSUCCESS(status)) {
|
||||
status = RssrvDisplayWriteConsole(console->Display, data, length);
|
||||
if (KSUCCESS(status) && length) RssrvConsoleChangedLocked(console);
|
||||
KSTATUS restore = RssrvDisplaySetConsoleCursor(console->Display,
|
||||
(int32_t)original.CursorX,
|
||||
(int32_t)original.CursorY);
|
||||
if (KSUCCESS(status)) status = restore;
|
||||
}
|
||||
RssrvConsoleUnlock(console);
|
||||
if (KSUCCESS(status) && writtenOut) *writtenOut = length;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Queries dimensions from the backing display and snapshots RSSRV's console generation.
|
||||
static KSTATUS RssrvConsoleQueryInfo(RSSRV_CONSOLE_STATE* console, NTL_CONSOLE_INFO* infoOut) {
|
||||
if (!console || !infoOut) return NTLSTATUS_INVALID_PARAMETER;
|
||||
@@ -297,6 +328,18 @@ static void RssrvConsoleSessionThread(uintptr_t argument) {
|
||||
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
|
||||
status, written, 0, 0);
|
||||
break;
|
||||
case NTL_CONSOLE_OP_WRITE_AT:
|
||||
if (request.Data.WriteAt.Length > NTL_CONSOLE_WRITE_MAX - 8u) {
|
||||
status = NTLSTATUS_INVALID_PARAMETER;
|
||||
} else {
|
||||
status = RssrvConsoleWriteAt(console, request.Data.WriteAt.X,
|
||||
request.Data.WriteAt.Y,
|
||||
request.Data.WriteAt.Data,
|
||||
request.Data.WriteAt.Length, &written);
|
||||
}
|
||||
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
|
||||
status, written, 0, 0);
|
||||
break;
|
||||
case NTL_CONSOLE_OP_QUERY_INFO:
|
||||
status = RssrvConsoleQueryInfo(console, &consoleInfo);
|
||||
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Exact-path namespace publication policy. A scoped capability can bind or unbind one name.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/resource.h>
|
||||
|
||||
static inline bool RssrvScopeAllows(const char* scope, uint32_t operation,
|
||||
const char* path, uint32_t pathLength) {
|
||||
if (!scope) return true; // Unscoped administrator; separately issued by RSSRV.
|
||||
return path &&
|
||||
(operation == NTL_RESOURCE_OP_BIND || operation == NTL_RESOURCE_OP_UNBIND) &&
|
||||
strlen(scope) == (size_t)pathLength && memcmp(scope, path, pathLength) == 0;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "rssrv_internal.h"
|
||||
#include "publication_scope.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -9,6 +10,7 @@ typedef struct RSSRV_SESSION {
|
||||
RSSRV_RESOURCE_SERVICE* Service;
|
||||
SYS_HANDLE Endpoint;
|
||||
bool Administrative;
|
||||
const char* ScopePath;
|
||||
} RSSRV_SESSION;
|
||||
|
||||
// Replies with one fixed resource-protocol reply and no handle transfer.
|
||||
@@ -248,10 +250,16 @@ static KSTATUS RssrvHandleBind(RSSRV_RESOURCE_SERVICE* service,
|
||||
return KSUCCESS(replyStatus) ? bindStatus : replyStatus;
|
||||
}
|
||||
|
||||
// Allocates one durable, exact-path publication listener owned by RSSRV.
|
||||
static KSTATUS RssrvCreatePublisher(RSSRV_RESOURCE_SERVICE* service,
|
||||
const char* path, uint32_t length,
|
||||
SYS_HANDLE* listenerOut);
|
||||
|
||||
// Dispatches one received resource request and returns whether a received handle was consumed.
|
||||
static uint32_t RssrvDispatchRequest(RSSRV_RESOURCE_SERVICE* service,
|
||||
SYS_HANDLE endpoint,
|
||||
uint32_t administrative,
|
||||
const char* scopePath,
|
||||
const uint8_t* payload,
|
||||
uint32_t payloadLength,
|
||||
const SYS_PORT_MESSAGE_INFO* info,
|
||||
@@ -281,6 +289,53 @@ static uint32_t RssrvDispatchRequest(RSSRV_RESOURCE_SERVICE* service,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A scoped listener can only publish or withdraw its exact delegated path.
|
||||
if (!RssrvScopeAllows(scopePath, operation, path, request.PathLength)) {
|
||||
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
||||
NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (operation == NTL_RESOURCE_OP_CREATE_PUBLISHER) {
|
||||
if (!administrative || scopePath || request.Flags != NTL_RESOURCE_FLAG_NONE ||
|
||||
request.AuxLength || info->HandleCount || request.Rights || request.ExportRights ||
|
||||
request.Kind != NTL_RESOURCE_KIND_INVALID ||
|
||||
RssrvVolumesOwnPath(path, request.PathLength) || request.PathLength > RSSRV_PUBLICATION_PATH_MAX) {
|
||||
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
||||
NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID);
|
||||
return 0;
|
||||
}
|
||||
SYS_HANDLE publisher = SYS_HANDLE_INVALID;
|
||||
status = RssrvCreatePublisher(service, path, request.PathLength, &publisher);
|
||||
if (!KSUCCESS(status)) {
|
||||
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
||||
status, NTL_RESOURCE_KIND_INVALID);
|
||||
return 0;
|
||||
}
|
||||
NTL_RESOURCE_REPLY reply = {0};
|
||||
reply.Size = sizeof(reply);
|
||||
reply.Version = NTL_RESOURCE_PROTOCOL_VERSION;
|
||||
reply.Operation = operation;
|
||||
reply.Status = NTLSTATUS_SUCCESS;
|
||||
SYS_PORT_HANDLE_TRANSFER transfer = {0};
|
||||
transfer.Handle = publisher;
|
||||
transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_DUPLICATE |
|
||||
SYS_RIGHT_TRANSFER | SYS_PORT_RIGHT_CONNECT;
|
||||
SYS_REPLY_PORT_REQUEST out = {0};
|
||||
out.Size = sizeof(out);
|
||||
out.EndpointHandle = endpoint;
|
||||
out.MessageId = info->MessageId;
|
||||
out.Payload = &reply;
|
||||
out.PayloadLength = sizeof(reply);
|
||||
out.Handles = &transfer;
|
||||
out.HandleCount = 1u;
|
||||
status = SysReplyPort(&out);
|
||||
if (!KSUCCESS(status))
|
||||
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
||||
status, NTL_RESOURCE_KIND_INVALID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (operation) {
|
||||
case NTL_RESOURCE_OP_LOOKUP:
|
||||
if (request.Flags != NTL_RESOURCE_FLAG_NONE || request.AuxLength != 0 || info->HandleCount != 0 ||
|
||||
@@ -408,6 +463,7 @@ static void RssrvSessionThread(uintptr_t argument) {
|
||||
RSSRV_RESOURCE_SERVICE* service = session->Service;
|
||||
SYS_HANDLE endpoint = session->Endpoint;
|
||||
uint32_t administrative = session->Administrative;
|
||||
const char* scopePath = session->ScopePath;
|
||||
RssrvSetCurrentThreadName(administrative ? "rssrv.session.admin" : "rssrv.session.public");
|
||||
free(session);
|
||||
|
||||
@@ -440,6 +496,7 @@ static void RssrvSessionThread(uintptr_t argument) {
|
||||
consumed = RssrvDispatchRequest(service,
|
||||
endpoint,
|
||||
administrative,
|
||||
scopePath,
|
||||
payload,
|
||||
info.PayloadLength,
|
||||
&info,
|
||||
@@ -449,6 +506,10 @@ static void RssrvSessionThread(uintptr_t argument) {
|
||||
}
|
||||
}
|
||||
|
||||
// A scoped publisher owns the binding for the lifetime of its connected
|
||||
// session. Release it on abrupt disconnect as well as graceful unbind.
|
||||
if (scopePath)
|
||||
(void)RssrvNamespaceUnbind(service->Namespace, scopePath, (uint32_t)strlen(scopePath));
|
||||
(void)SysCloseHandle(endpoint);
|
||||
SysExitThread(0);
|
||||
}
|
||||
@@ -483,6 +544,7 @@ static void RssrvListenerThread(uintptr_t argument) {
|
||||
session->Service = worker->Service;
|
||||
session->Endpoint = endpoint;
|
||||
session->Administrative = worker->Administrative;
|
||||
session->ScopePath = worker->ScopePath[0] ? worker->ScopePath : NULL;
|
||||
|
||||
SYS_CREATE_THREAD_REQUEST create;
|
||||
memset(&create, 0, sizeof(create));
|
||||
@@ -532,6 +594,53 @@ static KSTATUS RssrvStartListenerWorker(RSSRV_RESOURCE_SERVICE* service,
|
||||
return SysCreateThread(&create);
|
||||
}
|
||||
|
||||
// Reuses a per-path authority across service generations and never broadens its scope.
|
||||
static KSTATUS RssrvCreatePublisher(RSSRV_RESOURCE_SERVICE* service,
|
||||
const char* path, uint32_t length,
|
||||
SYS_HANDLE* listenerOut) {
|
||||
if (!service || !path || !listenerOut || !length ||
|
||||
length > RSSRV_PUBLICATION_PATH_MAX) return NTLSTATUS_INVALID_PARAMETER;
|
||||
KSTATUS status = SysWait(service->ScopeMutex, SYS_WAIT_FLAG_NONE);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
for (uint32_t i = 0; i < service->ScopeCount; ++i) {
|
||||
RSSRV_LISTENER_WORKER* worker = &service->ScopeWorkers[i];
|
||||
if (strlen(worker->ScopePath) == length &&
|
||||
memcmp(worker->ScopePath, path, length) == 0) {
|
||||
*listenerOut = worker->Listener;
|
||||
(void)SysReleaseMutex(service->ScopeMutex);
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
if (service->ScopeCount >= RSSRV_PUBLICATION_SCOPE_MAX) {
|
||||
(void)SysReleaseMutex(service->ScopeMutex);
|
||||
return NTLSTATUS_LIMIT_REACHED;
|
||||
}
|
||||
RSSRV_LISTENER_WORKER* worker = &service->ScopeWorkers[service->ScopeCount];
|
||||
memset(worker, 0, sizeof(*worker));
|
||||
memcpy(worker->ScopePath, path, length);
|
||||
worker->ScopePath[length] = '\0';
|
||||
status = RssrvCreateListener(&worker->Listener);
|
||||
if (KSUCCESS(status))
|
||||
status = RssrvStartListenerWorker(service, worker, worker->Listener, 1u);
|
||||
if (!KSUCCESS(status)) {
|
||||
if (worker->Listener != SYS_HANDLE_INVALID) (void)SysCloseHandle(worker->Listener);
|
||||
memset(worker, 0, sizeof(*worker));
|
||||
(void)SysReleaseMutex(service->ScopeMutex);
|
||||
return status;
|
||||
}
|
||||
++service->ScopeCount;
|
||||
*listenerOut = worker->Listener;
|
||||
(void)SysReleaseMutex(service->ScopeMutex);
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Issues a publisher restricted to one RSSRV-owned namespace path.
|
||||
KSTATUS RssrvResourceCreatePublisher(RSSRV_RESOURCE_SERVICE* service,
|
||||
const char* path, SYS_HANDLE* listenerOut) {
|
||||
if (!path) return NTLSTATUS_INVALID_PARAMETER;
|
||||
return RssrvCreatePublisher(service, path, (uint32_t)strlen(path), listenerOut);
|
||||
}
|
||||
|
||||
// Initializes the public/admin resource protocol component around existing namespace state.
|
||||
KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service,
|
||||
RSSRV_NAMESPACE* namespaceState) {
|
||||
@@ -539,6 +648,8 @@ KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service,
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
memset(service, 0, sizeof(*service));
|
||||
service->Namespace = namespaceState;
|
||||
KSTATUS lockStatus = SysCreateMutex(&service->ScopeMutex);
|
||||
if (!KSUCCESS(lockStatus)) return lockStatus;
|
||||
service->PublicListener = SYS_HANDLE_INVALID;
|
||||
service->AdminListener = SYS_HANDLE_INVALID;
|
||||
service->PublicWorker.Listener = SYS_HANDLE_INVALID;
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
#include "rssrv_internal.h"
|
||||
#include <ntlite/exception.h>
|
||||
#include <ntlite/desktop.h>
|
||||
#include <ntlite/posix_service.h>
|
||||
#include <ntlite/service.h>
|
||||
#include <ntlite/service_control.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include <stdint.h>
|
||||
#include <ntlite/bootstrap_capabilities.h>
|
||||
#include <string.h>
|
||||
|
||||
static RSSRV_STATE gRssrv;
|
||||
@@ -37,12 +40,14 @@ static KSTATUS RssrvAcceptControl(SYS_HANDLE controlListener, SYS_HANDLE* endpoi
|
||||
return SysAcceptPort(&accept);
|
||||
}
|
||||
|
||||
// Sends service readiness and delegates init-only namespace and DWM-session bootstrap capabilities.
|
||||
// Exports only the capabilities consumed by dependent services. Init treats
|
||||
// the names as opaque; RSSRV owns the underlying namespace policies.
|
||||
static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint,
|
||||
const RSSRV_RESOURCE_SERVICE* resource,
|
||||
const RSSRV_SESSION_STATE* session) {
|
||||
if (!resource || !session || resource->AdminListener == SYS_HANDLE_INVALID ||
|
||||
session->DwmListener == SYS_HANDLE_INVALID)
|
||||
const RSSRV_SESSION_STATE* session,
|
||||
SYS_HANDLE desktopPublisher,
|
||||
SYS_HANDLE posixPublisher) {
|
||||
if (!session || session->DwmListener == SYS_HANDLE_INVALID ||
|
||||
desktopPublisher == SYS_HANDLE_INVALID || posixPublisher == SYS_HANDLE_INVALID)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
NTL_SERVICE_CONTROL_MESSAGE message;
|
||||
@@ -50,18 +55,23 @@ static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint,
|
||||
message.Size = sizeof(message);
|
||||
message.Version = NTL_SERVICE_CONTROL_VERSION;
|
||||
message.Operation = NTL_SERVICE_CONTROL_READY;
|
||||
message.ExportCount = 2u;
|
||||
message.ExportCount = 3u;
|
||||
message.Status = NTLSTATUS_SUCCESS;
|
||||
if (!NtlServiceControlNameExport(&message, 0u, "session-control") ||
|
||||
!NtlServiceControlNameExport(&message, 1u, "desktop-publisher") ||
|
||||
!NtlServiceControlNameExport(&message, 2u, "posix-publisher"))
|
||||
return NTLSTATUS_FAILURE;
|
||||
|
||||
SYS_PORT_HANDLE_TRANSFER transfers[2];
|
||||
SYS_PORT_HANDLE_TRANSFER transfers[3];
|
||||
memset(transfers, 0, sizeof(transfers));
|
||||
transfers[0].Handle = resource->AdminListener;
|
||||
transfers[0].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfers[0].Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
||||
transfers[1].Handle = session->DwmListener;
|
||||
// Init must transfer this private listener once more into DWM. DWM itself receives only connect/query rights.
|
||||
transfers[1].DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfers[1].Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
||||
transfers[0].Handle = session->DwmListener;
|
||||
transfers[1].Handle = desktopPublisher;
|
||||
transfers[2].Handle = posixPublisher;
|
||||
for (uint32_t i = 0; i < 3u; ++i) {
|
||||
transfers[i].DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER |
|
||||
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
||||
transfers[i].Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
||||
}
|
||||
|
||||
SYS_SEND_PORT_REQUEST send;
|
||||
memset(&send, 0, sizeof(send));
|
||||
@@ -71,7 +81,7 @@ static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint,
|
||||
send.Payload = &message;
|
||||
send.PayloadLength = sizeof(message);
|
||||
send.Handles = transfers;
|
||||
send.HandleCount = 2u;
|
||||
send.HandleCount = 3u;
|
||||
send.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
||||
return SysSendPort(&send);
|
||||
}
|
||||
@@ -99,8 +109,7 @@ static void RssrvControlLoop(SYS_HANDLE controlEndpoint) {
|
||||
return;
|
||||
if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 ||
|
||||
info.PayloadLength != sizeof(message) || message.Size != sizeof(message) ||
|
||||
message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 ||
|
||||
message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0)
|
||||
!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u))
|
||||
continue;
|
||||
if (message.Operation == NTL_SERVICE_CONTROL_STOP)
|
||||
return;
|
||||
@@ -154,19 +163,19 @@ void _start(void) {
|
||||
memset(&security, 0, sizeof(security));
|
||||
security.Size = sizeof(security);
|
||||
status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security);
|
||||
if (!KSUCCESS(status) || security.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER ||
|
||||
security.DelegablePrivileges != 0 || security.ServiceClaims != SYS_SERVICE_CLAIM_RESOURCE ||
|
||||
security.DelegableServiceClaims != 0)
|
||||
if (!KSUCCESS(status) || security.EffectivePrivileges != 0 ||
|
||||
security.DelegablePrivileges != 0)
|
||||
RssrvExit(2);
|
||||
|
||||
SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE);
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL);
|
||||
SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER);
|
||||
SYS_HANDLE inputManager = SysGetStartupCapability(SYS_STARTUP_CAP_INPUT_MANAGER);
|
||||
SYS_HANDLE displayManager = SysGetStartupCapability(SYS_STARTUP_CAP_DISPLAY_MANAGER);
|
||||
SYS_HANDLE systemVolume = SysGetStartupCapability(NTL_BOOT_CAP_NAMESPACE_ROOT);
|
||||
SYS_HANDLE controlListener = SysGetStartupCapability("service.control");
|
||||
SYS_HANDLE volumeManager = SysGetStartupCapability(NTL_BOOT_CAP_VOLUME_MANAGER);
|
||||
SYS_HANDLE inputManager = SysGetStartupCapability(NTL_BOOT_CAP_INPUT_MANAGER);
|
||||
SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_POLICY);
|
||||
SYS_HANDLE registrationAuthority = SysGetStartupCapability(NTL_BOOT_CAP_RESOURCE_REGISTRATION);
|
||||
if (systemVolume == SYS_HANDLE_INVALID || controlListener == SYS_HANDLE_INVALID ||
|
||||
volumeManager == SYS_HANDLE_INVALID || inputManager == SYS_HANDLE_INVALID ||
|
||||
displayManager == SYS_HANDLE_INVALID)
|
||||
displayManager == SYS_HANDLE_INVALID || registrationAuthority == SYS_HANDLE_INVALID)
|
||||
RssrvExit(3);
|
||||
|
||||
uint32_t systemVolumeId = 0;
|
||||
@@ -232,13 +241,22 @@ void _start(void) {
|
||||
status = RssrvVolumesStartWorker(&gRssrv.Volumes);
|
||||
if (!KSUCCESS(status)) RssrvExit(11);
|
||||
|
||||
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, gRssrv.Resource.PublicListener);
|
||||
status = SysRegisterSystemService(registrationAuthority, gRssrv.Resource.PublicListener);
|
||||
if (!KSUCCESS(status)) RssrvExit(12);
|
||||
status = RssrvSendReady(controlEndpoint, &gRssrv.Resource, &gRssrv.Session);
|
||||
SYS_HANDLE desktopPublisher = SYS_HANDLE_INVALID;
|
||||
SYS_HANDLE posixPublisher = SYS_HANDLE_INVALID;
|
||||
status = RssrvResourceCreatePublisher(&gRssrv.Resource, NTL_DESKTOP_SERVICE_PATH,
|
||||
&desktopPublisher);
|
||||
if (!KSUCCESS(status)) RssrvExit(13);
|
||||
status = RssrvResourceCreatePublisher(&gRssrv.Resource, NTL_POSIX_SERVICE_PATH,
|
||||
&posixPublisher);
|
||||
if (!KSUCCESS(status)) RssrvExit(13);
|
||||
status = RssrvSendReady(controlEndpoint, &gRssrv.Session,
|
||||
desktopPublisher, posixPublisher);
|
||||
if (!KSUCCESS(status)) RssrvExit(13);
|
||||
|
||||
RssrvControlLoop(controlEndpoint);
|
||||
(void)SysUnregisterSystemService(SYS_SERVICE_RESOURCE, gRssrv.Resource.PublicListener);
|
||||
(void)SysUnregisterSystemService(registrationAuthority, gRssrv.Resource.PublicListener);
|
||||
(void)SysDisconnectPort(controlEndpoint);
|
||||
(void)SysCloseHandle(controlEndpoint);
|
||||
RssrvExit(0);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RSSRV_LISTENER_BACKLOG 16u
|
||||
#define RSSRV_PUBLICATION_SCOPE_MAX 32u
|
||||
#define RSSRV_PUBLICATION_PATH_MAX 127u
|
||||
#define RSSRV_SYMBOLIC_LINK_MAX_DEPTH 16u
|
||||
#define RSSRV_VOLUME_PATH_CAPACITY 48u
|
||||
#define RSSRV_INPUT_KEY_QUEUE_SIZE 256u
|
||||
@@ -137,6 +139,7 @@ typedef struct RSSRV_LISTENER_WORKER {
|
||||
RSSRV_RESOURCE_SERVICE* Service;
|
||||
SYS_HANDLE Listener;
|
||||
bool Administrative;
|
||||
char ScopePath[RSSRV_PUBLICATION_PATH_MAX + 1u];
|
||||
} RSSRV_LISTENER_WORKER;
|
||||
|
||||
struct RSSRV_RESOURCE_SERVICE {
|
||||
@@ -145,6 +148,9 @@ struct RSSRV_RESOURCE_SERVICE {
|
||||
SYS_HANDLE AdminListener;
|
||||
RSSRV_LISTENER_WORKER PublicWorker;
|
||||
RSSRV_LISTENER_WORKER AdminWorker;
|
||||
SYS_HANDLE ScopeMutex;
|
||||
uint32_t ScopeCount;
|
||||
RSSRV_LISTENER_WORKER ScopeWorkers[RSSRV_PUBLICATION_SCOPE_MAX];
|
||||
};
|
||||
|
||||
typedef struct RSSRV_STATE {
|
||||
@@ -305,3 +311,5 @@ KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service,
|
||||
|
||||
// Creates the resource listeners and starts their accept workers.
|
||||
KSTATUS RssrvResourceStart(RSSRV_RESOURCE_SERVICE* service);
|
||||
KSTATUS RssrvResourceCreatePublisher(RSSRV_RESOURCE_SERVICE* service,
|
||||
const char* path, SYS_HANDLE* listenerOut);
|
||||
@@ -0,0 +1,122 @@
|
||||
// Native input API smoke test.
|
||||
// Verifies status-returning keyboard and mouse polling contracts without requiring live input.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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("input_api_smoke: ");
|
||||
print(what);
|
||||
print(" status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Requires one exact status value.
|
||||
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
|
||||
if (got == expected) return;
|
||||
print("input_api_smoke: ");
|
||||
print(what);
|
||||
print(" got=");
|
||||
print_u64(got);
|
||||
print(" expected=");
|
||||
print_u64(expected);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Program entry point.
|
||||
int main(void) {
|
||||
print("input_api_smoke: start\n");
|
||||
|
||||
expect_status("poll keyboard null", SysPollKeyboard((INPUT_KEY*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("is key down null", SysIsKeyDown(INPUT_KEY_A, (bool*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("read key null", SysReadKey((INPUT_KEY*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("read char null", SysReadKeyboardChar((char*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
bool down = true;
|
||||
uint32_t status = SysIsKeyDown(INPUT_KEY_A, &down);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("is key down", status);
|
||||
|
||||
INPUT_KEY key = INPUT_KEY_A;
|
||||
status = SysPollKeyboard(&key);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK)
|
||||
fail_status("poll keyboard", status);
|
||||
if (status == NTLSTATUS_WOULD_BLOCK && key != INPUT_KEY_NONE)
|
||||
fail_status("poll keyboard would-block key", key);
|
||||
|
||||
INPUT_KEY_RECORD input;
|
||||
SYS_INPUT_EVENT_REQUEST inputReq;
|
||||
inputReq.Size = sizeof(inputReq);
|
||||
inputReq.QueueHandle = 0;
|
||||
inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE;
|
||||
inputReq.TimeoutMilliseconds = 0;
|
||||
inputReq.RecordOut = &input;
|
||||
|
||||
expect_status("peek input null", SysPeekInputEvent((const SYS_INPUT_EVENT_REQUEST*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
status = SysPeekInputEvent(&inputReq);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK)
|
||||
fail_status("peek input", status);
|
||||
|
||||
INPUT_MOUSE_EVENT mouse;
|
||||
expect_status("peek mouse null", SysPeekMouse((INPUT_MOUSE_EVENT*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("read mouse null", SysReadMouse((INPUT_MOUSE_EVENT*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
status = SysPeekMouse(&mouse);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK)
|
||||
fail_status("peek mouse", status);
|
||||
|
||||
uint32_t readStatus = SysReadMouse(&mouse);
|
||||
if (status == NTLSTATUS_SUCCESS) {
|
||||
if (readStatus != NTLSTATUS_SUCCESS)
|
||||
fail_status("read mouse after peek", readStatus);
|
||||
} else if (readStatus != NTLSTATUS_WOULD_BLOCK) {
|
||||
fail_status("read mouse would-block", readStatus);
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
expect_status("get mouse pos null x", SysGetMousePos((int*)0, &y), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("get mouse pos null y", SysGetMousePos(&x, (int*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
status = SysGetMousePos(&x, &y);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("get mouse pos", status);
|
||||
|
||||
print("input_api_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Input model smoke test.
|
||||
// Verifies that input queue handles and request-based reads target one keyboard queue.
|
||||
|
||||
#include <stdint.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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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("input_model_smoke: ");
|
||||
print(what);
|
||||
print(" status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Requires one exact status value.
|
||||
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
|
||||
if (got == expected) return;
|
||||
print("input_model_smoke: ");
|
||||
print(what);
|
||||
print(" got=");
|
||||
print_u64(got);
|
||||
print(" expected=");
|
||||
print_u64(expected);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Program entry point.
|
||||
int main(void) {
|
||||
print("input_model_smoke: start\n");
|
||||
|
||||
uint32_t inputQueue = 0;
|
||||
expect_status("open input queue null", SysOpenInputQueue(SYS_INPUT_QUEUE_FLAG_NONE, (uint32_t*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
expect_status("open input queue bad flags", SysOpenInputQueue(1, &inputQueue), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
uint32_t status = SysOpenInputQueue(SYS_INPUT_QUEUE_FLAG_NONE, &inputQueue);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("open input queue", status);
|
||||
|
||||
INPUT_KEY_RECORD record;
|
||||
SYS_INPUT_EVENT_REQUEST req;
|
||||
req.Size = sizeof(req);
|
||||
req.QueueHandle = inputQueue;
|
||||
req.Flags = SYS_INPUT_EVENT_FLAG_NONE;
|
||||
req.TimeoutMilliseconds = 0;
|
||||
req.RecordOut = &record;
|
||||
|
||||
SYS_INPUT_EVENT_REQUEST badReq = req;
|
||||
badReq.Size = 0;
|
||||
expect_status("read input bad size", SysReadInputEvent(&badReq), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
badReq = req;
|
||||
badReq.RecordOut = (INPUT_KEY_RECORD*)0;
|
||||
expect_status("peek input null out", SysPeekInputEvent(&badReq), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
badReq = req;
|
||||
badReq.QueueHandle = 0xdeadbeefu;
|
||||
expect_status("read input bad handle", SysReadInputEvent(&badReq), NTLSTATUS_INVALID_HANDLE);
|
||||
|
||||
SYS_GLOBAL_INPUT_STATE state;
|
||||
state.Size = sizeof(state);
|
||||
state.Flags = SYS_GLOBAL_INPUT_STATE_FLAG_NONE;
|
||||
expect_status("query global input null", SysQueryGlobalInputState((SYS_GLOBAL_INPUT_STATE*)0), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
SYS_GLOBAL_INPUT_STATE badState = state;
|
||||
badState.Size = 0;
|
||||
expect_status("query global input bad size", SysQueryGlobalInputState(&badState), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
badState = state;
|
||||
badState.Flags = 1;
|
||||
expect_status("query global input bad flags", SysQueryGlobalInputState(&badState), NTLSTATUS_INVALID_PARAMETER);
|
||||
|
||||
status = SysQueryGlobalInputState(&state);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("query global input", status);
|
||||
if (state.Size != sizeof(state))
|
||||
fail_status("query global input size", state.Size);
|
||||
if (state.Flags != SYS_GLOBAL_INPUT_STATE_FLAG_NONE)
|
||||
fail_status("query global input flags", state.Flags);
|
||||
|
||||
status = SysPeekInputEvent(&req);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK)
|
||||
fail_status("peek input immediate", status);
|
||||
|
||||
uint32_t waitStatus = SysWaitSingle(inputQueue, SYS_WAIT_FLAG_NONE, 0);
|
||||
if (status == NTLSTATUS_SUCCESS) {
|
||||
if (waitStatus != NTLSTATUS_SUCCESS)
|
||||
fail_status("wait input after readable peek", waitStatus);
|
||||
} else if (waitStatus != NTLSTATUS_TIMEOUT) {
|
||||
fail_status("wait empty input immediate", waitStatus);
|
||||
}
|
||||
|
||||
req.TimeoutMilliseconds = 1;
|
||||
status = SysReadInputEvent(&req);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT)
|
||||
fail_status("read input finite timeout", status);
|
||||
|
||||
SysCloseHandle(inputQueue);
|
||||
|
||||
print("input_model_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Native named queue lifetime smoke test.
|
||||
// Verifies explicit name withdrawal and automatic withdrawal on owner handle close.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_MESSAGE_TYPE 0x71756575u
|
||||
#define TEST_MESSAGE_VALUE 0x123456789abcdef0ull
|
||||
|
||||
typedef struct TEST_QUEUE_MESSAGE {
|
||||
uint32_t Type;
|
||||
uint32_t Flags;
|
||||
uint64_t Param[6];
|
||||
} TEST_QUEUE_MESSAGE;
|
||||
|
||||
typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[
|
||||
(sizeof(TEST_QUEUE_MESSAGE) == SYS_QUEUE_MESSAGE_SIZE) ? 1 : -1
|
||||
];
|
||||
|
||||
// Prints one NUL-terminated string to the kernel console.
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 plain message.
|
||||
static void fail(const char* msg) {
|
||||
print("named_queue_lifetime_smoke: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("named_queue_lifetime_smoke: ");
|
||||
print(what);
|
||||
print(" status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Requires one exact status value.
|
||||
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
|
||||
if (got == expected) return;
|
||||
print("named_queue_lifetime_smoke: ");
|
||||
print(what);
|
||||
print(" got=");
|
||||
print_u64(got);
|
||||
print(" expected=");
|
||||
print_u64(expected);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Publishes one queue under a short test name.
|
||||
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);
|
||||
}
|
||||
|
||||
// Withdraws one named queue publication.
|
||||
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);
|
||||
}
|
||||
|
||||
// Opens one named queue publication.
|
||||
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);
|
||||
}
|
||||
|
||||
// Sends one fixed-size queue message.
|
||||
static uint32_t send_message(uint32_t queue, const TEST_QUEUE_MESSAGE* message) {
|
||||
SYS_QUEUE_SEND_REQUEST req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.Size = sizeof(req);
|
||||
req.QueueHandle = queue;
|
||||
req.Flags = SYS_QUEUE_FLAG_NONE;
|
||||
req.TimeoutMilliseconds = 0;
|
||||
req.Message = message;
|
||||
return SysSendQueue(&req);
|
||||
}
|
||||
|
||||
// Receives one fixed-size queue message.
|
||||
static uint32_t recv_message(uint32_t queue, TEST_QUEUE_MESSAGE* message) {
|
||||
SYS_QUEUE_RECV_REQUEST req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.Size = sizeof(req);
|
||||
req.QueueHandle = queue;
|
||||
req.Flags = SYS_QUEUE_FLAG_NONE;
|
||||
req.TimeoutMilliseconds = 0;
|
||||
req.MessageOut = message;
|
||||
return SysRecvQueue(&req);
|
||||
}
|
||||
|
||||
// Verifies explicit unpublish removes only the name, not existing handles.
|
||||
static void check_explicit_unpublish(void) {
|
||||
uint32_t queue = 0;
|
||||
uint32_t opened = 0;
|
||||
uint32_t status = SysCreateQueue(&queue);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("create queue", status);
|
||||
|
||||
status = publish_queue("ntl.queue.explicit", queue);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("publish queue", status);
|
||||
|
||||
status = open_named_queue("ntl.queue.explicit", &opened);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("open published queue", status);
|
||||
if (!opened) fail("opened handle is zero");
|
||||
|
||||
status = unpublish_queue("ntl.queue.explicit");
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("unpublish queue", status);
|
||||
|
||||
uint32_t missing = 0;
|
||||
status = open_named_queue("ntl.queue.explicit", &missing);
|
||||
expect_status("open withdrawn name", status, NTLSTATUS_NOT_FOUND);
|
||||
|
||||
TEST_QUEUE_MESSAGE out;
|
||||
TEST_QUEUE_MESSAGE in;
|
||||
memset(&out, 0, sizeof(out));
|
||||
memset(&in, 0, sizeof(in));
|
||||
out.Type = TEST_MESSAGE_TYPE;
|
||||
out.Param[0] = TEST_MESSAGE_VALUE;
|
||||
status = send_message(queue, &out);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("send after unpublish", status);
|
||||
status = recv_message(opened, &in);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("recv after unpublish", status);
|
||||
if (in.Type != TEST_MESSAGE_TYPE || in.Param[0] != TEST_MESSAGE_VALUE)
|
||||
fail("message mismatch after unpublish");
|
||||
|
||||
SysCloseHandle(opened);
|
||||
SysCloseHandle(queue);
|
||||
}
|
||||
|
||||
// Verifies closing the publishing handle withdraws its names.
|
||||
static void check_close_withdraws_name(void) {
|
||||
uint32_t queue = 0;
|
||||
uint32_t status = SysCreateQueue(&queue);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("create close queue", status);
|
||||
|
||||
status = publish_queue("ntl.queue.close", queue);
|
||||
if (status != NTLSTATUS_SUCCESS) fail_status("publish close queue", status);
|
||||
|
||||
SysCloseHandle(queue);
|
||||
|
||||
uint32_t opened = 0;
|
||||
status = open_named_queue("ntl.queue.close", &opened);
|
||||
expect_status("open close-withdrawn name", status, NTLSTATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Runs the named queue lifetime smoke test.
|
||||
int main(void) {
|
||||
print("named_queue_lifetime_smoke: start\n");
|
||||
check_explicit_unpublish();
|
||||
check_close_withdraws_name();
|
||||
print("named_queue_lifetime_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -464,77 +464,21 @@ static int run_local_transport_test(void) {
|
||||
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.
|
||||
// Verifies that unrelated handles cannot authorize numeric service publication.
|
||||
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_HANDLE_INVALID, listener);
|
||||
if (status != NTLSTATUS_INVALID_HANDLE)
|
||||
return fail("missing registration capability", status);
|
||||
|
||||
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener);
|
||||
if (status != NTLSTATUS_ALREADY_EXISTS)
|
||||
return fail("resource service must already be registered", status);
|
||||
status = SysRegisterSystemService(listener, listener);
|
||||
if (KSUCCESS(status)) return fail("listener cannot impersonate registry authority", 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);
|
||||
status = SysUnregisterSystemService(listener, listener);
|
||||
if (KSUCCESS(status)) return fail("listener cannot revoke registry authority", status);
|
||||
|
||||
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
|
||||
SYS_CONNECT_SYSTEM_SERVICE_REQUEST connect;
|
||||
@@ -550,14 +494,8 @@ static int run_service_test(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Runs either the privileged parent or claimless child half.
|
||||
// Runs the capability-scoped port and registry smoke tests.
|
||||
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_peer_process_test() != 0) return 1;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TEST_STARTUP_EVENT_TAG 0x50425445u
|
||||
#define TEST_STARTUP_WORD 0x31503550u
|
||||
|
||||
// Exits without relying on a hosted runtime.
|
||||
@@ -58,7 +57,7 @@ static void verify_child_bootstrap(void) {
|
||||
|
||||
// Runs the child half and signals the inherited parent event after validation.
|
||||
static void run_child(void) {
|
||||
SYS_HANDLE event = SysGetStartupCapability(TEST_STARTUP_EVENT_TAG);
|
||||
SYS_HANDLE event = SysGetStartupCapability("test.builder-event");
|
||||
if (event == SYS_HANDLE_INVALID) exit_now(10);
|
||||
verify_child_bootstrap();
|
||||
if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11);
|
||||
@@ -88,7 +87,7 @@ static int run_parent(void) {
|
||||
|
||||
NTL_PROCESS_STARTUP_CAPABILITY startupCapability;
|
||||
memset(&startupCapability, 0, sizeof(startupCapability));
|
||||
startupCapability.Tag = TEST_STARTUP_EVENT_TAG;
|
||||
strcpy(startupCapability.Name, "test.builder-event");
|
||||
startupCapability.SourceHandle = event;
|
||||
startupCapability.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY;
|
||||
startupCapability.DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// Verifies that kernel-owned processes remain queryable but cannot be terminated.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define MAX_PROCESSES 16u
|
||||
#define MAX_PROCESSES 64u
|
||||
|
||||
// Prints one NUL-terminated string to the console.
|
||||
static void print(const char* text) {
|
||||
@@ -81,10 +82,12 @@ void main(void) {
|
||||
|
||||
int sawIdle = 0;
|
||||
int sawSystem = 0;
|
||||
int sawInit = 0;
|
||||
size_t limit = count < MAX_PROCESSES ? count : MAX_PROCESSES;
|
||||
for (size_t i = 0; i < limit; i++) {
|
||||
const SYS_PROCESS_INFO* process = &processes[i];
|
||||
if (!(process->Flags & (SYS_PROCESS_FLAG_IDLE | SYS_PROCESS_FLAG_SYSTEM)))
|
||||
int isInit = strcmp(process->Name, "system/init.exe") == 0;
|
||||
if (!(process->Flags & (SYS_PROCESS_FLAG_IDLE | SYS_PROCESS_FLAG_SYSTEM)) && !isInit)
|
||||
continue;
|
||||
|
||||
if (!(process->Flags & SYS_PROCESS_FLAG_TERMINATION_PROTECTED))
|
||||
@@ -93,11 +96,19 @@ void main(void) {
|
||||
verify_protected_process(process);
|
||||
if (process->Flags & SYS_PROCESS_FLAG_IDLE) sawIdle = 1;
|
||||
if (process->Flags & SYS_PROCESS_FLAG_SYSTEM) sawSystem = 1;
|
||||
if (isInit) sawInit = 1;
|
||||
}
|
||||
|
||||
if (!sawIdle || !sawSystem)
|
||||
if (!sawIdle || !sawSystem || !sawInit)
|
||||
fail_status("missing kernel process", (uint32_t)((sawIdle ? 1u : 0u) | (sawSystem ? 2u : 0u)));
|
||||
|
||||
uint32_t action = 0;
|
||||
SYS_HANDLE event = SYS_HANDLE_INVALID;
|
||||
if (SysOpenPowerEvent(&event) != NTLSTATUS_ACCESS_DENIED ||
|
||||
SysQuerySystemPower(&action) != NTLSTATUS_ACCESS_DENIED ||
|
||||
SysCompleteSystemPower(SYS_POWER_ACTION_SHUTDOWN) != NTLSTATUS_ACCESS_DENIED)
|
||||
fail_status("power coordinator ownership", NTLSTATUS_FAILURE);
|
||||
|
||||
print("process_protection_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
}
|
||||
@@ -86,8 +86,7 @@ static int run_restricted_child(uint32_t parentPid) {
|
||||
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)
|
||||
if (info.EffectivePrivileges || info.DelegablePrivileges)
|
||||
return 11;
|
||||
|
||||
SYS_HANDLE process = SYS_HANDLE_INVALID;
|
||||
@@ -145,9 +144,9 @@ static int run_restricted_child(uint32_t parentPid) {
|
||||
createPort.HandleOut = &listener;
|
||||
status = SysCreatePort(&createPort);
|
||||
if (!KSUCCESS(status)) return 25;
|
||||
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener);
|
||||
status = SysRegisterSystemService(SYS_HANDLE_INVALID, listener);
|
||||
(void)SysCloseHandle(listener);
|
||||
if (status != NTLSTATUS_ACCESS_DENIED) return 26;
|
||||
if (status != NTLSTATUS_INVALID_HANDLE) return 26;
|
||||
|
||||
uint32_t selfPid = SysGetCurrentPid();
|
||||
status = SysOpenProcess(selfPid, SYS_RIGHT_QUERY, &process);
|
||||
@@ -188,9 +187,7 @@ static int run_parent(void) {
|
||||
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)
|
||||
rootInfo.DelegablePrivileges != SYS_PRIV_ALL)
|
||||
fail_status("bootstrap context authority", NTLSTATUS_FAILURE);
|
||||
|
||||
SYS_HANDLE selfProcess = SYS_HANDLE_INVALID;
|
||||
@@ -209,9 +206,7 @@ static int run_parent(void) {
|
||||
if (!KSUCCESS(status)) fail_status("query exact process security context", status);
|
||||
if (exactInfo.SecurityId != rootInfo.SecurityId ||
|
||||
exactInfo.EffectivePrivileges != rootInfo.EffectivePrivileges ||
|
||||
exactInfo.DelegablePrivileges != rootInfo.DelegablePrivileges ||
|
||||
exactInfo.ServiceClaims != rootInfo.ServiceClaims ||
|
||||
exactInfo.DelegableServiceClaims != rootInfo.DelegableServiceClaims)
|
||||
exactInfo.DelegablePrivileges != rootInfo.DelegablePrivileges)
|
||||
fail_status("exact process security context identity", NTLSTATUS_FAILURE);
|
||||
(void)SysCloseHandle(exactContext);
|
||||
(void)SysCloseHandle(selfProcess);
|
||||
@@ -232,8 +227,7 @@ static int run_parent(void) {
|
||||
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)
|
||||
restrictedInfo.EffectivePrivileges || restrictedInfo.DelegablePrivileges)
|
||||
fail_status("stripped context contents", NTLSTATUS_FAILURE);
|
||||
|
||||
SYS_HANDLE illegal = SYS_HANDLE_INVALID;
|
||||
@@ -247,15 +241,6 @@ static int run_parent(void) {
|
||||
if (status != NTLSTATUS_ACCESS_DENIED)
|
||||
fail_status("blocked privilege amplification", status);
|
||||
|
||||
memset(&lify, 0, sizeof(amplify));
|
||||
amplify.Size = sizeof(amplify);
|
||||
amplify.SourceContext = restricted;
|
||||
amplify.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE;
|
||||
amplify.ContextHandleOut = &illegal;
|
||||
status = SysDeriveSecurityContext(&lify);
|
||||
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
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <ntlite/process_loader.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_SURFACE_TAG 0x53555246u
|
||||
#define TEST_READY_TAG 0x53555245u
|
||||
#define TEST_WIDTH 32u
|
||||
#define TEST_HEIGHT 16u
|
||||
#define TEST_PIXEL_X 7u
|
||||
@@ -49,8 +47,8 @@ static void write_pixel(void* base, uint32_t pitch, uint32_t value) {
|
||||
|
||||
// Runs the child side using only the attenuated capabilities supplied at process construction.
|
||||
static void run_child(void) {
|
||||
SYS_HANDLE surface = SysGetStartupCapability(TEST_SURFACE_TAG);
|
||||
SYS_HANDLE ready = SysGetStartupCapability(TEST_READY_TAG);
|
||||
SYS_HANDLE surface = SysGetStartupCapability("test.surface");
|
||||
SYS_HANDLE ready = SysGetStartupCapability("test.ready");
|
||||
if (surface == SYS_HANDLE_INVALID || ready == SYS_HANDLE_INVALID) exit_now(10);
|
||||
|
||||
void* invalidBase = (void*)0;
|
||||
@@ -112,11 +110,11 @@ int main(void) {
|
||||
|
||||
NTL_PROCESS_STARTUP_CAPABILITY startup[2];
|
||||
memset(startup, 0, sizeof(startup));
|
||||
startup[0].Tag = TEST_SURFACE_TAG;
|
||||
strcpy(startup[0].Name, "test.surface");
|
||||
startup[0].SourceHandle = surface;
|
||||
startup[0].DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_SURFACE_RIGHT_MAP_READ;
|
||||
startup[0].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
||||
startup[1].Tag = TEST_READY_TAG;
|
||||
strcpy(startup[1].Name, "test.ready");
|
||||
startup[1].SourceHandle = ready;
|
||||
startup[1].DesiredRights = SYS_RIGHT_QUERY | SYS_EVENT_RIGHT_SIGNAL;
|
||||
startup[1].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
||||
|
||||
Reference in new issue
Block a user