#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; }