diff --git a/boot/freeldr/freeldr/ntldr/registry.c b/boot/freeldr/freeldr/ntldr/registry.c index adc0f9d075c..0b81b71cf16 100644 --- a/boot/freeldr/freeldr/ntldr/registry.c +++ b/boot/freeldr/freeldr/ntldr/registry.c @@ -2,6 +2,7 @@ * FreeLoader * * Copyright (C) 2014 Timo Kreuzer + * 2022 George Bișoc * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,6 +42,8 @@ HKEY CurrentControlSetKey = NULL; #define GET_HHIVE_FROM_HKEY(hKey) GET_HHIVE(CmSystemHive) #define GET_CM_KEY_NODE(hHive, hKey) ((PCM_KEY_NODE)HvGetCell(hHive, HKEY_TO_HCI(hKey))) +#define GET_HBASE_BLOCK(ChunkBase) ((PHBASE_BLOCK)ChunkBase) + PVOID NTAPI CmpAllocate( @@ -62,22 +65,47 @@ CmpFree( FrLdrHeapFree(Ptr, 0); } +/** + * @brief + * Initializes a flat hive descriptor for the + * hive and validates the registry hive. + * Volatile data is purged during this procedure + * for initialization. + * + * @param[in] CmHive + * A pointer to a CM (in-memory) hive descriptor + * containing the hive descriptor to be initialized. + * + * @param[in] ChunkBase + * An arbitrary pointer that points to the registry + * chunk base. This pointer serves as the base block + * containing the hive file header data. + * + * @param[in] LoadAlternate + * If set to TRUE, the function will initialize the + * hive as an alternate hive, otherwise FALSE to initialize + * it as primary. + * + * @return + * Returns TRUE if the hive has been initialized + * and registry data inside the hive is valid, FALSE + * otherwise. + */ +static BOOLEAN -RegImportBinaryHive( +RegInitializeHive( + _In_ PCMHIVE CmHive, _In_ PVOID ChunkBase, - _In_ ULONG ChunkSize) + _In_ BOOLEAN LoadAlternate) { NTSTATUS Status; - PCM_KEY_NODE KeyNode; + CM_CHECK_REGISTRY_STATUS CmStatusCode; - TRACE("RegImportBinaryHive(%p, 0x%lx)\n", ChunkBase, ChunkSize); - - /* Allocate and initialize the hive */ - CmSystemHive = FrLdrTempAlloc(sizeof(CMHIVE), 'eviH'); - Status = HvInitialize(GET_HHIVE(CmSystemHive), + /* Initialize the hive */ + Status = HvInitialize(GET_HHIVE(CmHive), HINIT_FLAT, // HINIT_MEMORY_INPLACE 0, - 0, + LoadAlternate ? HFILE_TYPE_ALTERNATE : HFILE_TYPE_PRIMARY, ChunkBase, CmpAllocate, CmpFree, @@ -89,11 +117,374 @@ RegImportBinaryHive( NULL); if (!NT_SUCCESS(Status)) { - ERR("Corrupted hive %p!\n", ChunkBase); - FrLdrTempFree(CmSystemHive, 'eviH'); + ERR("Failed to initialize the flat hive (Status 0x%lx)\n", Status); return FALSE; } + /* Now check the hive and purge volatile data */ + CmStatusCode = CmCheckRegistry(CmHive, CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES | CM_CHECK_REGISTRY_VALIDATE_HIVE); + if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode)) + { + ERR("CmCheckRegistry detected problems with the loaded flat hive (check code %lu)\n", CmStatusCode); + return FALSE; + } + + return TRUE; +} + +/** + * @brief + * Loads and reads a hive log at specified + * file offset. + * + * @param[in] DirectoryPath + * A pointer to a string that denotes the directory + * path of the hives and logs location. + * + * @param[in] LogFileOffset + * The file offset of which this function uses to + * seek at specific position during read. + * + * @param[in] LogName + * A pointer to a string that denotes the name of + * the desired hive log (e.g. "SYSTEM"). + * + * @param[out] LogData + * A pointer to the returned hive log data that was + * read. The following data varies depending on the + * specified offset set up by the caller, that is used + * to where to start reading from the hive log. + * + * @return + * Returns TRUE if the hive log was loaded and read + * successfully, FALSE otherwise. + * + * @remarks + * The returned log data pointer to the caller is a + * virtual address. You must use VaToPa that converts + * the address to a physical one in order to actually + * use it! + */ +static +BOOLEAN +RegLoadHiveLog( + _In_ PCSTR DirectoryPath, + _In_ ULONG LogFileOffset, + _In_ PCSTR LogName, + _Out_ PVOID *LogData) +{ + ARC_STATUS Status; + ULONG LogId; + CHAR LogPath[MAX_PATH]; + ULONG LogFileSize; + FILEINFORMATION FileInfo; + LARGE_INTEGER Position; + ULONG BytesRead; + PVOID LogDataVirtual; + PVOID LogDataPhysical; + + /* Build the full path to the hive log */ + RtlStringCbCopyA(LogPath, sizeof(LogPath), DirectoryPath); + RtlStringCbCatA(LogPath, sizeof(LogPath), LogName); + + /* Open the file */ + Status = ArcOpen(LogPath, OpenReadOnly, &LogId); + if (Status != ESUCCESS) + { + ERR("Failed to open %s (ARC code %lu)\n", LogName, Status); + return FALSE; + } + + /* Get the file length */ + Status = ArcGetFileInformation(LogId, &FileInfo); + if (Status != ESUCCESS) + { + ERR("Failed to get file information from %s (ARC code %lu)\n", LogName, Status); + ArcClose(LogId); + return FALSE; + } + + /* Capture the size of the hive log file */ + LogFileSize = FileInfo.EndingAddress.LowPart; + if (LogFileSize == 0) + { + ERR("LogFileSize is 0, %s is corrupt\n", LogName); + ArcClose(LogId); + return FALSE; + } + + /* Allocate memory blocks for our log data */ + LogDataPhysical = MmAllocateMemoryWithType( + MM_SIZE_TO_PAGES(LogFileSize + MM_PAGE_SIZE - 1) << MM_PAGE_SHIFT, + LoaderRegistryData); + if (LogDataPhysical == NULL) + { + ERR("Failed to allocate memory for log data\n"); + ArcClose(LogId); + return FALSE; + } + + /* Convert the address to virtual so that it can be useable */ + LogDataVirtual = PaToVa(LogDataPhysical); + + /* Seek within the log file at desired position */ + Position.QuadPart = LogFileOffset; + Status = ArcSeek(LogId, &Position, SeekAbsolute); + if (Status != ESUCCESS) + { + ERR("Failed to seek at %s (ARC code %lu)\n", LogName, Status); + ArcClose(LogId); + return FALSE; + } + + /* And read the actual data from the log */ + Status = ArcRead(LogId, LogDataPhysical, LogFileSize, &BytesRead); + if (Status != ESUCCESS) + { + ERR("Failed to read %s (ARC code %lu)\n", LogName, Status); + ArcClose(LogId); + return FALSE; + } + + *LogData = LogDataVirtual; + ArcClose(LogId); + return TRUE; +} + +/** + * @brief + * Recovers the header base block of a flat + * registry hive. + * + * @param[in] ChunkBase + * A pointer to the registry hive chunk base of + * which the damaged header block is to be recovered. + * + * @param[in] DirectoryPath + * A pointer to a string that denotes the directory + * path of the hives and logs location. + * + * @param[in] LogName + * A pointer to a string that denotes the name of + * the desired hive log (e.g. "SYSTEM"). + * + * @return + * Returns TRUE if the header base block was successfully + * recovered, FALSE otherwise. + */ +static +BOOLEAN +RegRecoverHeaderHive( + _Inout_ PVOID ChunkBase, + _In_ PCSTR DirectoryPath, + _In_ PCSTR LogName) +{ + BOOLEAN Success; + CHAR FullLogFileName[MAX_PATH]; + PVOID LogData; + PHBASE_BLOCK HiveBaseBlock; + PHBASE_BLOCK LogBaseBlock; + + /* Build the complete path of the hive log */ + RtlStringCbCopyA(FullLogFileName, sizeof(FullLogFileName), LogName); + RtlStringCbCatA(FullLogFileName, sizeof(FullLogFileName), ".LOG"); + Success = RegLoadHiveLog(DirectoryPath, 0, FullLogFileName, &LogData); + if (!Success) + { + ERR("Failed to read the hive log\n"); + return FALSE; + } + + /* Make sure the header from the hive log is actually sane */ + LogData = VaToPa(LogData); + LogBaseBlock = GET_HBASE_BLOCK(LogData); + if (!HvpVerifyHiveHeader(LogBaseBlock, HFILE_TYPE_LOG)) + { + ERR("The hive log has corrupt base block\n"); + return FALSE; + } + + /* Copy the healthy header base block into the primary hive */ + HiveBaseBlock = GET_HBASE_BLOCK(ChunkBase); + WARN("Recovering the hive base block...\n"); + RtlCopyMemory(HiveBaseBlock, + LogBaseBlock, + LogBaseBlock->Cluster * HSECTOR_SIZE); + HiveBaseBlock->Type = HFILE_TYPE_PRIMARY; + return TRUE; +} + +/** + * @brief + * Recovers the corrupt data of a primary flat + * registry hive. + * + * @param[in] ChunkBase + * A pointer to the registry hive chunk base of + * which the damaged hive data is to be replaced + * with healthy data from the corresponding hive log. + * + * @param[in] DirectoryPath + * A pointer to a string that denotes the directory + * path of the hives and logs location. + * + * @param[in] LogName + * A pointer to a string that denotes the name of + * the desired hive log (e.g. "SYSTEM"). + * + * @return + * Returns TRUE if the hive data was successfully + * recovered, FALSE otherwise. + * + * @remarks + * Data recovery of the target hive does not always + * guarantee the primary hive is fully recovered. + * It could happen a block from a hive log is not + * marked dirty (pending to be written to disk) that + * has healthy data therefore the following bad block + * would still remain in corrupt state in the main primary + * hive. In such scenarios an alternate hive must be replayed. + */ +static +BOOLEAN +RegRecoverDataHive( + _Inout_ PVOID ChunkBase, + _In_ PCSTR DirectoryPath, + _In_ PCSTR LogName) +{ + BOOLEAN Success; + ULONG StorageLength; + ULONG BlockIndex, LogIndex; + PUCHAR BlockPtr, BlockDest; + CHAR FullLogFileName[MAX_PATH]; + PVOID LogData; + PUCHAR LogDataPhysical; + PHBASE_BLOCK HiveBaseBlock; + + /* Build the complete path of the hive log */ + RtlStringCbCopyA(FullLogFileName, sizeof(FullLogFileName), LogName); + RtlStringCbCatA(FullLogFileName, sizeof(FullLogFileName), ".LOG"); + Success = RegLoadHiveLog(DirectoryPath, HV_LOG_HEADER_SIZE, FullLogFileName, &LogData); + if (!Success) + { + ERR("Failed to read the hive log\n"); + return FALSE; + } + + /* Make sure the dirty vector signature is there otherwise the hive log is corrupt */ + LogDataPhysical = (PUCHAR)VaToPa(LogData); + if (*((PULONG)LogDataPhysical) != HV_LOG_DIRTY_SIGNATURE) + { + ERR("The hive log dirty signature could not be found\n"); + return FALSE; + } + + /* Copy the dirty data into the primary hive */ + LogIndex = 0; + BlockIndex = 0; + HiveBaseBlock = GET_HBASE_BLOCK(ChunkBase); + StorageLength = HiveBaseBlock->Length / HBLOCK_SIZE; + for (; BlockIndex < StorageLength; ++BlockIndex) + { + /* Skip this block if it's not dirty and go to the next one */ + if (LogDataPhysical[BlockIndex + sizeof(HV_LOG_DIRTY_SIGNATURE)] != HV_LOG_DIRTY_BLOCK) + { + continue; + } + + /* Read the dirty block and copy it at right offsets */ + BlockPtr = (PUCHAR)((ULONG_PTR)LogDataPhysical + 2 * HSECTOR_SIZE + LogIndex * HBLOCK_SIZE); + BlockDest = (PUCHAR)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HBLOCK_SIZE); + RtlCopyMemory(BlockDest, BlockPtr, HBLOCK_SIZE); + + /* Increment the index in log as we continue further */ + LogIndex++; + } + + /* Fix the secondary sequence of the primary hive and compute a new checksum */ + HiveBaseBlock->Sequence2 = HiveBaseBlock->Sequence1; + HiveBaseBlock->CheckSum = HvpHiveHeaderChecksum(HiveBaseBlock); + return TRUE; +} + +/** + * @brief + * Imports the SYSTEM binary hive from + * the registry base chunk that's been + * provided by the loader block. + * + * @param[in] ChunkBase + * A pointer to the registry base chunk + * that serves for SYSTEM hive initialization. + * + * @param[in] ChunkSize + * The size of the registry base chunk. This + * parameter refers to the actual size of + * the SYSTEM hive. This parameter is currently + * unused. + * + * @param[in] LoadAlternate + * If set to TRUE, the function will initialize the + * hive as an alternate hive, otherwise FALSE to initialize + * it as primary. + * + * @return + * Returns TRUE if hive importing and initialization + * have succeeded, FALSE otherwise. + */ +BOOLEAN +RegImportBinaryHive( + _In_ PVOID ChunkBase, + _In_ ULONG ChunkSize, + _In_ PCSTR SearchPath, + _In_ BOOLEAN LoadAlternate) +{ + BOOLEAN Success; + PCM_KEY_NODE KeyNode; + + TRACE("RegImportBinaryHive(%p, 0x%lx)\n", ChunkBase, ChunkSize); + + /* Assume that we don't need boot recover, unless we have to */ + ((PHBASE_BLOCK)ChunkBase)->BootRecover = HBOOT_NO_BOOT_RECOVER; + + /* Allocate and initialize the hive */ + CmSystemHive = FrLdrTempAlloc(sizeof(CMHIVE), 'eviH'); + Success = RegInitializeHive(CmSystemHive, ChunkBase, LoadAlternate); + if (!Success) + { + /* Free the buffer and retry again */ + FrLdrTempFree(CmSystemHive, 'eviH'); + CmSystemHive = NULL; + + if (!RegRecoverHeaderHive(ChunkBase, SearchPath, "SYSTEM")) + { + ERR("Failed to recover the hive header block\n"); + return FALSE; + } + + if (!RegRecoverDataHive(ChunkBase, SearchPath, "SYSTEM")) + { + ERR("Failed to recover the hive data\n"); + return FALSE; + } + + /* Now retry initializing the hive again */ + CmSystemHive = FrLdrTempAlloc(sizeof(CMHIVE), 'eviH'); + Success = RegInitializeHive(CmSystemHive, ChunkBase, LoadAlternate); + if (!Success) + { + ERR("Corrupted hive (despite recovery) %p\n", ChunkBase); + FrLdrTempFree(CmSystemHive, 'eviH'); + return FALSE; + } + + /* + * Acknowledge the kernel we recovered the SYSTEM hive + * on our side by applying log data. + */ + ((PHBASE_BLOCK)ChunkBase)->BootRecover = HBOOT_BOOT_RECOVERED_BY_HIVE_LOG; + } + /* Save the root key node */ SystemHive = GET_HHIVE(CmSystemHive); SystemRootCell = SystemHive->BaseBlock->RootCell; diff --git a/boot/freeldr/freeldr/ntldr/registry.h b/boot/freeldr/freeldr/ntldr/registry.h index 769d5b7c477..54ecbfd296c 100644 --- a/boot/freeldr/freeldr/ntldr/registry.h +++ b/boot/freeldr/freeldr/ntldr/registry.h @@ -30,7 +30,9 @@ typedef HANDLE HKEY, *PHKEY; BOOLEAN RegImportBinaryHive( _In_ PVOID ChunkBase, - _In_ ULONG ChunkSize); + _In_ ULONG ChunkSize, + _In_ PCSTR SearchPath, + _In_ BOOLEAN LoadAlternate); BOOLEAN RegInitCurrentControlSet( diff --git a/boot/freeldr/freeldr/ntldr/wlregistry.c b/boot/freeldr/freeldr/ntldr/wlregistry.c index cea9c2544db..124dc5fc048 100644 --- a/boot/freeldr/freeldr/ntldr/wlregistry.c +++ b/boot/freeldr/freeldr/ntldr/wlregistry.c @@ -31,14 +31,22 @@ static BOOLEAN WinLdrScanRegistry( IN OUT PLIST_ENTRY BootDriverListHead); +typedef enum _BAD_HIVE_REASON +{ + GoodHive = 1, + CorruptHive, + NoHive, + NoHiveAlloc +} BAD_HIVE_REASON, *PBAD_HIVE_REASON; /* FUNCTIONS **************************************************************/ static BOOLEAN WinLdrLoadSystemHive( - IN OUT PLOADER_PARAMETER_BLOCK LoaderBlock, - IN PCSTR DirectoryPath, - IN PCSTR HiveName) + _Inout_ PLOADER_PARAMETER_BLOCK LoaderBlock, + _In_ PCSTR DirectoryPath, + _In_ PCSTR HiveName, + _Out_ PBAD_HIVE_REASON Reason) { ULONG FileId; CHAR FullHiveName[MAX_PATH]; @@ -49,6 +57,9 @@ WinLdrLoadSystemHive( PVOID HiveDataVirtual; ULONG BytesRead; + /* Do not setup any bad reason for now */ + *Reason = GoodHive; + /* Concatenate path and filename to get the full name */ RtlStringCbCopyA(FullHiveName, sizeof(FullHiveName), DirectoryPath); RtlStringCbCatA(FullHiveName, sizeof(FullHiveName), HiveName); @@ -58,6 +69,7 @@ WinLdrLoadSystemHive( if (Status != ESUCCESS) { WARN("Error while opening '%s', Status: %u\n", FullHiveName, Status); + *Reason = NoHive; return FALSE; } @@ -66,6 +78,7 @@ WinLdrLoadSystemHive( if (Status != ESUCCESS) { WARN("Hive file has 0 size!\n"); + *Reason = CorruptHive; ArcClose(FileId); return FALSE; } @@ -79,6 +92,7 @@ WinLdrLoadSystemHive( if (HiveDataPhysical == NULL) { WARN("Could not alloc memory for hive!\n"); + *Reason = NoHiveAlloc; ArcClose(FileId); return FALSE; } @@ -95,6 +109,7 @@ WinLdrLoadSystemHive( if (Status != ESUCCESS) { WARN("Error while reading '%s', Status: %u\n", FullHiveName, Status); + *Reason = CorruptHive; ArcClose(FileId); return FALSE; } @@ -113,9 +128,12 @@ WinLdrInitSystemHive( IN BOOLEAN Setup) { CHAR SearchPath[1024]; + PVOID ChunkBase; PCSTR HiveName; BOOLEAN Success; + BAD_HIVE_REASON Reason; + /* Load the corresponding text-mode setup system hive or the standard hive */ if (Setup) { RtlStringCbCopyA(SearchPath, sizeof(SearchPath), SystemRoot); @@ -123,31 +141,83 @@ WinLdrInitSystemHive( } else { - // There is a simple logic here: try to load usual hive (system), if it - // fails, then give system.alt a try, and finally try a system.sav - - // FIXME: For now we only try system RtlStringCbCopyA(SearchPath, sizeof(SearchPath), SystemRoot); RtlStringCbCatA(SearchPath, sizeof(SearchPath), "system32\\config\\"); HiveName = "SYSTEM"; } TRACE("WinLdrInitSystemHive: loading hive %s%s\n", SearchPath, HiveName); - Success = WinLdrLoadSystemHive(LoaderBlock, SearchPath, HiveName); + Success = WinLdrLoadSystemHive(LoaderBlock, SearchPath, HiveName, &Reason); if (!Success) { + /* Check whether the SYSTEM hive does not exist or is too corrupt to be read */ + if (Reason == CorruptHive || Reason == NoHive) + { + /* Try loading the alternate hive, the main hive should be recovered later */ + goto LoadAlternateHive; + } + + /* We are failing for other reason, bail out */ UiMessageBox("Could not load %s hive!", HiveName); return FALSE; } /* Import what was loaded */ - Success = RegImportBinaryHive(VaToPa(LoaderBlock->RegistryBase), LoaderBlock->RegistryLength); + Success = RegImportBinaryHive(VaToPa(LoaderBlock->RegistryBase), LoaderBlock->RegistryLength, SearchPath, FALSE); if (!Success) { - UiMessageBox("Importing binary hive failed!"); - return FALSE; + /* + * Importing of the SYSTEM hive failed. The scenarios that would + * have made this possible are the following: + * + * 1. The primary hive is corrupt beyond repair (such as when + * core FS structures are total toast); + * + * 2. Repairing the hive could with a LOG could not recover it + * to the fullest. This is the case when the hive and LOG have + * diverged too much, or are mismatched, or the containing healthy + * data in the LOG was not marked as dirty that could be copied + * into the primary hive; + * + * 3. LOG is bad (e.g. corrupt dirty vector); + * + * 4. LOG does not physically exist on the backing storage. + * + * 5. SYSTEM hive does not physically exist or it is a 0 bytes file + * (the latter case still counts as corruption). + * + * With the hope to boot the system, load the mirror counterpart + * of the main hive, the alternate. The kernel should be able to recover + * the main hive later on as soon as it starts writing to it. + */ +LoadAlternateHive: + HiveName = "SYSTEM.ALT"; + Success = WinLdrLoadSystemHive(LoaderBlock, SearchPath, HiveName, &Reason); + if (!Success) + { + UiMessageBox("Could not load %s hive!", HiveName); + return FALSE; + } + + /* Retry importing it again */ + Success = RegImportBinaryHive(VaToPa(LoaderBlock->RegistryBase), LoaderBlock->RegistryLength, SearchPath, TRUE); + if (!Success) + { + UiMessageBox("Importing binary hive failed!"); + return FALSE; + } + + /* + * Acknowledge the kernel we recovered the SYSTEM hive + * on our side by loading the alternate variant of the hive. + */ + WARN("SYSTEM hive does not exist or is corrupt and SYSTEM.ALT has been loaded!\n"); + ChunkBase = VaToPa(LoaderBlock->RegistryBase); + ((PHBASE_BLOCK)ChunkBase)->BootRecover = HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE; } + // FIXME: Load SYSTEM.SAV if GUI setup installation is still in progress + /* Initialize the 'CurrentControlSet' link */ if (!RegInitCurrentControlSet(FALSE)) {