From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/common/windows/registry.c | 388 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/common/windows/registry.c (limited to 'src/common/windows/registry.c') diff --git a/src/common/windows/registry.c b/src/common/windows/registry.c new file mode 100644 index 0000000..3618760 --- /dev/null +++ b/src/common/windows/registry.c @@ -0,0 +1,388 @@ +#include "registry.h" +#include "unicode.h" +#include "common/mallocHelper.h" +#include "common/debug.h" +#include "common/windows/nt.h" + +#include +#include + +static HANDLE hRootKeys[8 /*(uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS - (uintptr_t) HKEY_CLASSES_ROOT + 1*/]; + +static const char* hKey2Str(HANDLE hRootKey) { +#define HKEY_CASE(compareKey) \ + if (hRootKey == hRootKeys[(uintptr_t) compareKey - (uintptr_t) HKEY_CLASSES_ROOT]) return #compareKey; + HKEY_CASE(HKEY_CLASSES_ROOT) + HKEY_CASE(HKEY_CURRENT_USER) + HKEY_CASE(HKEY_LOCAL_MACHINE) + HKEY_CASE(HKEY_USERS) + HKEY_CASE(HKEY_PERFORMANCE_DATA) + HKEY_CASE(HKEY_CURRENT_CONFIG) + HKEY_CASE(HKEY_DYN_DATA) + HKEY_CASE(HKEY_CURRENT_USER_LOCAL_SETTINGS) +#undef HKEY_CASE + + return "UNKNOWN"; +} + +HANDLE ffRegGetRootKeyHandle(HKEY hKey) { + assert(hKey); + assert((uintptr_t) hKey >= (uintptr_t) HKEY_CLASSES_ROOT && (uintptr_t) hKey <= (uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS); + + FF_DEBUG("Getting root key handle for HKEY %08llx", (uint64_t) (uintptr_t) hKey); + + HANDLE result = hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT]; + if (result) { + FF_DEBUG("Found cached root key handle for %s -> %p", hKey2Str(result), result); + return result; + } + + switch ((uintptr_t) hKey) { + case (uintptr_t) HKEY_CURRENT_USER: { + NTSTATUS status = RtlOpenCurrentUser(KEY_READ, &result); + if (!NT_SUCCESS(status)) { + FF_DEBUG("RtlOpenCurrentUser() failed: %s", ffDebugNtStatus(status)); + return NULL; + } + break; + } + + case (uintptr_t) HKEY_LOCAL_MACHINE: { + UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Registry\\Machine"); + NTSTATUS status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) { + .Length = sizeof(OBJECT_ATTRIBUTES), + .RootDirectory = NULL, + .ObjectName = &path, + .Attributes = OBJ_CASE_INSENSITIVE, + }); + if (!NT_SUCCESS(status)) { + FF_DEBUG("NtOpenKey(%ls) failed: %s (0x%08lx)", path.Buffer, ffDebugNtStatus(status), status); + return NULL; + } + break; + } + default: + // Unsupported + FF_DEBUG("Unsupported root key: %p", hKey); + assert(false); + return NULL; + } + hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT] = result; + FF_DEBUG("Opened root key %s -> %p", hKey2Str(result), result); + return result; +} + +bool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error) { + assert(hKey); + assert(subKeyW); + assert(result); + + FF_DEBUG("Opening subkey %s\\%ls for read", hKey2Str(hKey), subKeyW); + + USHORT subKeyLen = (USHORT) (wcslen(subKeyW) * sizeof(wchar_t)); + if (!NT_SUCCESS(NtOpenKey(result, KEY_READ, &(OBJECT_ATTRIBUTES) { + .Length = sizeof(OBJECT_ATTRIBUTES), + .RootDirectory = hKey, + .ObjectName = &(UNICODE_STRING) { + .Length = subKeyLen, + .MaximumLength = subKeyLen + (USHORT) sizeof(wchar_t), + .Buffer = (wchar_t*) subKeyW, + }, + }))) { + FF_DEBUG("NtOpenKey(%s\\) failed", hKey2Str(hKey)); + if (error) { + FF_STRBUF_AUTO_DESTROY subKeyA = ffStrbufCreateWS(subKeyW); + ffStrbufAppendF(error, "NtOpenKey(%s\\%s) failed", hKey2Str(hKey), subKeyA.chars); + } + return false; + } + FF_DEBUG("Opened subkey under %s -> %p", hKey2Str(hKey), *result); + return true; +} + +static bool processRegValue(const FFRegValueArg* arg, const ULONG regType, const void* regData, ULONG regDataLen, FFstrbuf* error) { + switch (arg->type) { + case FF_ARG_TYPE_STRBUF: { + if (regType != REG_SZ && regType != REG_EXPAND_SZ) { + goto type_mismatch; + } + + FFstrbuf* strbuf = (FFstrbuf*) arg->value; + uint32_t strLen = regDataLen / sizeof(wchar_t); + if (strLen == 0) { + ffStrbufClear(strbuf); + } else { + const wchar_t* ws = (const wchar_t*) regData; + if (ws[strLen - 1] == L'\0') { + --strLen; + } + ffStrbufSetNWS(strbuf, strLen, ws); + } + break; + } + + case FF_ARG_TYPE_UINT: + case FF_ARG_TYPE_UINT64: + case FF_ARG_TYPE_UINT16: + case FF_ARG_TYPE_UINT8: + case FF_ARG_TYPE_BOOL: { + uint64_t value = 0; + + if (regType == REG_DWORD) { + if (regDataLen < sizeof(uint32_t)) { + goto type_mismatch; + } + value = *(uint32_t*) regData; + } else if (regType == REG_QWORD) { + if (regDataLen < sizeof(uint64_t)) { + goto type_mismatch; + } + value = *(uint64_t*) regData; + } else { + goto type_mismatch; + } + + if (arg->type == FF_ARG_TYPE_UINT) { + *(uint32_t*) arg->value = (uint32_t) value; + } else if (arg->type == FF_ARG_TYPE_UINT64) { + *(uint64_t*) arg->value = (uint64_t) value; + } else if (arg->type == FF_ARG_TYPE_UINT16) { + *(uint16_t*) arg->value = (uint16_t) value; + } else if (arg->type == FF_ARG_TYPE_UINT8) { + *(uint8_t*) arg->value = (uint8_t) value; + } else if (arg->type == FF_ARG_TYPE_BOOL) { + *(bool*) arg->value = value != 0; + } + break; + } + + case FF_ARG_TYPE_FLOAT: { + if (regDataLen < sizeof(float)) { + goto type_mismatch; + } + *(float*) arg->value = *(float*) regData; + break; + } + + case FF_ARG_TYPE_DOUBLE: { + if (regDataLen < sizeof(double)) { + goto type_mismatch; + } + *(double*) arg->value = *(double*) regData; + break; + } + + case FF_ARG_TYPE_LIST: { + if (regType != REG_MULTI_SZ) { + goto type_mismatch; + } + + FFlist* list = (FFlist*) arg->value; + ffListClear(list); + + for ( + const wchar_t* ptr = (const wchar_t*) regData; + (const uint8_t*) ptr < (const uint8_t*) regData + regDataLen && *ptr; + ptr++) { + uint32_t strLen = (uint32_t) wcsnlen(ptr, regDataLen / sizeof(wchar_t) - (size_t) (ptr - (const wchar_t*) regData)); + ffStrbufInitNWS(FF_LIST_ADD(FFstrbuf, *list), strLen, ptr); + ptr += strLen; + } + break; + } + + case FF_ARG_TYPE_BUFFER: { + FFArgBuffer* buffer = (FFArgBuffer*) arg->value; + if (buffer->length == 0) { + buffer->data = malloc(regDataLen); + } else if (buffer->length < regDataLen) { + if (error) { + FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "ffRegReadValues(%s) buffer too small (%u): expected %u", nameA.chars, (unsigned) buffer->length, (unsigned) regDataLen); + } + return false; + } + buffer->length = regDataLen; + memcpy(buffer->data, regData, regDataLen); + break; + } + + case FF_ARG_TYPE_INT: // Use UINT instead + case FF_ARG_TYPE_STRING: + case FF_ARG_TYPE_NULL: + default: + if (error) { + FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "processRegValue(%s) unsupported FFArgType %u", nameA.chars, (unsigned) arg->type); + } + return false; + } + + return true; + +type_mismatch: + FF_DEBUG("ffRegReadValues(%ls) type mismatch: regType=%u, argType=%u, dataLen=%u", + arg->name ?: L"(default)", + (unsigned) regType, + (unsigned) arg->type, + (unsigned) regDataLen); + if (error) { + FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "ffRegReadValues(%s) type mismatch: regType=%u, argType=%u, dataLen=%u", nameA.chars, (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen); + } + return false; +} + +bool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error) { + UNICODE_STRING* valueNameU = &(UNICODE_STRING) { + .Length = arg->name ? (USHORT) (wcslen(arg->name) * sizeof(wchar_t)) : 0 /*(default)*/, + .MaximumLength = 0, + .Buffer = (wchar_t*) arg->name, + }; + + alignas(KEY_VALUE_PARTIAL_INFORMATION) uint8_t staticBuffer[128 + sizeof(KEY_VALUE_PARTIAL_INFORMATION)]; + FF_AUTO_FREE uint8_t* dynamicBuffer = NULL; + + KEY_VALUE_PARTIAL_INFORMATION* buffer = (KEY_VALUE_PARTIAL_INFORMATION*) &staticBuffer; + DWORD bufSize = sizeof(staticBuffer); + if (NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) { + goto process_value; + } + + if (bufSize == 0) { + FF_DEBUG("NtQueryValueKey(%p, %ls) failed (bufSize=0)", hKey, arg->name ?: L"(default)"); + if (error) { + FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "NtQueryValueKey(%p, %s) failed", hKey, valueNameA.chars); + } + return false; + } + + dynamicBuffer = (uint8_t*) malloc(bufSize); + buffer = (KEY_VALUE_PARTIAL_INFORMATION*) dynamicBuffer; + + if (!NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) { + FF_DEBUG("NtQueryValueKey(%p, %ls, buffer=%u) failed", hKey, arg->name ?: L"(default)", (unsigned) bufSize); + if (error) { + FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "NtQueryValueKey(%p, %s, buffer) failed", hKey, valueNameA.chars); + } + return false; + } + +process_value: + FF_DEBUG("Read value from %p (%ls), type=%u, len=%u", hKey, arg->name ?: L"(default)", (unsigned) buffer->Type, (unsigned) buffer->DataLength); + return processRegValue(arg, buffer->Type, buffer->Data, buffer->DataLength, error); +} + +bool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error) { + if (__builtin_expect(argc == 0, false)) { + return true; + } + + assert(argv); + + FF_AUTO_FREE UNICODE_STRING* names = (UNICODE_STRING*) calloc(argc, sizeof(*names)); + FF_AUTO_FREE KEY_VALUE_ENTRY* entries = (KEY_VALUE_ENTRY*) calloc(argc, sizeof(*entries)); + + for (uint32_t i = 0; i < argc; ++i) { + if (__builtin_expect(!argv[i].value, false)) { + FF_DEBUG("ffRegReadValues(argv[%u].value) is NULL", (unsigned) i); + if (error) { + ffStrbufAppendF(error, "ffRegReadValues(argv[%u].pVar) is NULL", (unsigned) i); + } + return false; + } + + names[i] = (UNICODE_STRING) { + .Length = argv[i].name ? (USHORT) (wcslen(argv[i].name) * sizeof(wchar_t)) : 0 /*(default)*/, + .MaximumLength = 0, + .Buffer = (wchar_t*) argv[i].name, + }; + entries[i].ValueName = &names[i]; + } + + ULONG bufferSize = argc * 128; + if (bufferSize < 512) { + bufferSize = 512; + } + + FF_AUTO_FREE uint8_t* buffer = NULL; + + while (true) { + buffer = (uint8_t*) realloc(buffer, bufferSize); + + ULONG writtenSize = bufferSize; + ULONG requiredSize = 0; + NTSTATUS status = NtQueryMultipleValueKey(hKey, entries, argc, buffer, &writtenSize, &requiredSize); + + if (!NT_SUCCESS(status)) { + // Buffer too small: docs guarantee requiredSize is returned when provided. + if (requiredSize > bufferSize) { + FF_DEBUG("NtQueryMultipleValueKey(%p) resize buffer: %u -> %u", hKey, (unsigned) bufferSize, (unsigned) requiredSize); + bufferSize = requiredSize; + continue; + } + + FF_DEBUG("NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status); + if (error) { + ffStrbufAppendF(error, "NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status); + } + return false; + } + + break; + } + + for (uint32_t i = 0; i < argc; ++i) { + const FFRegValueArg* arg = &argv[i]; + const KEY_VALUE_ENTRY* entry = &entries[i]; + + FF_DEBUG("Read value[%u] from %p: type=%u, len=%u", (unsigned) i, hKey, (unsigned) entry->Type, (unsigned) entry->DataLength); + if (!processRegValue(arg, entry->Type, buffer + entry->DataOffset, entry->DataLength, error)) { + return false; + } + } + + return true; +} + +bool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error) { + assert(hKey); + assert(result); + + alignas(KEY_BASIC_INFORMATION) uint8_t buffer[sizeof(KEY_BASIC_INFORMATION) + MAX_PATH * sizeof(wchar_t)]; + ULONG bufSize = (ULONG) sizeof(buffer); + KEY_BASIC_INFORMATION* keyInfo = (KEY_BASIC_INFORMATION*) buffer; + + if (!NT_SUCCESS(NtEnumerateKey(hKey, index, KeyBasicInformation, keyInfo, bufSize, &bufSize))) { + FF_DEBUG("NtEnumerateKey(hKey=%p, index=%u) failed", hKey, (unsigned) index); + if (error) { + ffStrbufAppendF(error, "NtEnumerateKey(hKey=%p, %u, keyInfo) failed", hKey, (unsigned) index); + } + return false; + } + + ffStrbufSetNWS(result, keyInfo->NameLength / sizeof(wchar_t), keyInfo->Name); + return true; +} + +bool ffRegGetNSubKeys(HANDLE hKey, uint32_t* result, FFstrbuf* error) { + assert(hKey); + assert(result); + + alignas(KEY_FULL_INFORMATION) uint8_t buffer[sizeof(KEY_FULL_INFORMATION) + MAX_PATH * sizeof(wchar_t)]; + ULONG bufSize = sizeof(buffer); + KEY_FULL_INFORMATION* keyInfo = (KEY_FULL_INFORMATION*) buffer; + + if (!NT_SUCCESS(NtQueryKey(hKey, KeyFullInformation, keyInfo, bufSize, &bufSize))) { + FF_DEBUG("NtQueryKey(hKey=%p, KeyFullInformation) failed", hKey); + if (error) { + ffStrbufAppendF(error, "NtQueryKey(hKey=%p, KeyFullInformation, keyInfo) failed", hKey); + } + return false; + } + + *result = (uint32_t) keyInfo->SubKeys; + return true; +} -- cgit v1.2.3