/* * PROJECT: ReactOS Kernel * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Configuration Manager Library - Registry Hive Loading & Initialization * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl * Copyright 2005 Filip Navara * Copyright 2021 Max Korostil * Copyright 2022 George Bișoc */ #include "cmlib.h" #define NDEBUG #include /* ENUMERATIONS *************************************************************/ typedef enum _RESULT { NotHive, Fail, NoMemory, HiveSuccess, RecoverHeader, RecoverData, SelfHeal } RESULT; /* PRIVATE FUNCTIONS ********************************************************/ /** * @brief * Validates the base block header of a registry * file (hive or log). * * @param[in] BaseBlock * A pointer to a base block header to * be validated. * * @param[in] FileType * The file type of a registry file to check * against the file type of the base block. * * @return * Returns TRUE if the base block header is valid, * FALSE otherwise. */ BOOLEAN CMAPI HvpVerifyHiveHeader( _In_ PHBASE_BLOCK BaseBlock, _In_ ULONG FileType) { if (BaseBlock->Signature != HV_HBLOCK_SIGNATURE || BaseBlock->Major != HSYS_MAJOR || BaseBlock->Minor < HSYS_MINOR || BaseBlock->Type != FileType || BaseBlock->Format != HBASE_FORMAT_MEMORY || BaseBlock->Cluster != 1 || BaseBlock->Sequence1 != BaseBlock->Sequence2 || HvpHiveHeaderChecksum(BaseBlock) != BaseBlock->CheckSum) { DPRINT1("Verify Hive Header failed:\n"); DPRINT1(" Signature: 0x%x, expected 0x%x; Major: 0x%x, expected 0x%x\n", BaseBlock->Signature, HV_HBLOCK_SIGNATURE, BaseBlock->Major, HSYS_MAJOR); DPRINT1(" Minor: 0x%x expected to be >= 0x%x; Type: 0x%x, expected 0x%x\n", BaseBlock->Minor, HSYS_MINOR, BaseBlock->Type, FileType); DPRINT1(" Format: 0x%x, expected 0x%x; Cluster: 0x%x, expected 1\n", BaseBlock->Format, HBASE_FORMAT_MEMORY, BaseBlock->Cluster); DPRINT1(" Sequence: 0x%x, expected 0x%x; Checksum: 0x%x, expected 0x%x\n", BaseBlock->Sequence1, BaseBlock->Sequence2, HvpHiveHeaderChecksum(BaseBlock), BaseBlock->CheckSum); return FALSE; } return TRUE; } /** * @brief * Frees all the bins within storage space * associated with a hive descriptor. * * @param[in] Hive * A pointer to a hive descriptor where * all the bins are to be freed. */ VOID CMAPI HvpFreeHiveBins( _In_ PHHIVE Hive) { ULONG i; PHBIN Bin; ULONG Storage; for (Storage = 0; Storage < Hive->StorageTypeCount; Storage++) { Bin = NULL; for (i = 0; i < Hive->Storage[Storage].Length; i++) { if (Hive->Storage[Storage].BlockList[i].BinAddress == (ULONG_PTR)NULL) continue; if (Hive->Storage[Storage].BlockList[i].BinAddress != (ULONG_PTR)Bin) { Bin = (PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress; Hive->Free((PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress, 0); } Hive->Storage[Storage].BlockList[i].BinAddress = (ULONG_PTR)NULL; Hive->Storage[Storage].BlockList[i].BlockAddress = (ULONG_PTR)NULL; } if (Hive->Storage[Storage].Length) Hive->Free(Hive->Storage[Storage].BlockList, 0); } } /** * @brief * Allocates a cluster-aligned hive base header block. * * @param[in] Hive * A pointer to a hive descriptor where * the header block allocator function is to * be gathered from. * * @param[in] Paged * If set to TRUE, the allocated base block will reside * in paged pool, otherwise it will reside in non paged * pool. * * @param[in] Tag * A tag name to supply for the allocated memory block * for identification. This is for debugging purposes. * * @return * Returns an allocated base block header if the function * succeeds, otherwise it returns NULL. */ static __inline PHBASE_BLOCK HvpAllocBaseBlockAligned( _In_ PHHIVE Hive, _In_ BOOLEAN Paged, _In_ ULONG Tag) { PHBASE_BLOCK BaseBlock; ULONG Alignment; ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); /* Allocate the buffer */ BaseBlock = Hive->Allocate(Hive->BaseBlockAlloc, Paged, Tag); if (!BaseBlock) return NULL; /* Check for, and enforce, alignment */ Alignment = Hive->Cluster * HSECTOR_SIZE -1; if ((ULONG_PTR)BaseBlock & Alignment) { /* Free the old header and reallocate a new one, always paged */ Hive->Free(BaseBlock, Hive->BaseBlockAlloc); BaseBlock = Hive->Allocate(PAGE_SIZE, TRUE, Tag); if (!BaseBlock) return NULL; Hive->BaseBlockAlloc = PAGE_SIZE; } return BaseBlock; } /** * @brief * Initializes a NULL-terminated Unicode hive file name * of a hive header by copying the last 31 characters of * the hive file name. Mainly used for debugging purposes. * * @param[in,out] BaseBlock * A pointer to a base block header where the hive * file name is to be copied to. * * @param[in] FileName * A pointer to a Unicode string structure containing * the hive file name to be copied from. If this argument * is NULL, the base block will not have any hive file name. */ static VOID HvpInitFileName( _Inout_ PHBASE_BLOCK BaseBlock, _In_opt_ PCUNICODE_STRING FileName) { ULONG_PTR Offset; SIZE_T Length; /* Always NULL-initialize */ RtlZeroMemory(BaseBlock->FileName, (HIVE_FILENAME_MAXLEN + 1) * sizeof(WCHAR)); /* Copy the 31 last characters of the hive file name if any */ if (!FileName) return; if (FileName->Length / sizeof(WCHAR) <= HIVE_FILENAME_MAXLEN) { Offset = 0; Length = FileName->Length; } else { Offset = FileName->Length / sizeof(WCHAR) - HIVE_FILENAME_MAXLEN; Length = HIVE_FILENAME_MAXLEN * sizeof(WCHAR); } RtlCopyMemory(BaseBlock->FileName, FileName->Buffer + Offset, Length); } /** * @brief * Initializes a hive descriptor structure for a * newly created hive in memory. * * @param[in,out] RegistryHive * A pointer to a registry hive descriptor where * the internal structures field are to be initialized * for the said hive. * * @param[in] FileName * A pointer to a Unicode string structure containing * the hive file name to be copied from. If this argument * is NULL, the base block will not have any hive file name. * * @return * Returns STATUS_SUCCESS if the function has created the * hive descriptor successfully. STATUS_NO_MEMORY is returned * if the base header block could not be allocated. */ NTSTATUS CMAPI HvpCreateHive( _Inout_ PHHIVE RegistryHive, _In_opt_ PCUNICODE_STRING FileName) { PHBASE_BLOCK BaseBlock; ULONG Index; /* Allocate the base block */ BaseBlock = HvpAllocBaseBlockAligned(RegistryHive, FALSE, TAG_CM); if (BaseBlock == NULL) return STATUS_NO_MEMORY; /* Clear it */ RtlZeroMemory(BaseBlock, RegistryHive->BaseBlockAlloc); BaseBlock->Signature = HV_HBLOCK_SIGNATURE; BaseBlock->Major = HSYS_MAJOR; BaseBlock->Minor = HSYS_MINOR; BaseBlock->Type = HFILE_TYPE_PRIMARY; BaseBlock->Format = HBASE_FORMAT_MEMORY; BaseBlock->Cluster = 1; BaseBlock->RootCell = HCELL_NIL; BaseBlock->Length = 0; BaseBlock->Sequence1 = 1; BaseBlock->Sequence2 = 1; BaseBlock->TimeStamp.QuadPart = 0ULL; /* * No need to compute the checksum since * the hive resides only in memory so far. */ BaseBlock->CheckSum = 0; /* Set default boot type */ BaseBlock->BootType = HBOOT_TYPE_REGULAR; /* Setup hive data */ RegistryHive->BaseBlock = BaseBlock; RegistryHive->Version = BaseBlock->Minor; // == HSYS_MINOR for (Index = 0; Index < 24; Index++) { RegistryHive->Storage[Stable].FreeDisplay[Index] = HCELL_NIL; RegistryHive->Storage[Volatile].FreeDisplay[Index] = HCELL_NIL; } HvpInitFileName(BaseBlock, FileName); return STATUS_SUCCESS; } /** * @brief * Initializes a hive descriptor from an already loaded * registry hive stored in memory. The data of the hive is * copied and it is prepared for read/write access. * * @param[in] Hive * A pointer to a registry hive descriptor where * the internal structures field are to be initialized * from hive data that is already loaded in memory. * * @param[in] ChunkBase * A pointer to a valid base block header containing * registry header data for initialization. * * @param[in] FileName * A pointer to a Unicode string structure containing * the hive file name to be copied from. If this argument * is NULL, the base block will not have any hive file name. * * @return * Returns STATUS_SUCCESS if the function has initialized the * hive descriptor successfully. STATUS_REGISTRY_CORRUPT is * returned if the base block header contains invalid header * data. STATUS_NO_MEMORY is returned if memory could not * be allocated for registry stuff. */ NTSTATUS CMAPI HvpInitializeMemoryHive( _In_ PHHIVE Hive, _In_ PHBASE_BLOCK ChunkBase, _In_opt_ PCUNICODE_STRING FileName) { SIZE_T BlockIndex; PHBIN Bin, NewBin; ULONG i; ULONG BitmapSize; PULONG BitmapBuffer; SIZE_T ChunkSize; ChunkSize = ChunkBase->Length; DPRINT("ChunkSize: %zx\n", ChunkSize); if (ChunkSize < sizeof(HBASE_BLOCK) || !HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY)) { DPRINT1("Registry is corrupt: ChunkSize 0x%zx < sizeof(HBASE_BLOCK) 0x%zx, " "or HvpVerifyHiveHeader() failed\n", ChunkSize, sizeof(HBASE_BLOCK)); return STATUS_REGISTRY_CORRUPT; } /* Allocate the base block */ Hive->BaseBlock = HvpAllocBaseBlockAligned(Hive, FALSE, TAG_CM); if (Hive->BaseBlock == NULL) return STATUS_NO_MEMORY; RtlCopyMemory(Hive->BaseBlock, ChunkBase, sizeof(HBASE_BLOCK)); /* Setup hive data */ Hive->Version = ChunkBase->Minor; /* * Build a block list from the in-memory chunk and copy the data as * we go. */ Hive->Storage[Stable].Length = (ULONG)(ChunkSize / HBLOCK_SIZE); Hive->Storage[Stable].BlockList = Hive->Allocate(Hive->Storage[Stable].Length * sizeof(HMAP_ENTRY), FALSE, TAG_CM); if (Hive->Storage[Stable].BlockList == NULL) { DPRINT1("Allocating block list failed\n"); Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); return STATUS_NO_MEMORY; } for (BlockIndex = 0; BlockIndex < Hive->Storage[Stable].Length; ) { Bin = (PHBIN)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HBLOCK_SIZE); if (Bin->Signature != HV_HBIN_SIGNATURE || (Bin->Size % HBLOCK_SIZE) != 0 || (Bin->FileOffset / HBLOCK_SIZE) != BlockIndex) { /* * Bin is toast but luckily either the signature, size or offset * is out of order. For the signature it is obvious what we are going * to do, for the offset we are re-positioning the bin back to where it * was and for the size we will set it up to a block size, since technically * a hive bin is large as a block itself to accommodate cells. */ if (!CmIsSelfHealEnabled(FALSE)) { DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x. Self-heal not possible!\n", (unsigned long)BlockIndex, (unsigned)Bin->Signature, (unsigned)Bin->Size); Hive->Free(Hive->Storage[Stable].BlockList, 0); Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); return STATUS_REGISTRY_CORRUPT; } /* Fix this bin */ Bin->Signature = HV_HBIN_SIGNATURE; Bin->Size = HBLOCK_SIZE; Bin->FileOffset = BlockIndex * HBLOCK_SIZE; ChunkBase->BootType |= HBOOT_TYPE_SELF_HEAL; DPRINT1("Bin at index %lu is corrupt and it has been repaired!\n", (unsigned long)BlockIndex); } NewBin = Hive->Allocate(Bin->Size, TRUE, TAG_CM); if (NewBin == NULL) { Hive->Free(Hive->Storage[Stable].BlockList, 0); Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); return STATUS_NO_MEMORY; } Hive->Storage[Stable].BlockList[BlockIndex].BinAddress = (ULONG_PTR)NewBin; Hive->Storage[Stable].BlockList[BlockIndex].BlockAddress = (ULONG_PTR)NewBin; RtlCopyMemory(NewBin, Bin, Bin->Size); if (Bin->Size > HBLOCK_SIZE) { for (i = 1; i < Bin->Size / HBLOCK_SIZE; i++) { Hive->Storage[Stable].BlockList[BlockIndex + i].BinAddress = (ULONG_PTR)NewBin; Hive->Storage[Stable].BlockList[BlockIndex + i].BlockAddress = ((ULONG_PTR)NewBin + (i * HBLOCK_SIZE)); } } BlockIndex += Bin->Size / HBLOCK_SIZE; } if (!NT_SUCCESS(HvpCreateHiveFreeCellList(Hive))) { HvpFreeHiveBins(Hive); Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); return STATUS_NO_MEMORY; } BitmapSize = ROUND_UP(Hive->Storage[Stable].Length, sizeof(ULONG) * 8) / 8; BitmapBuffer = (PULONG)Hive->Allocate(BitmapSize, TRUE, TAG_CM); if (BitmapBuffer == NULL) { HvpFreeHiveBins(Hive); Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); return STATUS_NO_MEMORY; } RtlInitializeBitMap(&Hive->DirtyVector, BitmapBuffer, BitmapSize * 8); RtlClearAllBits(&Hive->DirtyVector); /* * Mark the entire hive as dirty. Indeed we understand if we charged up * the alternate variant of the primary hive (e.g. SYSTEM.ALT) because * FreeLdr could not load the main SYSTEM hive, due to corruptions, and * repairing it with a LOG did not help at all. */ if (ChunkBase->BootRecover == HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE) { RtlSetAllBits(&Hive->DirtyVector); Hive->DirtyCount = Hive->DirtyVector.SizeOfBitMap; } HvpInitFileName(Hive->BaseBlock, FileName); return STATUS_SUCCESS; } /** * @brief * Initializes a hive descriptor for an already loaded hive * that is stored in memory. This descriptor serves to denote * such hive as being "flat", that is, the data and properties * can be only read and not written into. * * @param[in] Hive * A pointer to a registry hive descriptor where * the internal structures fields are to be initialized * from hive data that is already loaded in memory. Such * hive descriptor will become read-only and flat. * * @param[in] ChunkBase * A pointer to a valid base block header containing * registry header data for initialization. * * @return * Returns STATUS_SUCCESS if the function has initialized the * flat hive descriptor. STATUS_REGISTRY_CORRUPT is returned if * the base block header contains invalid header data. */ NTSTATUS CMAPI HvpInitializeFlatHive( _In_ PHHIVE Hive, _In_ PHBASE_BLOCK ChunkBase) { if (!HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY)) return STATUS_REGISTRY_CORRUPT; /* Setup hive data */ Hive->BaseBlock = ChunkBase; Hive->Version = ChunkBase->Minor; Hive->Flat = TRUE; Hive->ReadOnly = TRUE; Hive->StorageTypeCount = 1; /* Set default boot type */ ChunkBase->BootType = HBOOT_TYPE_REGULAR; return STATUS_SUCCESS; } /** * @brief * Retrieves the base block hive header from the * primary hive file stored in physical backing storage. * This function may invoke a self-healing warning if * hive header couldn't be obtained. See Return and Remarks * sections for further information. * * @param[in] Hive * A pointer to a registry hive descriptor that points * to the primary hive being loaded. This descriptor is * needed to obtain the hive header block from said hive. * * @param[in,out] HiveBaseBlock * A pointer returned by the function that contains * the hive header base block buffer obtained from * the primary hive file pointed by the Hive argument. * This parameter must not be NULL! * * @param[in,out] TimeStamp * A pointer returned by the function that contains * the time-stamp of the registry hive file at the * moment of creation or modification. This parameter * must not be NULL! * * @return * This function returns a result indicator. That is, * HiveSuccess is returned if the hive header was obtained * successfully. NoMemory is returned if the hive base block * could not be allocated. NotHive is returned if the hive file * that's been read isn't actually a hive. RecoverHeader is * returned if the header needs to be recovered. RecoverData * is returned if the hive data needs to be returned. * * @remarks * RecoverHeader and RecoverData are status indicators that * invoke a self-healing procedure if the hive header could not * be obtained in a normal way and as a matter of fact the whole * registry initialization procedure is orchestrated. RecoverHeader * implies that the base block header of a hive is corrupt and it * needs to be recovered, whereas RecoverData implies the registry * data is corrupt. The latter status indicator is less severe unlike * the former because the system can cope with data loss. */ RESULT CMAPI HvpGetHiveHeader( _In_ PHHIVE Hive, _Inout_ PHBASE_BLOCK *HiveBaseBlock, _Inout_ PLARGE_INTEGER TimeStamp) { PHBASE_BLOCK BaseBlock; ULONG Result; ULONG FileOffset; PHBIN FirstBin; ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); /* Assume failure and allocate the base block */ *HiveBaseBlock = NULL; BaseBlock = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM); if (!BaseBlock) { DPRINT1("Failed to allocate an aligned base block buffer\n"); return NoMemory; } /* Clear it */ RtlZeroMemory(BaseBlock, sizeof(HBASE_BLOCK)); /* Now read it from disk */ FileOffset = 0; Result = Hive->FileRead(Hive, HFILE_TYPE_PRIMARY, &FileOffset, BaseBlock, Hive->Cluster * HSECTOR_SIZE); if (!Result) { /* * Don't assume the hive is ultimately destroyed * but instead try to read the first block of * the first bin hive. So that we're sure of * ourselves we can somewhat recover this hive. */ FileOffset = HBLOCK_SIZE; Result = Hive->FileRead(Hive, HFILE_TYPE_PRIMARY, &FileOffset, (PVOID)BaseBlock, Hive->Cluster * HSECTOR_SIZE); if (!Result) { DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n"); Hive->Free(BaseBlock, Hive->BaseBlockAlloc); return NotHive; } /* * Deconstruct the casted buffer we got * into a hive bin. Check if the offset * position is in the right place (namely * its offset must be 0 because it's the first * bin) and it should have a sane signature. */ FirstBin = (PHBIN)BaseBlock; if (FirstBin->Signature != HV_HBIN_SIGNATURE || FirstBin->FileOffset != 0) { DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n"); Hive->Free(BaseBlock, Hive->BaseBlockAlloc); return NotHive; } /* * There's still hope for this hive so acknowledge the * caller this hive needs a recoverable header. */ *TimeStamp = BaseBlock->TimeStamp; DPRINT1("The hive is not fully corrupt, the base block needs to be RECOVERED\n"); return RecoverHeader; } /* * This hive has a base block that's not maimed * but is the header data valid? * * FIXME: We must check if primary and secondary * sequences mismatch separately and fire up RecoverData * in that case but due to a hack in HvLoadHive, let * HvpVerifyHiveHeader check the sequences for now. */ if (!HvpVerifyHiveHeader(BaseBlock, HFILE_TYPE_PRIMARY)) { DPRINT1("The hive base header block needs to be RECOVERED\n"); *TimeStamp = BaseBlock->TimeStamp; Hive->Free(BaseBlock, Hive->BaseBlockAlloc); return RecoverHeader; } /* Return information */ *HiveBaseBlock = BaseBlock; *TimeStamp = BaseBlock->TimeStamp; return HiveSuccess; } /* * FIXME: Disable compilation for AMD64 for now since it makes * the FreeLdr binary size so large it makes booting impossible. */ #if !defined(_M_AMD64) /** * @brief * Computes the hive space size by querying * the file size of the associated hive file. * * @param[in] Hive * A pointer to a hive descriptor where the * hive length size is to be calculated. * * @return * Returns the computed hive size. */ ULONG CMAPI HvpQueryHiveSize( _In_ PHHIVE Hive) { #if !defined(CMLIB_HOST) && !defined(_BLDR_) NTSTATUS Status; FILE_STANDARD_INFORMATION FileStandard; IO_STATUS_BLOCK IoStatusBlock; #endif ULONG HiveSize = 0; /* * Query the file size of the physical hive * file. We need that information in order * to ensure how big the hive actually is. */ #if !defined(CMLIB_HOST) && !defined(_BLDR_) Status = ZwQueryInformationFile(((PCMHIVE)Hive)->FileHandles[HFILE_TYPE_PRIMARY], &IoStatusBlock, &FileStandard, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(Status)) { DPRINT1("ZwQueryInformationFile returned 0x%lx\n", Status); return HiveSize; } /* Now compute the hive size */ HiveSize = FileStandard.EndOfFile.u.LowPart - HBLOCK_SIZE; #endif return HiveSize; } /** * @brief * Recovers the base block header by obtaining * it from a log file associated with the hive. * * @param[in] Hive * A pointer to a hive descriptor associated * with the log file where the hive header is * to be read from. * * @param[in] TimeStamp * A pointer to a time-stamp used to check * if the provided time matches with that * of the hive. * * @param[in,out] BaseBlock * A pointer returned by the caller that contains * the base block header that was read from the log. * This base block could be also made manually by hand. * See Remarks for further information. * * @return * Returns HiveSuccess if the header was obtained * normally from the log. NoMemory is returned if * the base block header could not be allocated. * Fail is returned if self-healing mode is disabled * and the log couldn't be read or a write attempt * to the primary hive has failed. SelfHeal is returned * to indicate that self-heal mode goes further. * * @remarks * When SelfHeal is returned this indicates that * even the log we have gotten at hand is corrupt * but since we do have at least a log our only hope * is to reconstruct the pieces of the base header * by hand. */ RESULT CMAPI HvpRecoverHeaderFromLog( _In_ PHHIVE Hive, _In_ PLARGE_INTEGER TimeStamp, _Inout_ PHBASE_BLOCK *BaseBlock) { BOOLEAN Success; PHBASE_BLOCK LogHeader; ULONG FileOffset; ULONG HiveSize; BOOLEAN HeaderResuscitated; /* * The cluster must not be greater than what the * base block can permit. */ ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); /* Assume we haven't resuscitated the header */ HeaderResuscitated = FALSE; /* Allocate an aligned buffer for the log header */ LogHeader = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM); if (!LogHeader) { DPRINT1("Failed to allocate memory for the log header\n"); return NoMemory; } /* Zero out our header buffer */ RtlZeroMemory(LogHeader, HSECTOR_SIZE); /* Get the base header from the log */ FileOffset = 0; Success = Hive->FileRead(Hive, HFILE_TYPE_LOG, &FileOffset, LogHeader, Hive->Cluster * HSECTOR_SIZE); if (!Success || !HvpVerifyHiveHeader(LogHeader, HFILE_TYPE_LOG) || TimeStamp->HighPart != LogHeader->TimeStamp.HighPart || TimeStamp->LowPart != LogHeader->TimeStamp.LowPart) { /* * We failed to read the base block header from * the log, or the header itself or timestamp is * invalid. Check if self healing is enabled. */ if (!CmIsSelfHealEnabled(FALSE)) { DPRINT1("The log couldn't be read and self-healing mode is disabled\n"); Hive->Free(LogHeader, Hive->BaseBlockAlloc); return Fail; } /* * Determine the size of this hive so that * we can estabilish the length of the base * block we are trying to resuscitate. */ HiveSize = HvpQueryHiveSize(Hive); if (HiveSize == 0) { DPRINT1("Failed to query the hive size\n"); Hive->Free(LogHeader, Hive->BaseBlockAlloc); return Fail; } /* * We can still resuscitate the base header if we * could not grab one from the log by reconstructing * the header internals by hand (this assumes the * root cell is not NIL nor damaged). CmCheckRegistry * does the ultimate judgement whether the root cell * is fatally kaput or not after the hive has been * initialized and loaded. * * For more information about base block header * resuscitation, see https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md#notes-4. */ LogHeader->Signature = HV_HBLOCK_SIGNATURE; LogHeader->Sequence1 = 1; LogHeader->Sequence2 = 1; LogHeader->Cluster = 1; LogHeader->Length = HiveSize; LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader); /* * Acknowledge that we have resuscitated * the header. */ HeaderResuscitated = TRUE; DPRINT1("Header has been resuscitated, triggering self-heal mode\n"); } /* * Tag this log header as a primary hive before * writing it to the hive. */ LogHeader->Type = HFILE_TYPE_PRIMARY; /* * If we have not made attempts of recovering * the header due to log corruption then we * have to compute the checksum. This is * already done when the header has been resuscitated * so don't try to do it twice. */ if (!HeaderResuscitated) { LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader); } /* Write the header back to hive now */ Success = Hive->FileWrite(Hive, HFILE_TYPE_PRIMARY, &FileOffset, LogHeader, Hive->Cluster * HSECTOR_SIZE); if (!Success) { DPRINT1("Couldn't write the base header to primary hive\n"); Hive->Free(LogHeader, Hive->BaseBlockAlloc); return Fail; } *BaseBlock = LogHeader; return HeaderResuscitated ? SelfHeal : HiveSuccess; } /** * @brief * Recovers the registry data by obtaining it * from a log that is associated with the hive. * * @param[in] Hive * A pointer to a hive descriptor associated * with the log file where the hive data is to * be read from. * * @param[in] BaseBlock * A pointer to a base block header. * * @return * Returns HiveSuccess if the data was obtained * normally from the log. Fail is returned if * self-healing is disabled and we couldn't be * able to read the data from the log or the * dirty vector signature is garbage or we * failed to write the data block to the primary * hive. SelfHeal is returned to indicate that * the log is corrupt and the system will continue * to be recovered at the expense of data loss. */ RESULT CMAPI HvpRecoverDataFromLog( _In_ PHHIVE Hive, _In_ PHBASE_BLOCK BaseBlock) { BOOLEAN Success; ULONG FileOffset; ULONG BlockIndex; ULONG LogIndex; ULONG StorageLength; UCHAR DirtyVector[HSECTOR_SIZE]; UCHAR Buffer[HBLOCK_SIZE]; /* Read the dirty data from the log */ FileOffset = HV_LOG_HEADER_SIZE; Success = Hive->FileRead(Hive, HFILE_TYPE_LOG, &FileOffset, DirtyVector, HSECTOR_SIZE); if (!Success) { if (!CmIsSelfHealEnabled(FALSE)) { DPRINT1("The log couldn't be read and self-healing mode is disabled\n"); return Fail; } /* * There's nothing we can do on a situation * where dirty data could not be read from * the log. It does not make much sense to * behead the system on such scenario so * trigger a self-heal and go on. The worst * thing that can happen? Data loss, that's it. */ DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n"); return SelfHeal; } /* Check the dirty vector */ if (*((PULONG)DirtyVector) != HV_LOG_DIRTY_SIGNATURE) { if (!CmIsSelfHealEnabled(FALSE)) { DPRINT1("The log's dirty vector signature is not valid\n"); return Fail; } /* * Trigger a self-heal like above. If the * vector signature is garbage then logically * whatever comes after the signature is also * garbage. */ DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n"); return SelfHeal; } /* Now read each data individually and write it back to hive */ LogIndex = 0; StorageLength = BaseBlock->Length / HBLOCK_SIZE; for (BlockIndex = 0; BlockIndex < StorageLength; BlockIndex++) { /* Skip this block if it's not dirty and go to the next one */ if (DirtyVector[BlockIndex + sizeof(HV_LOG_DIRTY_SIGNATURE)] != HV_LOG_DIRTY_BLOCK) { continue; } FileOffset = HSECTOR_SIZE + HSECTOR_SIZE + LogIndex * HBLOCK_SIZE; Success = Hive->FileRead(Hive, HFILE_TYPE_LOG, &FileOffset, Buffer, HBLOCK_SIZE); if (!Success) { DPRINT1("Failed to read the dirty block (index %lu)\n", BlockIndex); return Fail; } FileOffset = HBLOCK_SIZE + BlockIndex * HBLOCK_SIZE; Success = Hive->FileWrite(Hive, HFILE_TYPE_PRIMARY, &FileOffset, Buffer, HBLOCK_SIZE); if (!Success) { DPRINT1("Failed to write dirty block to hive (index %lu)\n", BlockIndex); return Fail; } /* Increment the index in log as we continue further */ LogIndex++; } return HiveSuccess; } #endif /** * @brief * Loads a registry hive from a physical hive file * within the physical backing storage. Base block * and registry data are read from the said physical * hive file. This function can perform registry recovery * if hive loading could not be done normally. * * @param[in] Hive * A pointer to a hive descriptor where the said hive * is to be loaded from the physical hive file. * * @param[in] FileName * A pointer to a NULL-terminated Unicode string structure * containing the hive file name to be copied from. * * @return * STATUS_SUCCESS is returned if the hive has been loaded * successfully. STATUS_INSUFFICIENT_RESOURCES is returned * if there's not enough memory resources to satisfy registry * operations and/or requests. STATUS_NOT_REGISTRY_FILE is returned * if the hive is not actually a hive file. STATUS_REGISTRY_CORRUPT * is returned if the hive has subdued previous damage and * the hive could not be recovered because there's no * log present or self healing is disabled. STATUS_REGISTRY_RECOVERED * is returned if the hive has been recovered. An eventual flush * of the registry is needed after the hive's been fully loaded. */ NTSTATUS CMAPI HvLoadHive( _In_ PHHIVE Hive, _In_opt_ PCUNICODE_STRING FileName) { NTSTATUS Status; BOOLEAN Success; PHBASE_BLOCK BaseBlock = NULL; /* FIXME: See the comment above (near HvpQueryHiveSize) */ #if defined(_M_AMD64) ULONG Result; #else ULONG Result, Result2; #endif LARGE_INTEGER TimeStamp; ULONG Offset = 0; PVOID HiveData; ULONG FileSize; BOOLEAN HiveSelfHeal = FALSE; /* Get the hive header */ Result = HvpGetHiveHeader(Hive, &BaseBlock, &TimeStamp); switch (Result) { /* Out of memory */ case NoMemory: { /* Fail */ DPRINT1("There's no enough memory to get the header\n"); return STATUS_INSUFFICIENT_RESOURCES; } /* Not a hive */ case NotHive: { /* Fail */ DPRINT1("The hive is not an actual registry hive file\n"); return STATUS_NOT_REGISTRY_FILE; } /* Hive data needs a repair */ case RecoverData: { /* * FIXME: We must be handling this status * case if the header isn't corrupt but * the counter sequences do not match but * due to a hack in HvLoadHive we have * to do both a header + data recovery. * RecoverHeader also implies RecoverData * anyway. When HvLoadHive gets rid of * that hack, data recovery must be done * after we read the hive block by block. */ break; } /* Hive header needs a repair */ case RecoverHeader: /* FIXME: See the comment above (near HvpQueryHiveSize) */ #if defined(_M_AMD64) { return STATUS_REGISTRY_CORRUPT; } #else { /* Check if this hive has a log at hand to begin with */ #if (NTDDI_VERSION < NTDDI_VISTA) if (!Hive->Log) { DPRINT1("The hive has no log for header recovery\n"); return STATUS_REGISTRY_CORRUPT; } #endif /* The header needs to be recovered so do it */ DPRINT1("Attempting to heal the header...\n"); Result2 = HvpRecoverHeaderFromLog(Hive, &TimeStamp, &BaseBlock); if (Result2 == NoMemory) { DPRINT1("There's no enough memory to recover header from log\n"); return STATUS_INSUFFICIENT_RESOURCES; } /* Did we fail? */ if (Result2 == Fail) { DPRINT1("Failed to recover the hive header\n"); return STATUS_REGISTRY_CORRUPT; } /* Did we trigger the self-heal mode? */ if (Result2 == SelfHeal) { HiveSelfHeal = TRUE; } /* Now recover the data */ Result2 = HvpRecoverDataFromLog(Hive, BaseBlock); if (Result2 == Fail) { DPRINT1("Failed to recover the hive data\n"); return STATUS_REGISTRY_CORRUPT; } /* Tag the boot as self heal if we haven't done it before */ if ((Result2 == SelfHeal) && (!HiveSelfHeal)) { HiveSelfHeal = TRUE; } break; } #endif } /* Set the boot type */ BaseBlock->BootType = HiveSelfHeal ? HBOOT_TYPE_SELF_HEAL : HBOOT_TYPE_REGULAR; /* Setup hive data */ Hive->BaseBlock = BaseBlock; Hive->Version = BaseBlock->Minor; /* Allocate a buffer large enough to hold the hive */ FileSize = HBLOCK_SIZE + BaseBlock->Length; // == sizeof(HBASE_BLOCK) + BaseBlock->Length; HiveData = Hive->Allocate(FileSize, TRUE, TAG_CM); if (!HiveData) { Hive->Free(BaseBlock, Hive->BaseBlockAlloc); DPRINT1("There's no enough memory to allocate hive data\n"); return STATUS_INSUFFICIENT_RESOURCES; } /* HACK (see explanation below): Now read the whole hive */ Success = Hive->FileRead(Hive, HFILE_TYPE_PRIMARY, &Offset, HiveData, FileSize); if (!Success) { DPRINT1("Failed to read the whole hive\n"); Hive->Free(HiveData, FileSize); Hive->Free(BaseBlock, Hive->BaseBlockAlloc); return STATUS_NOT_REGISTRY_FILE; } /* * HACK (FIXME): Free our base block... it's useless in * this implementation. * * And it's useless because while the idea of reading the * hive from physical file is correct, the implementation * is hacky and incorrect. Instead of reading the whole hive, * we should be instead reading the hive block by block, * deconstruct the block buffer and enlist the bins and * prepare the storage for the hive. What we currently do * is we try to initialize the hive storage and bins enlistment * by calling HvpInitializeMemoryHive below. This mixes * HINIT_FILE and HINIT_MEMORY together which is disgusting * because HINIT_FILE implementation shouldn't be calling * HvpInitializeMemoryHive. */ Hive->Free(BaseBlock, Hive->BaseBlockAlloc); Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to initialize hive from memory\n"); Hive->Free(HiveData, FileSize); return Status; } /* * If we have done some sort of recovery against * the hive we were going to load it from file, * tell the caller we did recover it. The caller * is responsible to flush the data later on. */ return (Result == RecoverHeader) ? STATUS_REGISTRY_RECOVERED : STATUS_SUCCESS; } /** * @brief * Initializes a registry hive. It allocates a hive * descriptor and sets up the hive type depending * on the type chosen by the caller. * * @param[in,out] RegistryHive * A pointer to a hive descriptor to be initialized. * * @param[in] OperationType * The operation type to choose for hive initialization. * For further information about this, see Remarks. * * @param[in] HiveFlags * A hive flag. Such flag is used to determine what kind * of action must be taken into the hive or what aspects * must be taken into account for such hive. For further * information, see Remarks. * * @param[in] FileType * Hive file type. For the newly initialized hive, you can * choose from three different types for the hive: * * HFILE_TYPE_PRIMARY - Initializes a hive as primary hive * of the system. * * HFILE_TYPE_LOG - The newly created hive is a hive log. * Logs don't exist per se but they're accompanied with their * associated primary hives. The Log field member of the hive * descriptor is set to TRUE. * * HFILE_TYPE_EXTERNAL - The newly created hive is a portable * hive, that can be used and copied for different machines, * unlike primary hives. * * HFILE_TYPE_ALTERNATE - The newly created hive is an alternate hive. * Technically speaking it is the same as a primary hive (the representation * of on-disk image of the registry header is HFILE_TYPE_PRIMARY), with * the purpose is to serve as a backup hive. The Alternate field of the * hive descriptor is set to TRUE. Only the SYSTEM hive has a backup * alternate hive. * * @param[in] HiveData * An arbitrary pointer that points to the hive data. Usually this * data is in form of a hive base block given by the caller of this * function. * * @param[in] Allocate * A pointer to a ALLOCATE_ROUTINE function that describes * the main allocation routine for this hive. This parameter * can be NULL. * * @param[in] Free * A pointer to a FREE_ROUTINE function that describes the * the main memory freeing routine for this hive. This parameter * can be NULL. * * @param[in] FileSetSize * A pointer to a FILE_SET_SIZE_ROUTINE function that describes * the file set size routine for this hive. This parameter * can be NULL. * * @param[in] FileWrite * A pointer to a FILE_WRITE_ROUTINE function that describes * the file writing routine for this hive. This parameter * can be NULL. * * @param[in] FileRead * A pointer to a FILE_READ_ROUTINE function that describes * the file reading routine for this hive. This parameter * can be NULL. * * @param[in] FileFlush * A pointer to a FILE_FLUSH_ROUTINE function that describes * the file flushing routine for this hive. This parameter * can be NULL. * * @param[in] Cluster * The registry hive cluster to be set. Usually this value * is set to 1. * * @param[in] FileName * A to a NULL-terminated Unicode string structure containing * the hive file name. This parameter can be NULL. * * @return * Returns STATUS_SUCCESS if the function has successfully * initialized the hive. STATUS_REGISTRY_RECOVERED is returned * if the hive has subdued previous damage and it's been recovered. * This function will perform a hive writing and flushing with * healthy and recovered data in that case. STATUS_REGISTRY_IO_FAILED * is returned if registry hive writing/flushing of recovered data * has failed. STATUS_INVALID_PARAMETER is returned if an invalid * operation type pointed by OperationType parameter has been * submitted. A failure NTSTATUS code is returned otherwise. * * @remarks * OperationType parameter influences how should the hive be * initialized. These are the following supported operation * types: * * HINIT_CREATE -- Creates a new fresh hive. * * HINIT_MEMORY -- Initializes a registry hive that already exists * from memory. The hive data is copied from the * loaded hive in memory and used for read/write * access. * * HINIT_FLAT -- Initializes a flat registry hive, with data that can * only be read and not written into. Cells are always * allocated on a flat hive. * * HINIT_FILE -- Initializes a hive from a hive file from the physical * backing storage of the system. In this situation the * function will perform self-healing and resuscitation * procedures if data read from the physical hive file * is corrupt. * * HINIT_MEMORY_INPLACE -- This operation type is similar to HINIT_FLAT, * with the difference is that the hive is initialized * with hive data from memory. The hive can only be read * and not written into. * * HINIT_MAPFILE -- Initializes a hive from a hive file from the physical * backing storage of the system. Unlike HINIT_FILE, the * initialized hive is not backed to paged pool in memory * but rather through mapping views. * * Alongside the operation type, the hive flags also influence the aspect * of the newly initialized hive. These are the following supported hive * flags: * * HIVE_VOLATILE -- Tells the function that this hive will be volatile, that * is, the data stored inside the hive space resides only * in volatile memory of the system, aka the RAM, and the * data will be erased upon shutdown of the system. * * HIVE_NOLAZYFLUSH -- Tells the function that no lazy flushing must be * done to this hive. */ NTSTATUS CMAPI HvInitialize( _Inout_ PHHIVE RegistryHive, _In_ ULONG OperationType, _In_ ULONG HiveFlags, _In_ ULONG FileType, _In_opt_ PVOID HiveData, _In_opt_ PALLOCATE_ROUTINE Allocate, _In_opt_ PFREE_ROUTINE Free, _In_opt_ PFILE_SET_SIZE_ROUTINE FileSetSize, _In_opt_ PFILE_WRITE_ROUTINE FileWrite, _In_opt_ PFILE_READ_ROUTINE FileRead, _In_opt_ PFILE_FLUSH_ROUTINE FileFlush, _In_ ULONG Cluster, _In_opt_ PCUNICODE_STRING FileName) { NTSTATUS Status; PHHIVE Hive = RegistryHive; /* * Create a new hive structure that will hold all the maintenance data. */ RtlZeroMemory(Hive, sizeof(HHIVE)); Hive->Signature = HV_HHIVE_SIGNATURE; Hive->Allocate = Allocate; Hive->Free = Free; Hive->FileSetSize = FileSetSize; Hive->FileWrite = FileWrite; Hive->FileRead = FileRead; Hive->FileFlush = FileFlush; Hive->RefreshCount = 0; Hive->StorageTypeCount = HTYPE_COUNT; Hive->Cluster = Cluster; Hive->BaseBlockAlloc = sizeof(HBASE_BLOCK); // == HBLOCK_SIZE Hive->Version = HSYS_MINOR; #if (NTDDI_VERSION < NTDDI_VISTA) Hive->Log = (FileType == HFILE_TYPE_LOG); Hive->Alternate = (FileType == HFILE_TYPE_ALTERNATE); #endif Hive->HiveFlags = HiveFlags & ~HIVE_NOLAZYFLUSH; // TODO: The CellRoutines point to different callbacks // depending on the OperationType. Hive->GetCellRoutine = HvpGetCellData; Hive->ReleaseCellRoutine = NULL; switch (OperationType) { case HINIT_CREATE: { /* Create a new fresh hive */ Status = HvpCreateHive(Hive, FileName); break; } case HINIT_MEMORY: { /* Initialize a hive from memory */ Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); break; } case HINIT_FLAT: { /* Initialize a flat read-only hive */ Status = HvpInitializeFlatHive(Hive, HiveData); break; } case HINIT_FILE: { /* Initialize a hive by loading it from physical file in backing storage */ Status = HvLoadHive(Hive, FileName); if ((Status != STATUS_SUCCESS) && (Status != STATUS_REGISTRY_RECOVERED)) { /* Unrecoverable failure */ DPRINT1("Registry hive couldn't be initialized, it's corrupt (hive 0x%p)\n", Hive); return Status; } /* FIXME: See the comment above (near HvpQueryHiveSize) */ #if !defined(_M_AMD64) /* * Check if we have recovered this hive. We are responsible to * flush the primary hive back to backing storage afterwards. */ if (Status == STATUS_REGISTRY_RECOVERED) { if (!HvSyncHiveFromRecover(Hive)) { DPRINT1("Fail to write healthy data back to hive\n"); return STATUS_REGISTRY_IO_FAILED; } /* * We are saved from hell, now clear out the * dirty bits and dirty count. * * FIXME: We must as well clear out the log * and reset its size to 0 but we are lacking * in code that deals with log growing/shrinking * management. When the time comes to implement * this stuff we must set the LogSize and file size * to 0 here. */ RtlClearAllBits(&Hive->DirtyVector); Hive->DirtyCount = 0; /* * Masquerade the status code as success. * STATUS_REGISTRY_RECOVERED is not a failure * code but not STATUS_SUCCESS either so the caller * thinks we failed at our job. */ Status = STATUS_SUCCESS; } #endif break; } case HINIT_MEMORY_INPLACE: { // Status = HvpInitializeMemoryInplaceHive(Hive, HiveData); // break; DPRINT1("HINIT_MEMORY_INPLACE is UNIMPLEMENTED\n"); return STATUS_NOT_IMPLEMENTED; } case HINIT_MAPFILE: { DPRINT1("HINIT_MAPFILE is UNIMPLEMENTED\n"); return STATUS_NOT_IMPLEMENTED; } default: { DPRINT1("Invalid operation type (OperationType = %lu)\n", OperationType); return STATUS_INVALID_PARAMETER; } } return Status; } /** * @brief * Frees all the bins within the storage, the dirty vector * and the base block associated with the given registry * hive descriptor. * * @param[in] RegistryHive * A pointer to a hive descriptor where all of its data * is to be freed. */ VOID CMAPI HvFree( _In_ PHHIVE RegistryHive) { if (!RegistryHive->ReadOnly) { /* Release hive bitmap */ if (RegistryHive->DirtyVector.Buffer) { RegistryHive->Free(RegistryHive->DirtyVector.Buffer, 0); } HvpFreeHiveBins(RegistryHive); /* Free the BaseBlock */ if (RegistryHive->BaseBlock) { RegistryHive->Free(RegistryHive->BaseBlock, RegistryHive->BaseBlockAlloc); RegistryHive->BaseBlock = NULL; } } } /* EOF */