Files

502 lines
20 KiB
C

// Native userspace path policy. RSSRV resolves global resource prefixes; kernel I/O sees only capabilities and relative paths.
#include <ntlite/atomic.h>
#include <intrin.h>
#include <ntlite/path.h>
#include <ntlite/resource.h>
#include <string.h>
#include "module_loader_internal.h"
#define NTL_PATH_COMPONENT_MAX 255u
typedef struct NTL_RESOLVED_ROOT {
SYS_HANDLE Handle;
SYS_STRING_VIEW RelativePath;
uint32_t Kind;
size_t ResolvedLength;
} NTL_RESOLVED_ROOT;
static volatile int32_t gPathState;
static volatile int32_t gPathLock;
static char gCurrentDirectory[SYS_PATH_MAX + 1u];
static size_t gCurrentDirectoryLength;
static size_t gCurrentDirectoryRootLength;
// Acquires the tiny userspace CWD lock without introducing a kernel object.
static void NtlpLockPathState(void) {
while (NtlAtomicCompareExchange32(&gPathLock, 1, 0) != 0)
(void)SysYield();
}
// Releases the userspace CWD lock.
static void NtlpUnlockPathState(void) {
NtlAtomicExchange32(&gPathLock, 0);
}
// Initializes the process-local CWD from opaque loader data, or /SystemRoot for bootstrap init.
static KSTATUS NtlpEnsurePathState(void) {
if (NtlAtomicCompareExchange32(&gPathState, 2, 2) == 2)
return NTLSTATUS_SUCCESS;
if (NtlAtomicCompareExchange32(&gPathState, 1, 0) == 0) {
SYS_STRING_VIEW inherited;
KSTATUS status = NtlpGetLoaderCurrentDirectory(&inherited);
if (!KSUCCESS(status))
inherited = SYS_SV_LITERAL("/SystemRoot");
if (!SysSvIsValid(inherited) || inherited.Length == 0 || inherited.Length > SYS_PATH_MAX ||
inherited.Data[0] != '/' || SysSvContainsByte(inherited, '\\') || SysSvContainsByte(inherited, '\0')) {
NtlAtomicExchange32(&gPathState, 0);
return NTLSTATUS_FAILURE;
}
memcpy(gCurrentDirectory, inherited.Data, inherited.Length);
gCurrentDirectory[inherited.Length] = '\0';
gCurrentDirectoryLength = inherited.Length;
gCurrentDirectoryRootLength = 0;
NtlAtomicExchange32(&gPathState, 2);
return NTLSTATUS_SUCCESS;
}
while (NtlAtomicCompareExchange32(&gPathState, 2, 2) == 1)
(void)SysYield();
return NtlAtomicCompareExchange32(&gPathState, 2, 2) == 2
? NTLSTATUS_SUCCESS : NTLSTATUS_FAILURE;
}
// Appends one normalized path component while enforcing the owning resource boundary.
static KSTATUS NtlpAppendComponent(char* output,
size_t outputSize,
size_t* lengthInOut,
size_t componentStart,
const char* component,
size_t componentLength,
uint32_t clampAtBoundary) {
if (!output || !lengthInOut || !component || componentLength == 0 ||
componentLength > NTL_PATH_COMPONENT_MAX)
return NTLSTATUS_INVALID_PARAMETER;
size_t length = *lengthInOut;
if (componentLength == 1 && component[0] == '.')
return NTLSTATUS_SUCCESS;
if (componentLength == 2 && component[0] == '.' && component[1] == '.') {
if (length <= componentStart)
return clampAtBoundary ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED;
size_t slash = length;
while (slash > componentStart && output[slash - 1u] != '/') slash--;
if (slash <= componentStart)
return clampAtBoundary ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED;
length = slash - 1u;
*lengthInOut = length;
output[length] = '\0';
return NTLSTATUS_SUCCESS;
}
if (length && output[length - 1u] != '/') {
if (length + 1u >= outputSize) return NTLSTATUS_BUFFER_TOO_SMALL;
output[length++] = '/';
}
if (componentLength > outputSize - length - 1u)
return NTLSTATUS_BUFFER_TOO_SMALL;
memcpy(output + length, component, componentLength);
length += componentLength;
output[length] = '\0';
*lengthInOut = length;
return NTLSTATUS_SUCCESS;
}
// Returns whether one relative path contains a parent-traversal component.
static uint32_t NtlpContainsParentComponent(SYS_STRING_VIEW path) {
size_t componentStart = 0;
for (size_t i = 0; i <= path.Length; ++i) {
if (i != path.Length && path.Data[i] != '/')
continue;
size_t componentLength = i - componentStart;
if (componentLength == 2 && path.Data[componentStart] == '.' &&
path.Data[componentStart + 1u] == '.')
return 1;
componentStart = i + 1u;
}
return 0;
}
// Builds the canonical absolute prefix preceding the first parent component.
static KSTATUS NtlpBuildAbsoluteParentPrefix(SYS_STRING_VIEW path,
char output[SYS_PATH_MAX + 1u],
size_t* lengthOut) {
if (!output || !lengthOut || !SysSvIsValid(path) || path.Length == 0 || path.Data[0] != '/')
return NTLSTATUS_INVALID_PARAMETER;
size_t length = 1u;
output[0] = '/';
output[1] = '\0';
size_t componentStart = 1u;
for (size_t i = 1u; i <= path.Length; ++i) {
if (i != path.Length && path.Data[i] != '/')
continue;
size_t componentLength = i - componentStart;
if (componentLength == 1u && path.Data[componentStart] == '.') {
componentStart = i + 1u;
continue;
}
if (componentLength == 2u && path.Data[componentStart] == '.' &&
path.Data[componentStart + 1u] == '.') {
if (length <= 1u) return NTLSTATUS_ACCESS_DENIED;
*lengthOut = length;
return NTLSTATUS_SUCCESS;
}
if (componentLength != 0) {
KSTATUS status = NtlpAppendComponent(output, SYS_PATH_MAX + 1u, &length, 1u,
path.Data + componentStart, componentLength, 0);
if (!KSUCCESS(status)) return status;
}
componentStart = i + 1u;
}
return NTLSTATUS_INVALID_PARAMETER;
}
// Resolves one canonical path solely to discover the owning RSSRV resource-prefix boundary.
static KSTATUS NtlpResolveResourceBoundary(const char* canonical,
size_t canonicalLength,
size_t* boundaryOut) {
if (!canonical || canonicalLength < 2u || !boundaryOut)
return NTLSTATUS_INVALID_PARAMETER;
*boundaryOut = 0;
SYS_HANDLE session = SYS_HANDLE_INVALID;
KSTATUS status = NtlResourceConnect(&session);
if (!KSUCCESS(status)) return status;
SYS_HANDLE root = SYS_HANDLE_INVALID;
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
uint32_t resolvedLength = 0;
status = NtlResourceResolve(session, SysSvFromParts(canonical, canonicalLength),
NTL_RESOURCE_FLAG_NONE, &root, &kind, &resolvedLength);
(void)NtlResourceClose(session);
if (root != SYS_HANDLE_INVALID) (void)SysCloseHandle(root);
if (!KSUCCESS(status)) return status;
if (kind == NTL_RESOURCE_KIND_INVALID || resolvedLength < 2u || resolvedLength > canonicalLength ||
(resolvedLength != canonicalLength && canonical[resolvedLength] != '/'))
return NTLSTATUS_FAILURE;
*boundaryOut = resolvedLength;
return NTLSTATUS_SUCCESS;
}
// Normalizes one absolute or CWD-relative native path into a canonical resource path.
static KSTATUS NtlpNormalizePath(SYS_STRING_VIEW path, char output[SYS_PATH_MAX + 1u], size_t* lengthOut) {
if (!output || !lengthOut || !SysSvIsValid(path) || SysSvContainsByte(path, '\0') ||
SysSvContainsByte(path, '\\'))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = NtlpEnsurePathState();
if (!KSUCCESS(status)) return status;
size_t outputLength = 0;
size_t rootBoundary = 1u;
size_t sourceOffset = 0;
uint32_t absolutePath = path.Length && path.Data[0] == '/';
if (absolutePath) {
output[0] = '/';
output[1] = '\0';
outputLength = 1u;
sourceOffset = 1u;
if (NtlpContainsParentComponent(path)) {
char prefix[SYS_PATH_MAX + 1u];
size_t prefixLength = 0;
status = NtlpBuildAbsoluteParentPrefix(path, prefix, &prefixLength);
if (!KSUCCESS(status)) return status;
status = NtlpResolveResourceBoundary(prefix, prefixLength, &rootBoundary);
if (!KSUCCESS(status)) return status;
}
} else {
NtlpLockPathState();
if (gCurrentDirectoryLength > SYS_PATH_MAX ||
gCurrentDirectoryRootLength > gCurrentDirectoryLength) {
NtlpUnlockPathState();
return NTLSTATUS_FAILURE;
}
memcpy(output, gCurrentDirectory, gCurrentDirectoryLength + 1u);
outputLength = gCurrentDirectoryLength;
rootBoundary = gCurrentDirectoryRootLength;
NtlpUnlockPathState();
sourceOffset = 0;
if (rootBoundary == 0 && NtlpContainsParentComponent(path)) {
status = NtlpResolveResourceBoundary(output, outputLength, &rootBoundary);
if (!KSUCCESS(status)) return status;
NtlpLockPathState();
if (gCurrentDirectoryLength == outputLength &&
memcmp(gCurrentDirectory, output, outputLength + 1u) == 0)
gCurrentDirectoryRootLength = rootBoundary;
NtlpUnlockPathState();
}
}
size_t componentStart = sourceOffset;
for (size_t i = sourceOffset; i <= path.Length; ++i) {
if (i != path.Length && path.Data[i] != '/')
continue;
size_t componentLength = i - componentStart;
if (componentLength != 0) {
status = NtlpAppendComponent(output, SYS_PATH_MAX + 1u, &outputLength,
rootBoundary, path.Data + componentStart, componentLength,
absolutePath ? 0u : 1u);
if (!KSUCCESS(status)) return status;
}
componentStart = i + 1u;
}
while (outputLength > 1u && output[outputLength - 1u] == '/') outputLength--;
output[outputLength] = '\0';
if (outputLength < 2u || output[0] != '/') return NTLSTATUS_INVALID_PARAMETER;
*lengthOut = outputLength;
return NTLSTATUS_SUCCESS;
}
// Resolves a canonical resource path to one volume/directory capability and a relative tail.
static KSTATUS NtlpResolveRoot(const char* canonical,
size_t canonicalLength,
uint32_t modify,
NTL_RESOLVED_ROOT* rootOut) {
if (!canonical || canonicalLength < 2u || !rootOut)
return NTLSTATUS_INVALID_PARAMETER;
memset(rootOut, 0, sizeof(*rootOut));
SYS_HANDLE session = SYS_HANDLE_INVALID;
KSTATUS status = NtlResourceConnect(&session);
if (!KSUCCESS(status)) return status;
SYS_HANDLE root = SYS_HANDLE_INVALID;
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
uint32_t resolvedLength = 0;
status = NtlResourceResolve(session,
SysSvFromParts(canonical, canonicalLength),
modify ? NTL_RESOURCE_RESOLVE_FLAG_MODIFY : NTL_RESOURCE_FLAG_NONE,
&root, &kind, &resolvedLength);
(void)NtlResourceClose(session);
if (!KSUCCESS(status)) return status;
size_t relativeOffset = resolvedLength;
if (relativeOffset < canonicalLength) relativeOffset++;
if (relativeOffset > canonicalLength) {
(void)SysCloseHandle(root);
return NTLSTATUS_FAILURE;
}
rootOut->Handle = root;
rootOut->Kind = kind;
rootOut->ResolvedLength = resolvedLength;
rootOut->RelativePath = SysSvFromParts(canonical + relativeOffset, canonicalLength - relativeOffset);
return NTLSTATUS_SUCCESS;
}
// Returns one canonical absolute resource path.
KSTATUS NtlRealPath(SYS_STRING_VIEW path,
char* buffer,
size_t bufferSize,
size_t* requiredSizeOut) {
if (requiredSizeOut) *requiredSizeOut = 0;
if (!buffer && bufferSize) return NTLSTATUS_INVALID_PARAMETER;
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
size_t required = length + 1u;
if (requiredSizeOut) *requiredSizeOut = required;
if (!buffer) return NTLSTATUS_SUCCESS;
if (bufferSize < required) return NTLSTATUS_BUFFER_TOO_SMALL;
memcpy(buffer, canonical, required);
return NTLSTATUS_SUCCESS;
}
// Returns the process-local userspace current directory.
KSTATUS NtlGetCurrentDirectory(char* buffer, size_t bufferSize, size_t* requiredSizeOut) {
if (requiredSizeOut) *requiredSizeOut = 0;
if (!buffer && bufferSize) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = NtlpEnsurePathState();
if (!KSUCCESS(status)) return status;
NtlpLockPathState();
size_t required = gCurrentDirectoryLength + 1u;
if (requiredSizeOut) *requiredSizeOut = required;
if (buffer && bufferSize >= required)
memcpy(buffer, gCurrentDirectory, required);
NtlpUnlockPathState();
if (!buffer) return NTLSTATUS_SUCCESS;
return bufferSize >= required ? NTLSTATUS_SUCCESS : NTLSTATUS_BUFFER_TOO_SMALL;
}
// Validates and installs one process-local userspace current directory.
KSTATUS NtlSetCurrentDirectory(SYS_STRING_VIEW path) {
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, 0, &root);
if (!KSUCCESS(status)) return status;
SYS_HANDLE directory = SYS_HANDLE_INVALID;
SYS_OPEN_DIR_AT_REQUEST open = SysMakeOpenDirAtRequest(root.Handle, root.RelativePath,
SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN,
SYS_DIR_FLAG_NONE, &directory);
status = SysOpenDirAt(&open);
(void)SysCloseHandle(root.Handle);
if (!KSUCCESS(status)) return status;
(void)SysCloseHandle(directory);
NtlpLockPathState();
memcpy(gCurrentDirectory, canonical, length + 1u);
gCurrentDirectoryLength = length;
gCurrentDirectoryRootLength = root.ResolvedLength;
NtlpUnlockPathState();
return NTLSTATUS_SUCCESS;
}
// Opens or creates one file through RSSRV resource resolution and capability-relative kernel I/O.
KSTATUS NtlOpenFile(SYS_STRING_VIEW path,
uint32_t access,
uint32_t disposition,
uint32_t flags,
SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
*handleOut = SYS_HANDLE_INVALID;
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
uint32_t modify = ((access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND | SYS_FILE_ACCESS_LOCK)) != 0 ||
disposition != SYS_FILE_OPEN_EXISTING);
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, modify, &root);
if (!KSUCCESS(status)) return status;
if (root.RelativePath.Length == 0) {
(void)SysCloseHandle(root.Handle);
return NTLSTATUS_INVALID_PARAMETER;
}
SYS_OPEN_FILE_AT_REQUEST request = SysMakeOpenFileAtRequest(root.Handle, root.RelativePath,
access, disposition, flags, handleOut);
status = SysOpenFileAt(&request);
(void)SysCloseHandle(root.Handle);
return status;
}
// Opens one directory through RSSRV resource resolution and capability-relative kernel I/O.
KSTATUS NtlOpenDir(SYS_STRING_VIEW path, uint32_t access, uint32_t flags, SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
*handleOut = SYS_HANDLE_INVALID;
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, (access & SYS_DIR_ACCESS_MODIFY) != 0, &root);
if (!KSUCCESS(status)) return status;
SYS_OPEN_DIR_AT_REQUEST request = SysMakeOpenDirAtRequest(root.Handle, root.RelativePath,
access, flags, handleOut);
status = SysOpenDirAt(&request);
(void)SysCloseHandle(root.Handle);
return status;
}
// Queries file metadata through one resolved root capability.
KSTATUS NtlStatPath(SYS_STRING_VIEW path, SYS_FILE_STAT* statOut) {
if (!statOut) return NTLSTATUS_INVALID_PARAMETER;
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, 0, &root);
if (!KSUCCESS(status)) return status;
SYS_STAT_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath, statOut };
status = SysStatAt(&request);
(void)SysCloseHandle(root.Handle);
return status;
}
// Deletes one file through one resolved writable root capability.
KSTATUS NtlDeleteFile(SYS_STRING_VIEW path) {
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, 1, &root);
if (!KSUCCESS(status)) return status;
SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath };
status = root.RelativePath.Length ? SysDeleteAt(&request) : NTLSTATUS_INVALID_PARAMETER;
(void)SysCloseHandle(root.Handle);
return status;
}
// Renames one file using independently resolved writable roots.
KSTATUS NtlRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) {
char oldCanonical[SYS_PATH_MAX + 1u];
char newCanonical[SYS_PATH_MAX + 1u];
size_t oldLength = 0, newLength = 0;
KSTATUS status = NtlpNormalizePath(oldPath, oldCanonical, &oldLength);
if (!KSUCCESS(status)) return status;
status = NtlpNormalizePath(newPath, newCanonical, &newLength);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT oldRoot, newRoot;
status = NtlpResolveRoot(oldCanonical, oldLength, 1, &oldRoot);
if (!KSUCCESS(status)) return status;
status = NtlpResolveRoot(newCanonical, newLength, 1, &newRoot);
if (!KSUCCESS(status)) { (void)SysCloseHandle(oldRoot.Handle); return status; }
if (oldRoot.RelativePath.Length == 0 || newRoot.RelativePath.Length == 0) {
(void)SysCloseHandle(newRoot.Handle);
(void)SysCloseHandle(oldRoot.Handle);
return NTLSTATUS_INVALID_PARAMETER;
}
SYS_RENAME_AT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.OldRootHandle = oldRoot.Handle;
request.OldPath = oldRoot.RelativePath;
request.NewRootHandle = newRoot.Handle;
request.NewPath = newRoot.RelativePath;
status = SysRenameAt(&request);
(void)SysCloseHandle(newRoot.Handle);
(void)SysCloseHandle(oldRoot.Handle);
return status;
}
// Creates one directory through one resolved writable root capability.
KSTATUS NtlCreateDirectory(SYS_STRING_VIEW path) {
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, 1, &root);
if (!KSUCCESS(status)) return status;
SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath };
status = root.RelativePath.Length ? SysCreateDirAt(&request) : NTLSTATUS_INVALID_PARAMETER;
(void)SysCloseHandle(root.Handle);
return status;
}
// Removes one directory through one resolved writable root capability.
KSTATUS NtlRemoveDirectory(SYS_STRING_VIEW path) {
char canonical[SYS_PATH_MAX + 1u];
size_t length = 0;
KSTATUS status = NtlpNormalizePath(path, canonical, &length);
if (!KSUCCESS(status)) return status;
NTL_RESOLVED_ROOT root;
status = NtlpResolveRoot(canonical, length, 1, &root);
if (!KSUCCESS(status)) return status;
SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath };
status = root.RelativePath.Length ? SysRemoveDirAt(&request) : NTLSTATUS_INVALID_PARAMETER;
(void)SysCloseHandle(root.Handle);
return status;
}