https://git.reactos.org/?p=reactos.git;a=commitdiff;h=7983b65e10ec032f0a523…
commit 7983b65e10ec032f0a52387da9cb3280dc2b6d3a
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 21:36:02 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:28 2023 +0100
[FREELDR] Implement SYSTEM hive recovery at bootloader level & use CmCheckRegistry for registry validation
Validate the SYSTEM hive with CmCheckRegistry and purge volatile data with the same function when initializing a hive descriptor for SYSTEM.
Also implement SYSTEM recovery code that takes use of SYSTEM log in case something is fishy with the hive. If hive repair doesn't have fully recovered the SYSTEM hive, FreeLdr will load the alternate variant of the SYSTEM hive, aka SYSTEM.ALT.
If FreeLdr repairs the hive with a LOG, it will mark it with HBOOT_BOOT_RECOVERED_BY_HIVE_LOG on BootRecover field of the header. All the recovered data that is present as dirty in memory will have to be flushed by the kernel once it is in charge of the system.
Otherwise if the system boot occurred by loading SYSTEM.ALT instead, FreeLdr will mark HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE, the kernel will start recovering the main hive as soon as it does any I/O activity into it.
---
boot/freeldr/freeldr/ntldr/registry.c | 413 +++++++++++++++++++++++++++++++-
boot/freeldr/freeldr/ntldr/registry.h | 4 +-
boot/freeldr/freeldr/ntldr/wlregistry.c | 92 ++++++-
3 files changed, 486 insertions(+), 23 deletions(-)
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 <timo.kreuzer(a)reactos.org>
+ * 2022 George Bișoc <george.bisoc(a)reactos.org>
*
* 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))
{
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=cc63d8f4a2c3e4e22dd3f…
commit cc63d8f4a2c3e4e22dd3f4c706e2373978914b68
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 20:21:29 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:27 2023 +0100
[SDK][CMLIB] Implement log transaction writes & Resuscitation
=== DOCUMENTATION REMARKS ===
This implements (also enables some parts of code been decayed for years) the transacted writing of the registry. Transacted writing (or writing into registry in a transactional way) is an operation that ensures the successfulness can be achieved by monitoring two main points.
In CMLIB, such points are what we internally call them the primary and secondary sequences. A sequence is a numeric field that is incremented each time a writing operation (namely done with the FileWrite function and such) has successfully completed.
The primary sequence is incremented to suggest that the initial work of syncing the registry is in progress. During this phase, the base block header is written into the primary hive file and registry data is being written to said file in form of blocks. Afterwards the seconady sequence
is increment to report completion of the transactional writing of the registry. This operation occurs in HvpWriteHive function (invoked by HvSyncHive for syncing). If the transactional writing fails or if the lazy flushing of the registry fails, LOG files come into play.
Like HvpWriteHive, LOGs are updated by the HvpWriteLog which writes dirty data (base block header included) to the LOG themselves. These files serve for recovery and emergency purposes in case the primary machine hive has been damaged due to previous forced interruption of writing stuff into
the registry hive. With specific recovery algorithms, the data that's been gathered from a LOG will be applied to the primary hive, salvaging it. But if a LOG file is corrupt as well, then the system will perform resuscitation techniques by reconstructing the base block header to reasonable values,
reset the registry signature and whatnot.
This work is an inspiration from PR #3932 by mrmks04 (aka Max Korostil). I have continued his work by doing some more tweaks and whatnot. In addition to that, the whole transaction writing code is documented.
=== IMPORTANT NOTES ===
HvpWriteLog -- Currently this function lacks the ability to grow the log file size since we pretty much lack the necessary code that deals with hive shrinking and log shrinking/growing as well. This part is not super critical for us so this shall be left as a TODO for future.
HvLoadHive -- Currently there's a hack that prevents us from refactoring this function in a proper way. That is, we should not be reading the whole and prepare the hive storage using HvpInitializeMemoryHive which is strictly used for HINIT_MEMORY but rather we must read the hive file block by block
and deconstruct the read buffer from the file so that we can get the bins that we read from the file. With the hive bins we got the hive storage will be prepared based on such bins. If one of the bins is corrupt, self healing is applied in such scenario.
For this matter, if in any case the hive we'll be reading is corrupt we could potentially read corrupt data and lead the system into failure. So we have to perform header and data recovery as well before reading the whole hive.
---
sdk/lib/cmlib/cmlib.h | 10 +
sdk/lib/cmlib/hiveinit.c | 1138 ++++++++++++++++++++++++++++++++++++++++------
sdk/lib/cmlib/hivewrt.c | 526 +++++++++++++++++----
3 files changed, 1432 insertions(+), 242 deletions(-)
diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h
index 77911739ab6..8a7c13c6f15 100644
--- a/sdk/lib/cmlib/cmlib.h
+++ b/sdk/lib/cmlib/cmlib.h
@@ -509,6 +509,11 @@ BOOLEAN CMAPI
HvWriteHive(
PHHIVE RegistryHive);
+BOOLEAN
+CMAPI
+HvSyncHiveFromRecover(
+ _In_ PHHIVE RegistryHive);
+
BOOLEAN
CMAPI
HvTrackCellRef(
@@ -546,6 +551,11 @@ ULONG CMAPI
HvpHiveHeaderChecksum(
PHBASE_BLOCK HiveHeader);
+BOOLEAN CMAPI
+HvpVerifyHiveHeader(
+ _In_ PHBASE_BLOCK BaseBlock,
+ _In_ ULONG FileType);
+
//
// Registry Self-Heal Routines
//
diff --git a/sdk/lib/cmlib/hiveinit.c b/sdk/lib/cmlib/hiveinit.c
index 3eee62d149e..3ec381695ed 100644
--- a/sdk/lib/cmlib/hiveinit.c
+++ b/sdk/lib/cmlib/hiveinit.c
@@ -1,27 +1,59 @@
/*
- * PROJECT: Registry manipulation library
- * LICENSE: GPL - See COPYING in the top level directory
- * COPYRIGHT: Copyright 2005 Filip Navara <navaraf(a)reactos.org>
- * Copyright 2001 - 2005 Eric Kohl
+ * 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 <navaraf(a)reactos.org>
+ * Copyright 2021 Max Korostil
+ * Copyright 2022 George Bișoc <george.bisoc(a)reactos.org>
*/
#include "cmlib.h"
#define NDEBUG
#include <debug.h>
+/* ENUMERATIONS *************************************************************/
+
+typedef enum _RESULT
+{
+ NotHive,
+ Fail,
+ NoMemory,
+ HiveSuccess,
+ RecoverHeader,
+ RecoverData,
+ SelfHeal
+} RESULT;
+
+/* PRIVATE FUNCTIONS ********************************************************/
+
/**
- * @name HvpVerifyHiveHeader
+ * @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.
*
- * Internal function to verify that a hive header has valid format.
+ * @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
+BOOLEAN
+CMAPI
HvpVerifyHiveHeader(
- IN PHBASE_BLOCK BaseBlock)
+ _In_ PHBASE_BLOCK BaseBlock,
+ _In_ ULONG FileType)
{
if (BaseBlock->Signature != HV_HBLOCK_SIGNATURE ||
BaseBlock->Major != HSYS_MAJOR ||
BaseBlock->Minor < HSYS_MINOR ||
- BaseBlock->Type != HFILE_TYPE_PRIMARY ||
+ BaseBlock->Type != FileType ||
BaseBlock->Format != HBASE_FORMAT_MEMORY ||
BaseBlock->Cluster != 1 ||
BaseBlock->Sequence1 != BaseBlock->Sequence2 ||
@@ -31,7 +63,7 @@ HvpVerifyHiveHeader(
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, HFILE_TYPE_PRIMARY);
+ 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",
@@ -45,13 +77,18 @@ HvpVerifyHiveHeader(
}
/**
- * @name HvpFreeHiveBins
+ * @brief
+ * Frees all the bins within storage space
+ * associated with a hive descriptor.
*
- * Internal function to free all bin storage associated with a hive descriptor.
+ * @param[in] Hive
+ * A pointer to a hive descriptor where
+ * all the bins are to be freed.
*/
-VOID CMAPI
+VOID
+CMAPI
HvpFreeHiveBins(
- PHHIVE Hive)
+ _In_ PHHIVE Hive)
{
ULONG i;
PHBIN Bin;
@@ -79,15 +116,34 @@ HvpFreeHiveBins(
}
/**
- * @name HvpAllocBaseBlockAligned
+ * @brief
+ * Allocates a cluster-aligned hive base header block.
*
- * Internal helper function to allocate cluster-aligned hive base blocks.
+ * @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
+static
+__inline
+PHBASE_BLOCK
HvpAllocBaseBlockAligned(
- IN PHHIVE Hive,
- IN BOOLEAN Paged,
- IN ULONG Tag)
+ _In_ PHHIVE Hive,
+ _In_ BOOLEAN Paged,
+ _In_ ULONG Tag)
{
PHBASE_BLOCK BaseBlock;
ULONG Alignment;
@@ -114,16 +170,25 @@ HvpAllocBaseBlockAligned(
}
/**
- * @name HvpInitFileName
+ * @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.
*
- * Internal function to initialize the UNICODE NULL-terminated hive file name
- * member of a hive header by copying the last 31 characters of the 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
+static
+VOID
HvpInitFileName(
- IN OUT PHBASE_BLOCK BaseBlock,
- IN PCUNICODE_STRING FileName OPTIONAL)
+ _Inout_ PHBASE_BLOCK BaseBlock,
+ _In_opt_ PCUNICODE_STRING FileName)
{
ULONG_PTR Offset;
SIZE_T Length;
@@ -149,17 +214,30 @@ HvpInitFileName(
}
/**
- * @name HvpCreateHive
+ * @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.
*
- * Internal helper function to initialize a hive descriptor structure
- * for a newly created hive in memory.
+ * @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.
*
- * @see HvInitialize
+ * @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
+NTSTATUS
+CMAPI
HvpCreateHive(
- IN OUT PHHIVE RegistryHive,
- IN PCUNICODE_STRING FileName OPTIONAL)
+ _Inout_ PHHIVE RegistryHive,
+ _In_opt_ PCUNICODE_STRING FileName)
{
PHBASE_BLOCK BaseBlock;
ULONG Index;
@@ -191,7 +269,7 @@ HvpCreateHive(
BaseBlock->CheckSum = 0;
/* Set default boot type */
- BaseBlock->BootType = 0;
+ BaseBlock->BootType = HBOOT_TYPE_REGULAR;
/* Setup hive data */
RegistryHive->BaseBlock = BaseBlock;
@@ -209,19 +287,38 @@ HvpCreateHive(
}
/**
- * @name HvpInitializeMemoryHive
+ * @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.
*
- * Internal helper function to initialize hive descriptor structure for
- * an existing hive stored in memory. The data of the hive is copied
- * and it is prepared for read/write access.
+ * @param[in] ChunkBase
+ * A pointer to a valid base block header containing
+ * registry header data for initialization.
*
- * @see HvInitialize
+ * @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
+NTSTATUS
+CMAPI
HvpInitializeMemoryHive(
- PHHIVE Hive,
- PHBASE_BLOCK ChunkBase,
- IN PCUNICODE_STRING FileName OPTIONAL)
+ _In_ PHHIVE Hive,
+ _In_ PHBASE_BLOCK ChunkBase,
+ _In_opt_ PCUNICODE_STRING FileName)
{
SIZE_T BlockIndex;
PHBIN Bin, NewBin;
@@ -234,7 +331,7 @@ HvpInitializeMemoryHive(
DPRINT("ChunkSize: %zx\n", ChunkSize);
if (ChunkSize < sizeof(HBASE_BLOCK) ||
- !HvpVerifyHiveHeader(ChunkBase))
+ !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));
@@ -332,20 +429,34 @@ HvpInitializeMemoryHive(
}
/**
- * @name HvpInitializeFlatHive
+ * @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.
*
- * Internal helper function to initialize hive descriptor structure for
- * a hive stored in memory. The in-memory data of the hive are directly
- * used and it is read-only accessible.
+ * @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.
*
- * @see HvInitialize
+ * @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
+NTSTATUS
+CMAPI
HvpInitializeFlatHive(
- PHHIVE Hive,
- PHBASE_BLOCK ChunkBase)
+ _In_ PHHIVE Hive,
+ _In_ PHBASE_BLOCK ChunkBase)
{
- if (!HvpVerifyHiveHeader(ChunkBase))
+ if (!HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY))
return STATUS_REGISTRY_CORRUPT;
/* Setup hive data */
@@ -357,53 +468,150 @@ HvpInitializeFlatHive(
Hive->StorageTypeCount = 1;
/* Set default boot type */
- ChunkBase->BootType = 0;
+ ChunkBase->BootType = HBOOT_TYPE_REGULAR;
return STATUS_SUCCESS;
}
-typedef enum _RESULT
-{
- NotHive,
- Fail,
- NoMemory,
- HiveSuccess,
- RecoverHeader,
- RecoverData,
- SelfHeal
-} RESULT;
-
-RESULT CMAPI
-HvpGetHiveHeader(IN PHHIVE Hive,
- IN PHBASE_BLOCK *HiveBaseBlock,
- IN PLARGE_INTEGER TimeStamp)
+/**
+ * @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 Offset = 0;
+ 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) return NoMemory;
+ 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,
- &Offset,
+ &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;
+ }
- /* Couldn't read: assume it's not a hive */
- if (!Result) 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;
+ }
- /* Do validation */
- if (!HvpVerifyHiveHeader(BaseBlock)) return NotHive;
+ /*
+ * 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;
@@ -411,17 +619,391 @@ HvpGetHiveHeader(IN PHHIVE Hive,
return HiveSuccess;
}
-NTSTATUS CMAPI
-HvLoadHive(IN PHHIVE Hive,
- IN PCUNICODE_STRING FileName OPTIONAL)
+/**
+ * @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%20fo….
+ */
+ 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;
+}
+
+/**
+ * @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;
- ULONG Result;
+ ULONG Result, Result2;
LARGE_INTEGER TimeStamp;
ULONG Offset = 0;
PVOID HiveData;
ULONG FileSize;
+ BOOLEAN HiveSelfHeal = FALSE;
/* Get the hive header */
Result = HvpGetHiveHeader(Hive, &BaseBlock, &TimeStamp);
@@ -429,26 +1011,91 @@ HvLoadHive(IN PHHIVE Hive,
{
/* 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;
+ }
- /* Has recovery data */
+ /* 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:
+ {
+ /* 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
- /* Fail */
- return STATUS_REGISTRY_CORRUPT;
+ /* 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;
+ }
}
- /* Set default boot type */
- BaseBlock->BootType = 0;
+ /* Set the boot type */
+ BaseBlock->BootType = HiveSelfHeal ? HBOOT_TYPE_SELF_HEAL : HBOOT_TYPE_REGULAR;
/* Setup hive data */
Hive->BaseBlock = BaseBlock;
@@ -460,79 +1107,214 @@ HvLoadHive(IN PHHIVE Hive,
if (!HiveData)
{
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
+ DPRINT1("There's no enough memory to allocate hive data\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
- /* Now read the whole hive */
- Result = Hive->FileRead(Hive,
- HFILE_TYPE_PRIMARY,
- &Offset,
- HiveData,
- FileSize);
- if (!Result)
+ /* 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;
}
- // This is a HACK!
- /* Free our base block... it's usless in this implementation */
+ /*
+ * 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);
-
- /* Initialize the hive directly from memory */
Status = HvpInitializeMemoryHive(Hive, HiveData, FileName);
if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to initialize hive from memory\n");
Hive->Free(HiveData, FileSize);
+ return Status;
+ }
- 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;
}
/**
- * @name HvInitialize
- *
- * Allocate a new hive descriptor structure and intialize it.
- *
- * @param RegistryHive
- * Output variable to store pointer to the hive descriptor.
- * @param OperationType
- * - HV_OPERATION_CREATE_HIVE
- * Create a new hive for read/write access.
- * - HV_OPERATION_MEMORY
- * Load and copy in-memory hive for read/write access. The
- * pointer to data passed to this routine can be freed after
- * the function is executed.
- * - HV_OPERATION_MEMORY_INPLACE
- * Load an in-memory hive for read-only access. The pointer
- * to data passed to this routine MUSTN'T be freed until
- * HvFree is called.
- * @param ChunkBase
- * Pointer to hive data.
- * @param ChunkSize
- * Size of passed hive data.
+ * @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
- * STATUS_NO_MEMORY - A memory allocation failed.
- * STATUS_REGISTRY_CORRUPT - Registry corruption was detected.
- * STATUS_SUCCESS
+ * 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.
*
- * @see HvFree
+ * HIVE_NOLAZYFLUSH -- Tells the function that no lazy flushing must be
+ * done to this hive.
*/
-NTSTATUS CMAPI
+NTSTATUS
+CMAPI
HvInitialize(
- PHHIVE RegistryHive,
- ULONG OperationType,
- ULONG HiveFlags,
- ULONG FileType,
- PVOID HiveData OPTIONAL,
- PALLOCATE_ROUTINE Allocate,
- PFREE_ROUTINE Free,
- PFILE_SET_SIZE_ROUTINE FileSetSize,
- PFILE_WRITE_ROUTINE FileWrite,
- PFILE_READ_ROUTINE FileRead,
- PFILE_FLUSH_ROUTINE FileFlush,
- ULONG Cluster OPTIONAL,
- PCUNICODE_STRING FileName OPTIONAL)
+ _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;
@@ -570,42 +1352,95 @@ HvInitialize(
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;
}
- /* Check for previous damage */
- ASSERT(Status != STATUS_REGISTRY_RECOVERED);
+ /*
+ * 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;
+ }
+
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:
- /* FIXME: A better return status value is needed */
- Status = STATUS_NOT_IMPLEMENTED;
- ASSERT(FALSE);
+ {
+ DPRINT1("Invalid operation type (OperationType = %lu)\n", OperationType);
+ return STATUS_INVALID_PARAMETER;
+ }
}
if (!NT_SUCCESS(Status)) return Status;
@@ -619,14 +1454,19 @@ HvInitialize(
}
/**
- * @name HvFree
+ * @brief
+ * Frees all the bins within the storage, the dirty vector
+ * and the base block associated with the given registry
+ * hive descriptor.
*
- * Free all stroage and handles associated with hive descriptor.
- * But do not free the hive descriptor itself.
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where all of its data
+ * is to be freed.
*/
-VOID CMAPI
+VOID
+CMAPI
HvFree(
- PHHIVE RegistryHive)
+ _In_ PHHIVE RegistryHive)
{
if (!RegistryHive->ReadOnly)
{
diff --git a/sdk/lib/cmlib/hivewrt.c b/sdk/lib/cmlib/hivewrt.c
index 76bf72b1b45..4fea5fa70e6 100644
--- a/sdk/lib/cmlib/hivewrt.c
+++ b/sdk/lib/cmlib/hivewrt.c
@@ -1,176 +1,350 @@
/*
- * PROJECT: Registry manipulation library
- * LICENSE: GPL - See COPYING in the top level directory
- * COPYRIGHT: Copyright 2005 Filip Navara <navaraf(a)reactos.org>
- * Copyright 2001 - 2005 Eric Kohl
+ * PROJECT: ReactOS Kernel
+ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log Writing
+ * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl
+ * Copyright 2005 Filip Navara <navaraf(a)reactos.org>
+ * Copyright 2021 Max Korostil
+ * Copyright 2022 George Bișoc <george.bisoc(a)reactos.org>
*/
#include "cmlib.h"
#define NDEBUG
#include <debug.h>
-static BOOLEAN CMAPI
+/* DECLARATIONS *************************************************************/
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+BOOLEAN
+NTAPI
+IoSetThreadHardErrorMode(
+ _In_ BOOLEAN HardErrorEnabled);
+#endif
+
+/* GLOBALS *****************************************************************/
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+extern BOOLEAN CmpMiniNTBoot;
+#endif
+
+/* PRIVATE FUNCTIONS ********************************************************/
+
+/**
+ * @brief
+ * Validates the base block header of a primary
+ * hive for consistency.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor to look
+ * for the header block.
+ */
+static
+VOID
+HvpValidateBaseHeader(
+ _In_ PHHIVE RegistryHive)
+{
+ PHBASE_BLOCK BaseBlock;
+
+ /*
+ * Cache the base block and validate it.
+ * Especially...
+ *
+ * 1. It must must have a valid signature.
+ * 2. It must have a valid format.
+ * 3. It must be of an adequate major version,
+ * not anything else.
+ */
+ BaseBlock = RegistryHive->BaseBlock;
+ ASSERT(BaseBlock->Signature == HV_HBLOCK_SIGNATURE);
+ ASSERT(BaseBlock->Format == HBASE_FORMAT_MEMORY);
+ ASSERT(BaseBlock->Major == HSYS_MAJOR);
+}
+
+/**
+ * @unimplemented
+ * @brief
+ * Writes dirty data in a transacted way to a hive
+ * log file during hive syncing operation. Log
+ * files are used by the kernel/bootloader to
+ * perform recovery operations against a
+ * damaged primary hive.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where the log
+ * belongs to and of which we write data into the
+ * said log.
+ *
+ * @return
+ * Returns TRUE if log transaction writing has succeeded,
+ * FALSE otherwise.
+ *
+ * @remarks
+ * The function is not completely implemented, that is,
+ * it lacks the implementation for growing the log file size.
+ * See the FIXME comment below for further details.
+ */
+static
+BOOLEAN
+CMAPI
HvpWriteLog(
- PHHIVE RegistryHive)
+ _In_ PHHIVE RegistryHive)
{
+ BOOLEAN Success;
ULONG FileOffset;
- UINT32 BufferSize;
- UINT32 BitmapSize;
- PUCHAR Buffer;
- PUCHAR Ptr;
ULONG BlockIndex;
ULONG LastIndex;
- PVOID BlockPtr;
- BOOLEAN Success;
- static ULONG PrintCount = 0;
-
- if (PrintCount++ == 0)
- {
- UNIMPLEMENTED;
- }
- return TRUE;
-
+ PVOID Block;
+ UINT32 BitmapSize, BufferSize;
+ PUCHAR HeaderBuffer, Ptr;
+
+ /*
+ * The hive log we are going to write data into
+ * has to be writable and with a sane storage.
+ */
ASSERT(RegistryHive->ReadOnly == FALSE);
ASSERT(RegistryHive->BaseBlock->Length ==
RegistryHive->Storage[Stable].Length * HBLOCK_SIZE);
- DPRINT("HvpWriteLog called\n");
-
+ /* Validate the base header before we go further */
+ HvpValidateBaseHeader(RegistryHive);
+
+ /*
+ * The sequences can diverge in an occurrence of forced
+ * shutdown of the system such as during a power failure,
+ * the hardware crapping itself or during a system crash
+ * when one of the sequences have been modified during
+ * writing into the log or hive. In such cases the hive
+ * needs a repair.
+ */
if (RegistryHive->BaseBlock->Sequence1 !=
RegistryHive->BaseBlock->Sequence2)
{
+ DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n",
+ RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2);
return FALSE;
}
- BitmapSize = RegistryHive->DirtyVector.SizeOfBitMap;
- BufferSize = HV_LOG_HEADER_SIZE + sizeof(ULONG) + BitmapSize;
- BufferSize = ROUND_UP(BufferSize, HBLOCK_SIZE);
-
- DPRINT("Bitmap size %u buffer size: %u\n", BitmapSize, BufferSize);
-
- Buffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM);
- if (Buffer == NULL)
+ /*
+ * FIXME: We must set a new file size for this log
+ * here but ReactOS lacks the necessary code implementation
+ * that manages the growing and shrinking of a hive's log
+ * size. So for now don't set any new size for the log.
+ */
+
+ /*
+ * Now calculate the bitmap and buffer sizes to hold up our
+ * contents in a buffer.
+ */
+ BitmapSize = ROUND_UP(sizeof(ULONG) + RegistryHive->DirtyVector.SizeOfBitMap / 8, HSECTOR_SIZE);
+ BufferSize = HV_LOG_HEADER_SIZE + BitmapSize;
+
+ /* Now allocate the base header block buffer */
+ HeaderBuffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM);
+ if (!HeaderBuffer)
{
+ DPRINT1("Couldn't allocate buffer for base header block\n");
return FALSE;
}
- /* Update first update counter and CheckSum */
+ /* Great, now zero out the buffer */
+ RtlZeroMemory(HeaderBuffer, BufferSize);
+
+ /*
+ * Update the base block of this hive and
+ * increment the primary sequence number
+ * as we are at the half of the work.
+ */
RegistryHive->BaseBlock->Type = HFILE_TYPE_LOG;
RegistryHive->BaseBlock->Sequence1++;
- RegistryHive->BaseBlock->CheckSum =
- HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
+ RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
+
+ /* Copy the base block header */
+ RtlCopyMemory(HeaderBuffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE);
+ Ptr = HeaderBuffer + HV_LOG_HEADER_SIZE;
+
+ /* Copy the dirty vector */
+ *((PULONG)Ptr) = HV_LOG_DIRTY_SIGNATURE;
+ Ptr += sizeof(HV_LOG_DIRTY_SIGNATURE);
+
+ /*
+ * FIXME: In ReactOS a vector contains one bit per block
+ * whereas in Windows each bit within a vector is per
+ * sector. Furthermore, the dirty blocks within a respective
+ * hive has to be marked as such in an appropriate function
+ * for this purpose (probably HvMarkDirty or similar).
+ *
+ * For the moment being, mark the relevant dirty blocks
+ * here.
+ */
+ BlockIndex = 0;
+ while (BlockIndex < RegistryHive->Storage[Stable].Length)
+ {
+ /* Check if the block is clean or we're past the last block */
+ LastIndex = BlockIndex;
+ BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
+ if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex)
+ {
+ break;
+ }
- /* Copy hive header */
- RtlCopyMemory(Buffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE);
- Ptr = Buffer + HV_LOG_HEADER_SIZE;
- RtlCopyMemory(Ptr, "DIRT", 4);
- Ptr += 4;
- RtlCopyMemory(Ptr, RegistryHive->DirtyVector.Buffer, BitmapSize);
+ /*
+ * Mark this block as dirty and go to the next one.
+ *
+ * FIXME: We should rather use RtlSetBits but that crashes
+ * the system with a bugckeck. So for now mark blocks manually
+ * by hand.
+ */
+ Ptr[BlockIndex] = HV_LOG_DIRTY_BLOCK;
+ BlockIndex++;
+ }
- /* Write hive block and block bitmap */
+ /* Now write the hive header and block bitmap into the log */
FileOffset = 0;
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
- &FileOffset, Buffer, BufferSize);
- RegistryHive->Free(Buffer, 0);
-
+ &FileOffset, HeaderBuffer, BufferSize);
+ RegistryHive->Free(HeaderBuffer, 0);
if (!Success)
{
+ DPRINT1("Failed to write the hive header block to log (primary sequence)\n");
return FALSE;
}
- /* Write dirty blocks */
+ /* Now write the actual dirty data to log */
FileOffset = BufferSize;
BlockIndex = 0;
while (BlockIndex < RegistryHive->Storage[Stable].Length)
{
+ /* Check if the block is clean or we're past the last block */
LastIndex = BlockIndex;
BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
- if (BlockIndex == ~0U || BlockIndex < LastIndex)
+ if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex)
{
break;
}
- BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
+ /* Get the block */
+ Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
- /* Write hive block */
+ /* Write it to log */
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
- &FileOffset, BlockPtr, HBLOCK_SIZE);
+ &FileOffset, Block, HBLOCK_SIZE);
if (!Success)
{
+ DPRINT1("Failed to write dirty block to log (block 0x%p, block index 0x%x)\n", Block, BlockIndex);
return FALSE;
}
+ /* Grow up the file offset as we go to the next block */
BlockIndex++;
FileOffset += HBLOCK_SIZE;
}
- Success = RegistryHive->FileSetSize(RegistryHive, HFILE_TYPE_LOG, FileOffset, FileOffset);
- if (!Success)
- {
- DPRINT("FileSetSize failed\n");
- return FALSE;
- }
-
- /* Flush the log file */
+ /*
+ * We wrote the header and body of log with dirty,
+ * data do a flush immediately.
+ */
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0);
if (!Success)
{
- DPRINT("FileFlush failed\n");
+ DPRINT1("Failed to flush the log\n");
+ return FALSE;
}
- /* Update second update counter and CheckSum */
+ /*
+ * OK, we're now at 80% of the work done.
+ * Increment the secondary sequence and flush
+ * the log again. We can have a fully successful
+ * transacted write of a log if the sequences
+ * are synced up properly.
+ */
RegistryHive->BaseBlock->Sequence2++;
- RegistryHive->BaseBlock->CheckSum =
- HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
+ RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
- /* Write hive header again with updated sequence counter. */
+ /* Write new stuff into log first */
FileOffset = 0;
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
&FileOffset, RegistryHive->BaseBlock,
HV_LOG_HEADER_SIZE);
if (!Success)
{
+ DPRINT1("Failed to write the log file (secondary sequence)\n");
return FALSE;
}
- /* Flush the log file */
+ /* Flush it finally */
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0);
if (!Success)
{
- DPRINT("FileFlush failed\n");
+ DPRINT1("Failed to flush the log\n");
+ return FALSE;
}
return TRUE;
}
-static BOOLEAN CMAPI
+/**
+ * @brief
+ * Writes data (dirty or non) to a primary hive during
+ * syncing operation. Hive writing is also performed
+ * during a flush occurrence on request by the system.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where the data is
+ * to be written to that hive.
+ *
+ * @param[in] OnlyDirty
+ * If set to TRUE, the function only looks for dirty
+ * data to be written to the primary hive, otherwise if
+ * it's set to FALSE then the function writes all the data.
+ *
+ * @return
+ * Returns TRUE if writing to hive has succeeded,
+ * FALSE otherwise.
+ */
+static
+BOOLEAN
+CMAPI
HvpWriteHive(
- PHHIVE RegistryHive,
- BOOLEAN OnlyDirty)
+ _In_ PHHIVE RegistryHive,
+ _In_ BOOLEAN OnlyDirty)
{
+ BOOLEAN Success;
ULONG FileOffset;
ULONG BlockIndex;
ULONG LastIndex;
- PVOID BlockPtr;
- BOOLEAN Success;
+ PVOID Block;
ASSERT(RegistryHive->ReadOnly == FALSE);
ASSERT(RegistryHive->BaseBlock->Length ==
RegistryHive->Storage[Stable].Length * HBLOCK_SIZE);
-
- DPRINT("HvpWriteHive called\n");
-
+ ASSERT(RegistryHive->BaseBlock->RootCell != HCELL_NIL);
+
+ /* Validate the base header before we go further */
+ HvpValidateBaseHeader(RegistryHive);
+
+ /*
+ * The sequences can diverge in an occurrence of forced
+ * shutdown of the system such as during a power failure,
+ * the hardware crapping itself or during a system crash
+ * when one of the sequences have been modified during
+ * writing into the log or hive. In such cases the hive
+ * needs a repair.
+ */
if (RegistryHive->BaseBlock->Sequence1 !=
RegistryHive->BaseBlock->Sequence2)
{
+ DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n",
+ RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2);
return FALSE;
}
- /* Update first update counter and CheckSum */
+ /*
+ * Update the primary sequence number and write
+ * the base block to hive.
+ */
RegistryHive->BaseBlock->Type = HFILE_TYPE_PRIMARY;
RegistryHive->BaseBlock->Sequence1++;
- RegistryHive->BaseBlock->CheckSum =
- HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
+ RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
/* Write hive block */
FileOffset = 0;
@@ -179,46 +353,70 @@ HvpWriteHive(
sizeof(HBASE_BLOCK));
if (!Success)
{
+ DPRINT1("Failed to write the base block header to primary hive (primary sequence)\n");
return FALSE;
}
+ /* Write the whole primary hive, block by block */
BlockIndex = 0;
while (BlockIndex < RegistryHive->Storage[Stable].Length)
{
+ /*
+ * If we have to syncrhonize the registry hive we
+ * want to look for dirty blocks to reflect the new
+ * updates done to the hive. Otherwise just write
+ * all the blocks as if we were doing a regular
+ * writing of the hive.
+ */
if (OnlyDirty)
{
+ /* Check if the block is clean or we're past the last block */
LastIndex = BlockIndex;
BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
- if (BlockIndex == ~0U || BlockIndex < LastIndex)
+ if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex)
{
break;
}
}
- BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
+ /* Get the block and offset position */
+ Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
FileOffset = (BlockIndex + 1) * HBLOCK_SIZE;
- /* Write hive block */
+ /* Now write this block to primary hive file */
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
- &FileOffset, BlockPtr, HBLOCK_SIZE);
+ &FileOffset, Block, HBLOCK_SIZE);
if (!Success)
{
+ DPRINT1("Failed to write hive block to primary hive file (block 0x%p, block index 0x%x)\n",
+ Block, BlockIndex);
return FALSE;
}
+ /* Go to the next block */
BlockIndex++;
}
+ /*
+ * We wrote all the hive contents to the file, we
+ * must flush the changes to disk now.
+ */
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
if (!Success)
{
- DPRINT("FileFlush failed\n");
+ DPRINT1("Failed to flush the primary hive\n");
+ return FALSE;
}
- /* Update second update counter and CheckSum */
+ /*
+ * Increment the secondary sequence number and
+ * update the checksum. A successful transaction
+ * writing of hive is both of sequences are the
+ * same indicating the writing operation didn't
+ * fail.
+ */
RegistryHive->BaseBlock->Sequence2++;
- RegistryHive->BaseBlock->CheckSum =
- HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
+ RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
/* Write hive block */
FileOffset = 0;
@@ -227,41 +425,113 @@ HvpWriteHive(
sizeof(HBASE_BLOCK));
if (!Success)
{
+ DPRINT1("Failed to write the base block header to primary hive (secondary sequence)\n");
return FALSE;
}
+ /* Flush the hive immediately */
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
if (!Success)
{
- DPRINT("FileFlush failed\n");
+ DPRINT1("Failed to flush the primary hive\n");
+ return FALSE;
}
return TRUE;
}
-BOOLEAN CMAPI
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+/**
+ * @brief
+ * Synchronizes a registry hive with latest updates
+ * from dirty data present in volatile memory, aka RAM.
+ * It writes both to hive log and corresponding primary
+ * hive. Syncing is done on request by the system during
+ * a flush occurrence.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where syncing is
+ * to be performed.
+ *
+ * @return
+ * Returns TRUE if syncing has succeeded, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
HvSyncHive(
- PHHIVE RegistryHive)
+ _In_ PHHIVE RegistryHive)
{
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ BOOLEAN HardErrors;
+#endif
+
ASSERT(RegistryHive->ReadOnly == FALSE);
+ ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE);
+
+ /*
+ * Check if there's any dirty data in the vector.
+ * A space with clean blocks would be pointless for
+ * a log because we want to write dirty data in and
+ * sync up, not clean data. So just consider our
+ * job as done as there's literally nothing to do.
+ */
+ if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~HV_CLEAN_BLOCK)
+ {
+ DPRINT("The dirty vector has clean data, nothing to do\n");
+ return TRUE;
+ }
- if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~0U)
+ /*
+ * We are either in Live CD or we are sharing hives.
+ * In either of the cases, hives can only be read
+ * so don't do any writing operations on them.
+ */
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ if (CmpMiniNTBoot)
{
+ DPRINT("We are sharing hives or in Live CD mode, abort syncing\n");
return TRUE;
}
+#endif
+
+ /* Avoid any writing operations on volatile hives */
+ if (RegistryHive->HiveFlags & HIVE_VOLATILE)
+ {
+ DPRINT("The hive is volatile (hive 0x%p)\n", RegistryHive);
+ return TRUE;
+ }
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ /* Disable hard errors before syncing the hive */
+ HardErrors = IoSetThreadHardErrorMode(FALSE);
+#endif
+#if !defined(_BLDR_)
/* Update hive header modification time */
KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);
+#endif
- /* Update log file */
- if (!HvpWriteLog(RegistryHive))
+ /* Update the log file of hive if present */
+ if (RegistryHive->Log == TRUE)
{
- return FALSE;
+ if (!HvpWriteLog(RegistryHive))
+ {
+ DPRINT1("Failed to write a log whilst syncing the hive\n");
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ IoSetThreadHardErrorMode(HardErrors);
+#endif
+ return FALSE;
+ }
}
- /* Update hive file */
+ /* Update the primary hive file */
if (!HvpWriteHive(RegistryHive, TRUE))
{
+ DPRINT1("Failed to write the primary hive\n");
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ IoSetThreadHardErrorMode(HardErrors);
+#endif
return FALSE;
}
@@ -269,32 +539,102 @@ HvSyncHive(
RtlClearAllBits(&RegistryHive->DirtyVector);
RegistryHive->DirtyCount = 0;
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ IoSetThreadHardErrorMode(HardErrors);
+#endif
return TRUE;
}
+/**
+ * @unimplemented
+ * @brief
+ * Determines whether a registry hive needs
+ * to be shrinked or not based on its overall
+ * size of the hive space to avoid unnecessary
+ * bloat.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where hive
+ * shrinking is to be determined.
+ *
+ * @return
+ * Returns TRUE if hive shrinking needs to be
+ * done, FALSE otherwise.
+ */
BOOLEAN
CMAPI
-HvHiveWillShrink(IN PHHIVE RegistryHive)
+HvHiveWillShrink(
+ _In_ PHHIVE RegistryHive)
{
/* No shrinking yet */
UNIMPLEMENTED_ONCE;
return FALSE;
}
-BOOLEAN CMAPI
+/**
+ * @brief
+ * Writes data to a registry hive. Unlike
+ * HvSyncHive, this function just writes
+ * the wholy registry data to a primary hive,
+ * ignoring if a certain data block is dirty
+ * or not.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where data
+ * is be written into.
+ *
+ * @return
+ * Returns TRUE if hive writing has succeeded,
+ * FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
HvWriteHive(
- PHHIVE RegistryHive)
+ _In_ PHHIVE RegistryHive)
{
ASSERT(RegistryHive->ReadOnly == FALSE);
+ ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE);
+#if !defined(_BLDR_)
/* Update hive header modification time */
KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);
+#endif
/* Update hive file */
if (!HvpWriteHive(RegistryHive, FALSE))
{
+ DPRINT1("Failed to write the hive\n");
return FALSE;
}
return TRUE;
}
+
+
+/**
+ * @brief
+ * Synchronizes a hive with recovered
+ * data during a healing/resuscitation
+ * operation of the registry.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where data
+ * syncing is to be done.
+ *
+ * @return
+ * Returns TRUE if hive syncing during recovery
+ * succeeded, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+HvSyncHiveFromRecover(
+ _In_ PHHIVE RegistryHive)
+{
+ ASSERT(RegistryHive->ReadOnly == FALSE);
+ ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE);
+
+ /* Call the private API call to do the deed for us */
+ return HvpWriteHive(RegistryHive, TRUE);
+}
+
+/* EOF */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=f33da480af6c50eb48880…
commit f33da480af6c50eb4888057bdcf0e929761f9b96
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 19:41:59 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:27 2023 +0100
[SDK][CMLIB] Implement CmCheckRegistry and validation private helpers
CmCheckRegistry is a function that provides the necessary validation checks for a registry hive. This function usually comes into action when logs have been replayed for example, or when a registry hive internals have changed such as when saving a key, loading a key, etc.
This commit implements the whole Check Registry infrastructure (cmcheck.c) in CMLIB library for ease of usage and wide accessibility across parts of the OS. In addition, two more functions for registry checks are also implemented -- HvValidateHive and HvValidateBin.
Instead of having the CmCheckRegistry implementation in the kernel, it's better to have it in the Configuration Manager library instead (aka CMLIB). The benefits of having it in the library are the following:
- CmCheckRegistry can be used in FreeLdr to fix the SYSTEM hive
- It can be used on-demand in the kernel
- It can be used for offline registry repair tools
- It makes the underlying CmCheckRegistry implementation code debug-able in user mode
CORE-9195
CORE-6762
---
ntoskrnl/config/cmcheck.c | 27 -
ntoskrnl/include/internal/cm.h | 10 -
ntoskrnl/ntos.cmake | 1 -
sdk/lib/cmlib/CMakeLists.txt | 1 +
sdk/lib/cmlib/cmcheck.c | 1718 ++++++++++++++++++++++++++++++++++++++++
sdk/lib/cmlib/cmlib.h | 92 ++-
6 files changed, 1807 insertions(+), 42 deletions(-)
diff --git a/ntoskrnl/config/cmcheck.c b/ntoskrnl/config/cmcheck.c
deleted file mode 100644
index 4172dddc332..00000000000
--- a/ntoskrnl/config/cmcheck.c
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * PROJECT: ReactOS Kernel
- * LICENSE: GPL - See COPYING in the top level directory
- * FILE: ntoskrnl/config/cmcheck.c
- * PURPOSE: Configuration Manager - Hive and Key Validation
- * PROGRAMMERS: Alex Ionescu (alex.ionescu(a)reactos.org)
- */
-
-/* INCLUDES ******************************************************************/
-
-#include "ntoskrnl.h"
-#define NDEBUG
-#include "debug.h"
-
-/* GLOBALS *******************************************************************/
-
-/* FUNCTIONS *****************************************************************/
-
-ULONG
-NTAPI
-CmCheckRegistry(IN PCMHIVE RegistryHive,
- IN ULONG Flags)
-{
- /* FIXME: HACK! */
- DPRINT1("CmCheckRegistry(0x%p, %lu) is UNIMPLEMENTED!\n", RegistryHive, Flags);
- return 0;
-}
diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h
index 5c2f352f753..04869a812bc 100644
--- a/ntoskrnl/include/internal/cm.h
+++ b/ntoskrnl/include/internal/cm.h
@@ -595,16 +595,6 @@ CmpCompareNewValueDataAgainstKCBCache(
IN ULONG DataSize
);
-//
-// Registry Validation Functions
-//
-ULONG
-NTAPI
-CmCheckRegistry(
- IN PCMHIVE Hive,
- IN ULONG Flags
-);
-
//
// Hive List Routines
//
diff --git a/ntoskrnl/ntos.cmake b/ntoskrnl/ntos.cmake
index 47e7c01eda3..d9328ae3d01 100644
--- a/ntoskrnl/ntos.cmake
+++ b/ntoskrnl/ntos.cmake
@@ -48,7 +48,6 @@ list(APPEND SOURCE
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmalloc.c
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmapi.c
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmboot.c
- ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmcheck.c
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmconfig.c
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmcontrl.c
${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmdata.c
diff --git a/sdk/lib/cmlib/CMakeLists.txt b/sdk/lib/cmlib/CMakeLists.txt
index 91ccbe6c3e5..ef8bff3ea66 100644
--- a/sdk/lib/cmlib/CMakeLists.txt
+++ b/sdk/lib/cmlib/CMakeLists.txt
@@ -4,6 +4,7 @@ add_definitions(
-DNASSERT)
list(APPEND SOURCE
+ cmcheck.c
cminit.c
cmheal.c
cmindex.c
diff --git a/sdk/lib/cmlib/cmcheck.c b/sdk/lib/cmlib/cmcheck.c
new file mode 100644
index 00000000000..0c1d82b0555
--- /dev/null
+++ b/sdk/lib/cmlib/cmcheck.c
@@ -0,0 +1,1718 @@
+/*
+ * PROJECT: ReactOS Kernel
+ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Configuration Manager Library - Registry Validation
+ * COPYRIGHT: Copyright 2022 George Bișoc <george.bisoc(a)reactos.org>
+ */
+
+#include "cmlib.h"
+#define NDEBUG
+#include <debug.h>
+
+/* STRUCTURES ****************************************************************/
+
+typedef struct _CMP_REGISTRY_STACK_WORK_STATE
+{
+ ULONG ChildCellIndex;
+ HCELL_INDEX Parent;
+ HCELL_INDEX Current;
+ HCELL_INDEX Sibling;
+} CMP_REGISTRY_STACK_WORK_STATE, *PCMP_REGISTRY_STACK_WORK_STATE;
+
+/* DEFINES ******************************************************************/
+
+#define GET_HHIVE(CmHive) (&((CmHive)->Hive))
+#define GET_HHIVE_ROOT_CELL(Hive) ((Hive)->BaseBlock->RootCell)
+#define GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex) ((PHBIN)Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress)
+#define GET_CELL_BIN(Bin) ((PHCELL)((PUCHAR)Bin + sizeof(HBIN)))
+
+#define IS_CELL_VOLATILE(Cell) (HvGetCellType(Cell) == Volatile)
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+extern PCMHIVE CmiVolatileHive;
+#endif
+
+#define CMP_PRIOR_STACK 1
+#define CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH 512
+
+#define CMP_KEY_SIZE_THRESHOLD 0x45C
+#define CMP_VOLATILE_LIST_UNINTIALIZED 0xBAADF00D
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+/**
+ * @brief
+ * Validates the lexicographical order between a child
+ * and prior sibling of the said child.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which lexicographical
+ * order of keys are to be checked.
+ *
+ * @param[in] Child
+ * A child subkey cell used for lexicographical order
+ * validation checks.
+ *
+ * @param[in] Sibling
+ * A subkey cell which is the prior sibling of the child.
+ * This is used in conjuction with the child to perfrom
+ * lexical order checks.
+ *
+ * @return
+ * Returns TRUE if the order is legal, FALSE otherwise.
+ */
+static
+BOOLEAN
+CmpValidateLexicalOrder(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX Child,
+ _In_ HCELL_INDEX Sibling)
+{
+ LONG Result;
+ UNICODE_STRING ChildString, SiblingString;
+ PCM_KEY_NODE ChildNode, SiblingNode;
+
+ PAGED_CODE();
+
+ /* Obtain the child node */
+ ChildNode = (PCM_KEY_NODE)HvGetCell(Hive, Child);
+ if (!ChildNode)
+ {
+ /* Couldn't get the child node, bail out */
+ DPRINT1("Failed to get the child node\n");
+ return FALSE;
+ }
+
+ /* Obtain the sibling node */
+ SiblingNode = (PCM_KEY_NODE)HvGetCell(Hive, Sibling);
+ if (!SiblingNode)
+ {
+ /* Couldn't get the sibling node, bail out */
+ DPRINT1("Failed to get the sibling node\n");
+ return FALSE;
+ }
+
+ /* CASE 1: Two distinct non-compressed Unicode names */
+ if ((ChildNode->Flags & KEY_COMP_NAME) == 0 &&
+ (SiblingNode->Flags & KEY_COMP_NAME) == 0)
+ {
+ /* Build the sibling string */
+ SiblingString.Buffer = &(SiblingNode->Name[0]);
+ SiblingString.Length = SiblingNode->NameLength;
+ SiblingString.MaximumLength = SiblingNode->NameLength;
+
+ /* Build the child string */
+ ChildString.Buffer = &(ChildNode->Name[0]);
+ ChildString.Length = ChildNode->NameLength;
+ ChildString.MaximumLength = ChildNode->NameLength;
+
+ Result = RtlCompareUnicodeString(&SiblingString, &ChildString, TRUE);
+ if (Result >= 0)
+ {
+ DPRINT1("The sibling node name is greater or equal to that of the child\n");
+ return FALSE;
+ }
+ }
+
+ /* CASE 2: Both compressed Unicode names */
+ if ((ChildNode->Flags & KEY_COMP_NAME) &&
+ (SiblingNode->Flags & KEY_COMP_NAME))
+ {
+ /* FIXME: Checks for two compressed names not implemented yet */
+ DPRINT("Lexicographical order checks for two compressed names is UNIMPLEMENTED, assume the key is healthy...\n");
+ return TRUE;
+ }
+
+ /* CASE 3: The child name is compressed but the sibling is not */
+ if ((ChildNode->Flags & KEY_COMP_NAME) &&
+ (SiblingNode->Flags & KEY_COMP_NAME) == 0)
+ {
+ SiblingString.Buffer = &(SiblingNode->Name[0]);
+ SiblingString.Length = SiblingNode->NameLength;
+ SiblingString.MaximumLength = SiblingNode->NameLength;
+ Result = CmpCompareCompressedName(&SiblingString,
+ ChildNode->Name,
+ ChildNode->NameLength);
+ if (Result >= 0)
+ {
+ DPRINT1("The sibling node name is greater or equal to that of the compressed child\n");
+ return FALSE;
+ }
+ }
+
+ /* CASE 4: The sibling name is compressed but the child is not */
+ if ((SiblingNode->Flags & KEY_COMP_NAME) &&
+ (ChildNode->Flags & KEY_COMP_NAME) == 0)
+ {
+ ChildString.Buffer = &(ChildNode->Name[0]);
+ ChildString.Length = ChildNode->NameLength;
+ ChildString.MaximumLength = ChildNode->NameLength;
+ Result = CmpCompareCompressedName(&ChildString,
+ SiblingNode->Name,
+ SiblingNode->NameLength);
+ if (Result <= 0)
+ {
+ DPRINT1("The compressed sibling node name is lesser or equal to that of the child\n");
+ return FALSE;
+ }
+ }
+
+ /*
+ * One of the cases above has met the conditions
+ * successfully, the lexicographical order is legal.
+ */
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Validates the class of a given key cell.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which
+ * the registry call is to be validated.
+ *
+ * @param[in] CurrentCell
+ * The current key cell that the class points to.
+ *
+ * @param[in] CellData
+ * A pointer to cell data of the current key cell
+ * that contains the class to be validated.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD if the class is in good shape.
+ * The same CM status code is returned if the class doesn't
+ * but the class length says otherwise. CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED
+ * is returned if the class cell is not allocated.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateClass(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData)
+{
+ ULONG ClassLength;
+ HCELL_INDEX ClassCell;
+
+ PAGED_CODE();
+
+ ASSERT(CurrentCell != HCELL_NIL);
+ ASSERT(CellData);
+
+ /* Cache the class cell and validate it (if any) */
+ ClassCell = CellData->u.KeyNode.Class;
+ ClassLength = CellData->u.KeyNode.ClassLength;
+ if (ClassLength > 0)
+ {
+ if (ClassCell == HCELL_NIL)
+ {
+ /*
+ * Somebody has freed the class but left the
+ * length as is, reset it.
+ */
+ DPRINT1("The key node class is NIL but the class length is not 0, resetting it\n");
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.ClassLength = 0;
+ return CM_CHECK_REGISTRY_GOOD;
+ }
+
+ if (!HvIsCellAllocated(Hive, ClassCell))
+ {
+ DPRINT1("The key class is not allocated\n");
+ return CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED;
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Validates each value in the list by count. A
+ * value that is damaged gets removed from the list.
+ * This routine performs self-healing process in
+ * this case.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which a list
+ * of registry values is to be validated.
+ *
+ * @param[in] CurrentCell
+ * The current key cell that the value list points to.
+ *
+ * @param[in] ListCount
+ * The list count that describes the actual number of
+ * values in the list.
+ *
+ * @param[in] ValueListData
+ * A pointer to cell data of the current key cell
+ * that contains the value list to be validated.
+ *
+ * @param[out] ValuesRemoved
+ * When the function finishes doing its operations,
+ * this parameter contains the amount of removed values
+ * from the list. A value of 0 indicates no values have
+ * been removed (which that would imply a self-healing
+ * process of the value list has occurred).
+ *
+ * @param[in] FixHive
+ * If set to TRUE, the target hive will be fixed.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD if the value list is
+ * sane. CM_CHECK_REGISTRY_VALUE_CELL_NIL is returned
+ * if a certain value cell is HCELL_NIL at specific
+ * count index. CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED is
+ * returned if a certain value cell is unallocated at specific
+ * count index. CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND is
+ * returned if cell data could not be mapped from the value cell,
+ * the value list is totally torn apart in this case.
+ * CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE is returned if the
+ * value's size is bogus. CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA
+ * is returned if the data inside the value is HCELL_NIL.
+ * CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED is returned if the
+ * data cell inside the value is not allocated.
+ * CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE is returned if the
+ * value's signature is not valid.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateValueListByCount(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG ListCount,
+ _In_ PCELL_DATA ValueListData,
+ _Out_ PULONG ValuesRemoved,
+ _In_ BOOLEAN FixHive)
+{
+ ULONG ValueDataSize;
+ ULONG ListCountIndex;
+ ULONG DataSize;
+ HCELL_INDEX DataCell;
+ HCELL_INDEX ValueCell;
+ PCELL_DATA ValueData;
+ ULONG ValueNameLength, TotalValueNameLength;
+
+ PAGED_CODE();
+
+ ASSERT(ValueListData);
+ ASSERT(ListCount != 0);
+
+ /* Assume we haven't removed any value counts for now */
+ *ValuesRemoved = 0;
+
+ /*
+ * Begin looping each value in the list and
+ * validate it accordingly.
+ */
+ ListCountIndex = 0;
+ while (ListCountIndex < ListCount)
+ {
+ ValueCell = ValueListData->u.KeyList[ListCountIndex];
+ if (ValueCell == HCELL_NIL)
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The value cell is NIL (at index %lu, list count %lu)\n",
+ ListCountIndex, ListCount);
+ return CM_CHECK_REGISTRY_VALUE_CELL_NIL;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+
+ if (!HvIsCellAllocated(Hive, ValueCell))
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The value cell is not allocated (at index %lu, list count %lu)\n",
+ ListCountIndex, ListCount);
+ return CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+
+ /* Obtain a cell data from this value */
+ ValueData = (PCELL_DATA)HvGetCell(Hive, ValueCell);
+ if (!ValueData)
+ {
+ DPRINT1("Cell data of the value cell not found (at index %lu, value count %lu)\n",
+ ListCountIndex, ListCount);
+ return CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND;
+ }
+
+ /* Check that the value size is sane */
+ ValueDataSize = HvGetCellSize(Hive, ValueData);
+ ValueNameLength = ValueData->u.KeyValue.NameLength;
+ TotalValueNameLength = ValueNameLength + FIELD_OFFSET(CM_KEY_VALUE, Name);
+ if (TotalValueNameLength > ValueDataSize)
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The total size is bigger than the actual cell size (total size %lu, cell size %lu, at index %lu)\n",
+ TotalValueNameLength, ValueDataSize, ListCountIndex);
+ return CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+
+ /*
+ * The value cell has a sane size. The last thing
+ * to validate is the actual data of the value cell.
+ * That is, we want that the data itself and length
+ * are consistent. Technically speaking, value keys
+ * that are small are directly located in the value
+ * cell and it's built-in, in other words, the data
+ * is immediately present in the cell so we don't have
+ * to bother validating them since they're alright on
+ * their own. This can't be said the same about normal
+ * values though.
+ */
+ DataCell = ValueData->u.KeyValue.Data;
+ if (!CmpIsKeyValueSmall(&DataSize, ValueData->u.KeyValue.DataLength))
+ {
+ /* Validate the actual data based on size */
+ if (DataSize == 0)
+ {
+ if (DataCell != HCELL_NIL)
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
+ return CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+ }
+ else
+ {
+ if (!HvIsCellAllocated(Hive, DataCell))
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
+ return CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+ }
+
+ /* FIXME: Big values not supported yet */
+ ASSERT_VALUE_BIG(Hive, DataSize);
+ }
+
+ /* Is the signature valid? */
+ if (ValueData->u.KeyValue.Signature != CM_KEY_VALUE_SIGNATURE)
+ {
+ if (!CmpRepairValueListCount(Hive,
+ CurrentCell,
+ ListCountIndex,
+ ValueListData,
+ FixHive))
+ {
+ DPRINT1("The key value signature is invalid\n");
+ return CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE;
+ }
+
+ /* Decrease the list count and go to the next value */
+ ListCount--;
+ *ValuesRemoved++;
+ DPRINT1("Damaged value removed, continuing with the next value...\n");
+ continue;
+ }
+
+ /* Advance to the next value */
+ ListCountIndex++;
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Validates the value list of a key. If the list
+ * is damaged due to corruption, the whole list
+ * is expunged. This function performs self-healing
+ * procedures in this case.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which a list of
+ * registry values is to be validated.
+ *
+ * @param[in] CurrentCell
+ * The current key cell that the value list points to.
+ *
+ * @param[in] CellData
+ * The cell data of the current cell of which the value
+ * list comes from.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, the target hive will be fixed.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD if the value list is
+ * sane. CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED is returned
+ * if the value list cell is not allocated. CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND
+ * is returned if cell data could not be mapped from the value
+ * list cell. CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE is returned
+ * if the size of the value list is bogus. A failure CM status code
+ * is returned otherwise.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateValueList(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ CM_CHECK_REGISTRY_STATUS CmStatusCode;
+ ULONG TotalValueLength, ValueSize;
+ ULONG ValueListCount;
+ ULONG ValuesRemoved;
+ HCELL_INDEX ValueListCell;
+ PCELL_DATA ValueListData;
+
+ PAGED_CODE();
+
+ ASSERT(CurrentCell != HCELL_NIL);
+ ASSERT(CellData);
+
+ /* Cache the value list and validate it */
+ ValueListCell = CellData->u.KeyNode.ValueList.List;
+ ValueListCount = CellData->u.KeyNode.ValueList.Count;
+ if (ValueListCount > 0)
+ {
+ if (!HvIsCellAllocated(Hive, ValueListCell))
+ {
+ DPRINT1("The value list is not allocated\n");
+ return CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED;
+ }
+
+ /* Obtain cell data from the value list cell */
+ ValueListData = (PCELL_DATA)HvGetCell(Hive, ValueListCell);
+ if (!ValueListData)
+ {
+ DPRINT1("Could not get cell data from the value list\n");
+ return CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND;
+ }
+
+ /*
+ * Cache the value size and total length and
+ * assert ourselves this is a sane value list
+ * to begin with.
+ */
+ ValueSize = HvGetCellSize(Hive, ValueListData);
+ TotalValueLength = ValueListCount * sizeof(HCELL_INDEX);
+ if (TotalValueLength > ValueSize)
+ {
+ DPRINT1("The value list is bigger than the cell (value list size %lu, cell size %lu)\n",
+ TotalValueLength, ValueSize);
+ return CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE;
+ }
+
+ /*
+ * The value list is sane, now we would
+ * need to validate the actual list
+ * by its count.
+ */
+ CmStatusCode = CmpValidateValueListByCount(Hive,
+ CurrentCell,
+ ValueListCount,
+ ValueListData,
+ &ValuesRemoved,
+ FixHive);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ DPRINT1("One of the values is corrupt and couldn't be repaired\n");
+ return CmStatusCode;
+ }
+
+ /* Log how much values have been removed */
+ if (ValuesRemoved > 0)
+ {
+ DPRINT1("Values removed in the list -- %lu\n", ValuesRemoved);
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Validates the subkeys list of a key. If the list is
+ * damaged from corruption, the function can either
+ * salvage this list or purge the whole of it. The
+ * function performs different validation steps for
+ * different storage types.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which a list of
+ * subkeys is to be validated.
+ *
+ * @param[in] CurrentCell
+ * The current key cell that the subkeys list points to.
+ *
+ * @param[in] CellData
+ * The cell data of the current cell of which the subkeys
+ * list comes from.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, the target hive will be fixed.
+ *
+ * @param[out] DoRepair
+ * A pointer to a boolean value set up by the function itself.
+ * The function automatically sets this to FALSE indicating
+ * that repairs can't be done onto the list itself. If the
+ * list can be salvaged, then the function sets this to TRUE.
+ * See Remarks for further information.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD if the subkeys list is in
+ * perfect shape. CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE is
+ * returned if the volatile storage has stable data which
+ * that should not happen (this is only for the case of volatile
+ * cells). CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED is returned
+ * if the subkeys list cell is not allocated. CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX
+ * is returned if a key index could not be mapped from the subkeys
+ * list cell. CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT is returned if
+ * the key index is a leaf and the subkeys count doesn't match up
+ * with that of the leaf. CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED is
+ * returned if the key index cell at the specific index in the list of
+ * the index is not allocated. CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT is
+ * returned if a leaf could not be mapped from an index.
+ * CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE is returned if the leaf has
+ * an invalid signature. CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE
+ * is returned if the key index has an invalid signature, that is, it's
+ * not a leaf nor a root.
+ *
+ * @remarks
+ * Deep subkeys list healing can be done in specific cases where only
+ * a subkey doesn't actually affect the key itself. The function will
+ * mark the subkeys list as repairable by setting DoRepair parameter
+ * to TRUE and the caller is responsible to heal the key by purging
+ * the whole subkeys list. If the damage is so bad that there's
+ * possibility the key itself is even damaged, no healing is done.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateSubKeyList(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive,
+ _Out_ PBOOLEAN DoRepair)
+{
+ ULONG SubKeyCounts;
+ HCELL_INDEX KeyIndexCell, SubKeysListCell;
+ PCM_KEY_INDEX RootKeyIndex, LeafKeyIndex;
+ ULONG RootIndex;
+ ULONG TotalLeafCount;
+
+ PAGED_CODE();
+
+ ASSERT(CurrentCell != HCELL_NIL);
+ ASSERT(CellData);
+
+ RootKeyIndex = NULL;
+ LeafKeyIndex = NULL;
+ TotalLeafCount = 0;
+
+ /*
+ * Assume for now that the caller should not
+ * do any kind of repairs on the subkeys list,
+ * unless explicitly given the consent by us.
+ */
+ *DoRepair = FALSE;
+
+ /*
+ * For volatile keys they have data that can
+ * fluctuate and change on the fly so there's
+ * pretty much nothing that we can validate those.
+ * But still, we would want that the volatile key
+ * is not damaged by external factors, like e.g.,
+ * having stable keys on a volatile space.
+ */
+ if (IS_CELL_VOLATILE(CurrentCell))
+ {
+ if (CellData->u.KeyNode.SubKeyCounts[Stable] != 0)
+ {
+ DPRINT1("The volatile key has stable subkeys\n");
+ return CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE;
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+ }
+
+ /*
+ * This is not a volatile key, cache the subkeys list
+ * and validate it.
+ */
+ SubKeysListCell = CellData->u.KeyNode.SubKeyLists[Stable];
+ SubKeyCounts = CellData->u.KeyNode.SubKeyCounts[Stable];
+ if (SubKeyCounts > 0)
+ {
+ if (!HvIsCellAllocated(Hive, SubKeysListCell))
+ {
+ DPRINT1("The subkeys list cell is not allocated\n");
+ *DoRepair = TRUE;
+ return CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED;
+ }
+
+ /* Obtain a root index and validate it */
+ RootKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, SubKeysListCell);
+ if (!RootKeyIndex)
+ {
+ DPRINT1("Could not get the root key index of the subkeys list cell\n");
+ return CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX;
+ }
+
+ /*
+ * For simple, fast and hashed leaves we would want
+ * that the corresponding root index count matches with
+ * that of the subkey counts itself. If this is not the
+ * case we can isolate this problem and fix the count.
+ */
+ if (RootKeyIndex->Signature == CM_KEY_INDEX_LEAF ||
+ RootKeyIndex->Signature == CM_KEY_FAST_LEAF ||
+ RootKeyIndex->Signature == CM_KEY_HASH_LEAF)
+ {
+ if (SubKeyCounts != RootKeyIndex->Count)
+ {
+ if (!CmpRepairSubKeyCounts(Hive,
+ CurrentCell,
+ RootKeyIndex->Count,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("The subkeys list has invalid count (subkeys count %lu, root key index count %lu)\n",
+ SubKeyCounts, RootKeyIndex->Count);
+ return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+ }
+
+ /*
+ * The root index is not a leaf, check if the index
+ * is an actual root then.
+ */
+ if (RootKeyIndex->Signature == CM_KEY_INDEX_ROOT)
+ {
+ /*
+ * For the root we have to loop each leaf
+ * from it and increase the total leaf count
+ * in the root after we determined the validity
+ * of a leaf. This way we can see if the subcount
+ * matches with that of the subkeys list count.
+ */
+ for (RootIndex = 0; RootIndex < RootKeyIndex->Count; RootIndex++)
+ {
+ KeyIndexCell = RootKeyIndex->List[RootIndex];
+ if (!HvIsCellAllocated(Hive, KeyIndexCell))
+ {
+ DPRINT1("The key index cell is not allocated at index %lu\n", RootIndex);
+ *DoRepair = TRUE;
+ return CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED;
+ }
+
+ /* Obtain a leaf from the root */
+ LeafKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, KeyIndexCell);
+ if (!LeafKeyIndex)
+ {
+ DPRINT1("The root key index's signature is invalid!\n");
+ return CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT;
+ }
+
+ /* Check that the leaf has valid signature */
+ if (LeafKeyIndex->Signature != CM_KEY_INDEX_LEAF &&
+ LeafKeyIndex->Signature != CM_KEY_FAST_LEAF &&
+ LeafKeyIndex->Signature != CM_KEY_HASH_LEAF)
+ {
+ DPRINT1("The leaf's signature is invalid!\n");
+ *DoRepair = TRUE;
+ return CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE;
+ }
+
+ /* Add up the count of the leaf */
+ TotalLeafCount += LeafKeyIndex->Count;
+ }
+
+ /*
+ * We have built up the total leaf count,
+ * we have to determine this count is exactly
+ * the same as the subkeys list count. Otherwise
+ * just fix it.
+ */
+ if (SubKeyCounts != TotalLeafCount)
+ {
+ if (!CmpRepairSubKeyCounts(Hive,
+ CurrentCell,
+ TotalLeafCount,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("The subkeys list has invalid count (subkeys count %lu, total leaf count %lu)\n",
+ SubKeyCounts, TotalLeafCount);
+ return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+ }
+
+ /*
+ * None of the valid signatures match with that of
+ * the root key index. By definition, the whole subkey
+ * list is total toast.
+ */
+ DPRINT1("The root key index's signature is invalid\n");
+ *DoRepair = TRUE;
+ return CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE;
+ }
+
+ /* If we reach here then this key has no subkeys */
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Purges the volatile storage of a registry
+ * hive. This operation is done mainly during
+ * the bootup of the system.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which volatiles
+ * are to be purged.
+ *
+ * @param[in] CurrentCell
+ * The current key cell that the volatile storage of
+ * the hive points to.
+ *
+ * @param[in] CellData
+ * The cell data of the current cell of which the volatile
+ * subkeys storage comes from.
+ *
+ * @param[in] Flags
+ * A bit mask flag that is used to influence how is the
+ * purging operation to be done. See CmCheckRegistry documentation
+ * below for more information.
+ */
+static
+VOID
+CmpPurgeVolatiles(
+ _In_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ ULONG Flags)
+{
+ PAGED_CODE();
+
+ ASSERT(CellData);
+
+ /* Did the caller ask to purge volatiles? */
+ if (((Flags & CM_CHECK_REGISTRY_PURGE_VOLATILES) ||
+ (Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES)) &&
+ (CellData->u.KeyNode.SubKeyCounts[Volatile] != 0))
+ {
+ /*
+ * OK, the caller wants them cleaned from this
+ * hive. For XP Beta 1 or newer hives, we unintialize
+ * the whole volatile subkeys list. For older hives,
+ * we just do a cleanup.
+ */
+#if !defined(_BLDR_)
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+#endif
+ if ((Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES) &&
+ (Hive->Version >= HSYS_WHISTLER_BETA1))
+ {
+ CellData->u.KeyNode.SubKeyLists[Volatile] = CMP_VOLATILE_LIST_UNINTIALIZED;
+ }
+ else
+ {
+ CellData->u.KeyNode.SubKeyLists[Volatile] = HCELL_NIL;
+ }
+
+ /* Clear the count */
+ CellData->u.KeyNode.SubKeyCounts[Volatile] = 0;
+ }
+}
+
+/**
+ * @brief
+ * Validates the key cell, ensuring that
+ * the key in the registry is valid and not corrupted.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of the registry where
+ * the key is to be validated.
+ *
+ * @param[in] SecurityDefaulted
+ * If the caller sets this to TRUE, this indicates the
+ * hive has its security property defaulted due to
+ * heal recovery of the said security. If the caller
+ * sets this to FALSE, the hive comes with its own
+ * security details. This parameter is currently unused.
+ *
+ * @param[in] ParentCell
+ * The parent key cell that comes before the current cell.
+ * This parameter can be HCELL_NIL if the first cell is
+ * the root cell which is the parent of its own.
+ *
+ * @param[in] CurrentCell
+ * The current child key cell that is to be validated.
+ *
+ * @param[in] Flags
+ * A bit mask flag that is used to influence how is the
+ * purging operation of volatile keys in the volatile storage
+ * to be done. See CmCheckRegistry documentation below for more
+ * information.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, the target hive will be fixed.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD if the key that has been validated
+ * is valid and not corrupted. CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED is
+ * returned if the key cell is not allocated. CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND
+ * is returned if cell data could not be mapped from the key cell.
+ * CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE is returned if the key cell
+ * has an abnormal size that is above the trehshold the validation checks
+ * can permit. CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO is returned if the
+ * name length of the key node is 0, meaning that the key has no name.
+ * CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL is returned if the key is too
+ * big than the cell itself. CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT is
+ * returned if the parent node of the key is incosistent and it couldn't
+ * be fixed. CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE is returned if
+ * the signature of the key node is corrupt and it couldn't be fixed.
+ * A failure CM status code is returned otherwise.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateKey(
+ _In_ PHHIVE Hive,
+ _In_ BOOLEAN SecurityDefaulted,
+ _In_ HCELL_INDEX ParentCell,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG Flags,
+ _In_ BOOLEAN FixHive)
+{
+ CM_CHECK_REGISTRY_STATUS CmStatusCode;
+ PCELL_DATA CellData;
+ ULONG CellSize;
+ BOOLEAN DoSubkeysRepair;
+ ULONG TotalKeyNameLength, NameLength;
+
+ PAGED_CODE();
+
+ /* The current key cell mustn't be NIL here! */
+ ASSERT(CurrentCell != HCELL_NIL);
+
+ /* TODO: To be removed once we support security caching in Cm */
+ UNREFERENCED_PARAMETER(SecurityDefaulted);
+
+ /*
+ * We must ensure that the key cell is
+ * allocated in the first place before
+ * we go further.
+ */
+ if (!HvIsCellAllocated(Hive, CurrentCell))
+ {
+ DPRINT1("The key cell is not allocated\n");
+ return CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED;
+ }
+
+ /* Obtain cell data from it */
+ CellData = (PCELL_DATA)HvGetCell(Hive, CurrentCell);
+ if (!CellData)
+ {
+ DPRINT1("Could not get cell data from the cell\n");
+ return CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND;
+ }
+
+ /* Get the size of this cell and validate its size */
+ CellSize = HvGetCellSize(Hive, CellData);
+ if (CellSize > CMP_KEY_SIZE_THRESHOLD)
+ {
+ DPRINT1("The cell size is above the threshold size (size %lu)\n", CellSize);
+ return CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE;
+ }
+
+ /*
+ * The cell size is OK but we must ensure
+ * the key is not bigger than the container
+ * of the cell.
+ */
+ NameLength = CellData->u.KeyNode.NameLength;
+ if (NameLength == 0)
+ {
+ DPRINT1("The key node name length is 0!\n");
+ return CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO;
+ }
+
+ TotalKeyNameLength = NameLength + FIELD_OFFSET(CM_KEY_NODE, Name);
+ if (TotalKeyNameLength > CellSize)
+ {
+ DPRINT1("The key is too big than the cell (key size %lu, cell size %lu)\n", TotalKeyNameLength, CellSize);
+ return CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL;
+ }
+
+ /* Is the parent cell consistent? */
+ if (ParentCell != HCELL_NIL &&
+ ParentCell != CellData->u.KeyNode.Parent)
+ {
+ if (!CmpRepairParentNode(Hive,
+ CurrentCell,
+ ParentCell,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("The parent key node doesn't point to the actual parent\n");
+ return CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT;
+ }
+ }
+
+ /* Is the key node signature valid? */
+ if (CellData->u.KeyNode.Signature != CM_KEY_NODE_SIGNATURE)
+ {
+ if (!CmpRepairKeyNodeSignature(Hive,
+ CurrentCell,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("The parent key node signature is not valid\n");
+ return CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE;
+ }
+ }
+
+ /*
+ * FIXME: Security cell checks have to be implemented here
+ * once we properly and reliably implement security caching
+ * in the kernel.
+ */
+
+ /* Validate the class */
+ CmStatusCode = CmpValidateClass(Hive, CurrentCell, CellData);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ if (!CmpRepairClassOfNodeKey(Hive,
+ CurrentCell,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("Failed to repair the hive, the cell class is not valid\n");
+ return CmStatusCode;
+ }
+ }
+
+ /* Validate the value list */
+ CmStatusCode = CmpValidateValueList(Hive, CurrentCell, CellData, FixHive);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ /*
+ * It happens that a certain value in the list
+ * is so bad like we couldn't map a cell data from it
+ * or the list itself is toast. In such cases what we
+ * can do here is to do a "value list sacrifice", aka
+ * purge the whole list.
+ */
+ if (!CmpRepairValueList(Hive, CurrentCell, FixHive))
+ {
+ DPRINT1("Failed to repair the hive, the value list is corrupt\n");
+ return CmStatusCode;
+ }
+ }
+
+ /* Validate the subkeys list */
+ CmStatusCode = CmpValidateSubKeyList(Hive, CurrentCell, CellData, FixHive, &DoSubkeysRepair);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ /*
+ * The subkeys list is in trouble. Worse when the actual
+ * subkey list is so severed this key is also kaput on itself.
+ */
+ if (!DoSubkeysRepair)
+ {
+ DPRINT1("The subkeys list is totally corrupt, can't repair\n");
+ return CmStatusCode;
+ }
+
+ /*
+ * OK, there's still some salvation for this key.
+ * Purge the whole subkeys list in order to fix it.
+ */
+ if (!CmpRepairSubKeyList(Hive,
+ CurrentCell,
+ CellData,
+ FixHive))
+ {
+ DPRINT1("Failed to repair the hive, the subkeys list is corrupt!\n");
+ return CmStatusCode;
+ }
+ }
+
+ /* Purge volatile data if needed */
+ CmpPurgeVolatiles(Hive, CurrentCell, CellData, Flags);
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Performs deep checking of the registry by walking
+ * down the registry tree using a stack based pool.
+ * This function is the guts of CmCheckRegistry.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of the registry where
+ * the validation is to be performed.
+ *
+ * @param[in] Flags
+ * Bit mask flag used for volatiles purging. Such
+ * flags influence on how volatile purging is actually
+ * done. See CmCheckRegistry documentation for more
+ * information.
+ *
+ * @param[in] SecurityDefaulted
+ * If the caller sets this to FALSE, the registry hive
+ * uses its own unique security details. Otherwise
+ * registry hive has the security details defaulted.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, the target hive will be fixed.
+ *
+ * @return
+ * Returns CM_CHECK_REGISTRY_GOOD is returned if the function
+ * has successfully performed deep registry checking and
+ * the registry contents are valid. CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL
+ * is returned if the function has failed to allocate the
+ * stack work state buffer in memory which is necessary for
+ * deep checking of the registry. CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND
+ * is returned if no root cell has been found of this hive.
+ * CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER is returned if the lexical
+ * order is not valid. CM_CHECK_REGISTRY_NODE_NOT_FOUND is returned if
+ * the no key node could be mapped from the key. CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND
+ * is returned if no subkey child cell could be found. CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS
+ * is returned if we have reached the maximum stack limit which means the registry that
+ * we have checked is too fat.
+ */
+static
+CM_CHECK_REGISTRY_STATUS
+CmpValidateRegistryInternal(
+ _In_ PHHIVE Hive,
+ _In_ ULONG Flags,
+ _In_ BOOLEAN SecurityDefaulted,
+ _In_ BOOLEAN FixHive)
+{
+ CM_CHECK_REGISTRY_STATUS CmStatusCode;
+ PCMP_REGISTRY_STACK_WORK_STATE WorkState;
+ HCELL_INDEX RootCell, ParentCell, CurrentCell;
+ HCELL_INDEX ChildSubKeyCell;
+ PCM_KEY_NODE KeyNode;
+ ULONG WorkStateLength;
+ LONG StackDepth;
+ BOOLEAN AllChildrenChecked;
+
+ PAGED_CODE();
+
+ ASSERT(Hive);
+
+ /*
+ * Allocate some memory blocks for the stack
+ * state structure. We'll be using it to walk
+ * down the registry hive tree in a recursive
+ * way without worrying that we explode the
+ * kernel stack in the most gruesome and gross
+ * ways.
+ */
+ WorkStateLength = CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH * sizeof(CMP_REGISTRY_STACK_WORK_STATE);
+ WorkState = CmpAllocate(WorkStateLength,
+ TRUE,
+ TAG_REGISTRY_STACK);
+ if (!WorkState)
+ {
+ DPRINT1("Couldn't allocate memory for registry stack work state\n");
+ return CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL;
+ }
+
+ /* Obtain the root cell of the hive */
+ RootCell = GET_HHIVE_ROOT_CELL(Hive);
+ if (RootCell == HCELL_NIL)
+ {
+ DPRINT1("Couldn't get the root cell of the hive\n");
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND;
+ }
+
+RestartValidation:
+ /*
+ * Prepare the stack state and start from
+ * the root cell. Ensure that the root cell
+ * itself is OK before we go forward.
+ */
+ StackDepth = 0;
+ WorkState[StackDepth].ChildCellIndex = 0;
+ WorkState[StackDepth].Current = RootCell;
+ WorkState[StackDepth].Parent = HCELL_NIL;
+ WorkState[StackDepth].Sibling = HCELL_NIL;
+
+ /*
+ * As we start checking the root cell which
+ * is the top element of a registry hive,
+ * we'll be going to look for child keys
+ * in the course of walking down the tree.
+ */
+ AllChildrenChecked = FALSE;
+
+ while (StackDepth >= 0)
+ {
+ /* Cache the current and parent cells */
+ CurrentCell = WorkState[StackDepth].Current;
+ ParentCell = WorkState[StackDepth].Parent;
+
+ /* Do we have still have children to validate? */
+ if (!AllChildrenChecked)
+ {
+ /* Check that the key is OK */
+ CmStatusCode = CmpValidateKey(Hive,
+ SecurityDefaulted,
+ ParentCell,
+ CurrentCell,
+ Flags,
+ FixHive);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ /*
+ * The key cell is damaged. We have to pray and
+ * hope that this is not the root cell as any
+ * damage done to the root is catastrophically
+ * fatal.
+ */
+ if (CurrentCell == RootCell)
+ {
+ DPRINT1("THE ROOT CELL IS BROKEN\n");
+ CmpFree(WorkState, WorkStateLength);
+ return CmStatusCode;
+ }
+
+ /*
+ * It is not the root, remove the faulting
+ * damaged cell from the parent so that we
+ * can heal the hive.
+ */
+ if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
+ {
+ DPRINT1("The key is corrupt (current cell %lu, parent cell %lu)\n",
+ CurrentCell, ParentCell);
+ CmpFree(WorkState, WorkStateLength);
+ return CmStatusCode;
+ }
+
+ /* Damaged cell removed, restart the loop */
+ DPRINT1("Hive repaired, restarting the validation loop...\n");
+ goto RestartValidation;
+ }
+
+ /*
+ * The key is in perfect shape. If we have advanced
+ * the stack depth then check the lexicographical
+ * order of the keys as well.
+ */
+ if (StackDepth > 0 &&
+ CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ if (WorkState[StackDepth - CMP_PRIOR_STACK].Sibling != HCELL_NIL)
+ {
+ if (!CmpValidateLexicalOrder(Hive,
+ CurrentCell,
+ WorkState[StackDepth - CMP_PRIOR_STACK].Sibling))
+ {
+ /*
+ * The lexicographical order is bad,
+ * attempt to heal the hive.
+ */
+ if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
+ {
+ DPRINT1("The lexicographical order is invalid (sibling %lu, current cell %lu)\n",
+ CurrentCell, WorkState[StackDepth - CMP_PRIOR_STACK].Sibling);
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER;
+ }
+
+ /* Damaged cell removed, restart the loop */
+ DPRINT1("Hive repaired, restarting the validation loop...\n");
+ goto RestartValidation;
+ }
+ }
+
+ /* Assign the prior sibling for upcoming iteration */
+ WorkState[StackDepth - CMP_PRIOR_STACK].Sibling = CurrentCell;
+ }
+ }
+
+ /* Obtain a node for this key */
+ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, CurrentCell);
+ if (!KeyNode)
+ {
+ DPRINT1("Couldn't get the node of key (current cell %lu)\n", CurrentCell);
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_NODE_NOT_FOUND;
+ }
+
+ /*
+ * If we have processed all the children from this
+ * node then adjust the stack depth work state by
+ * going back and restart the loop to lookup for
+ * the rest of the tree. Acknowledge the code path
+ * above that we checked all the children so that
+ * we don't have to validate the same subkey again.
+ */
+ if (WorkState[StackDepth].ChildCellIndex < KeyNode->SubKeyCounts[Stable])
+ {
+ /*
+ * We have children to process, obtain the
+ * child subkey in question so that we can
+ * cache it later for the next key validation.
+ */
+ ChildSubKeyCell = CmpFindSubKeyByNumber(Hive, KeyNode, WorkState[StackDepth].ChildCellIndex);
+ if (ChildSubKeyCell == HCELL_NIL)
+ {
+ DPRINT1("Couldn't get the child subkey cell (at stack index %lu)\n", StackDepth);
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND;
+ }
+
+ /*
+ * As we got the subkey advance the child index as
+ * well as the stack depth work state for the next
+ * key validation. However we must ensure since
+ * we're advancing the stack depth that we don't
+ * go over the maximum tree level depth. A registry
+ * tree can be at maximum 512 levels deep.
+ *
+ * For more information see https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-element-siz….
+ */
+ WorkState[StackDepth].ChildCellIndex++;
+ StackDepth++;
+ if (StackDepth >= CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH - 1)
+ {
+ /*
+ * This registry has so many levels it's
+ * so fat. We don't want to explode our
+ * kernel stack, so just simply bail out...
+ */
+ DPRINT1("The registry tree has so many levels!\n");
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS;
+ }
+
+ /* Prepare the work state for the next key */
+ WorkState[StackDepth].ChildCellIndex = 0;
+ WorkState[StackDepth].Current = ChildSubKeyCell;
+ WorkState[StackDepth].Parent = WorkState[StackDepth - CMP_PRIOR_STACK].Current;
+ WorkState[StackDepth].Sibling = HCELL_NIL;
+
+ /*
+ * As we prepared the work state, acknowledge the
+ * code path at the top of the loop that we need
+ * to process and validate the next child subkey.
+ */
+ AllChildrenChecked = FALSE;
+ continue;
+ }
+
+ /*
+ * We have validated all the child subkeys
+ * of the node. Decrease the stack depth
+ * and tell the above code we looked for all
+ * children so that we don't need to validate
+ * the same children again but go for the next
+ * node.
+ */
+ AllChildrenChecked = TRUE;
+ StackDepth--;
+ continue;
+ }
+
+ CmpFree(WorkState, WorkStateLength);
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+/**
+ * @brief
+ * Validates a bin from a hive. It performs checks
+ * against the cells from this bin, ensuring the
+ * bin is not corrupt and that the cells are consistent
+ * with each other.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor of which a hive bin
+ * is to be validated.
+ *
+ * @param[in] Bin
+ * A pointer to a bin where its cells are to be
+ * validated.
+ *
+ * @return
+ * CM_CHECK_REGISTRY_GOOD is returned if the bin is
+ * valid and not corrupt. CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT
+ * is returned if this bin has a corrupt signature. CM_CHECK_REGISTRY_BAD_FREE_CELL
+ * is returned if the free cell has a bogus size. CM_CHECK_REGISTRY_BAD_ALLOC_CELL
+ * is returned for the allocated cell has a bogus size.
+ */
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+HvValidateBin(
+ _In_ PHHIVE Hive,
+ _In_ PHBIN Bin)
+{
+ PHCELL Cell, Basket;
+
+ PAGED_CODE();
+
+ ASSERT(Bin);
+ ASSERT(Hive);
+
+ /* Ensure that this bin we got has valid signature header */
+ if (Bin->Signature != HV_HBIN_SIGNATURE)
+ {
+ DPRINT1("The bin's signature header is corrupt\n");
+ return CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT;
+ }
+
+ /*
+ * Walk over all the cells from this bin and
+ * validate that they're consistent with the bin.
+ * Namely we want that each cell from this bin doesn't
+ * have a bogus size.
+ */
+ Basket = (PHCELL)((PUCHAR)Bin + Bin->Size);
+ for (Cell = GET_CELL_BIN(Bin);
+ Cell < Basket;
+ Cell = (PHCELL)((PUCHAR)Cell + abs(Cell->Size)))
+ {
+ if (IsFreeCell(Cell))
+ {
+ /*
+ * This cell is free, check that
+ * the size of this cell is not bogus.
+ */
+ if (Cell->Size > Bin->Size ||
+ Cell->Size == 0)
+ {
+ /*
+ * This cell has too much free space that
+ * exceeds the boundary of the bin size.
+ * Otherwise the cell doesn't have actual
+ * free space (aka Size == 0) which is a
+ * no go for a bin.
+ */
+ DPRINT1("The free cell exceeds the bin size or cell size equal to 0 (cell 0x%p, cell size %d, bin size %lu)\n",
+ Cell, Cell->Size, Bin->Size);
+ return CM_CHECK_REGISTRY_BAD_FREE_CELL;
+ }
+ }
+ else
+ {
+ /*
+ * This cell is allocated, make sure that
+ * the size of this cell is not bogus.
+ */
+ if (abs(Cell->Size) > Bin->Size)
+ {
+ /*
+ * This cell allocated too much space
+ * that exceeds the boundary of the
+ * bin size.
+ */
+ DPRINT1("The allocated cell exceeds the bin size (cell 0x%p, cell size %d, bin size %lu)\n",
+ Cell, abs(Cell->Size), Bin->Size);
+ return CM_CHECK_REGISTRY_BAD_ALLOC_CELL;
+ }
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Validates a registry hive. This function ensures
+ * that the storage of this hive has valid bins.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor where validation on
+ * its hive bins is to be performed.
+ *
+ * @return
+ * CM_CHECK_REGISTRY_GOOD is returned if the hive
+ * is valid. CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE is
+ * returned if the hive has a corrupted signature.
+ * CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT is returned
+ * if the captured bin has a bad size. A failure CM status
+ * code is returned otherwise.
+ */
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+HvValidateHive(
+ _In_ PHHIVE Hive)
+{
+ CM_CHECK_REGISTRY_STATUS CmStatusCode;
+ ULONG StorageIndex;
+ ULONG BlockIndex;
+ ULONG StorageLength;
+ PHBIN Bin;
+
+ PAGED_CODE();
+
+ ASSERT(Hive);
+
+ /* Is the hive signature valid? */
+ if (Hive->Signature != HV_HHIVE_SIGNATURE)
+ {
+ DPRINT1("Hive's signature corrupted (signature %lu)\n", Hive->Signature);
+ return CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE;
+ }
+
+ /*
+ * Now loop each bin in the storage of this
+ * hive.
+ */
+ for (StorageIndex = 0; StorageIndex < Hive->StorageTypeCount; StorageIndex++)
+ {
+ /* Get the storage length at this index */
+ StorageLength = Hive->Storage[StorageIndex].Length;
+
+ for (BlockIndex = 0; BlockIndex < StorageLength;)
+ {
+ /* Go to the next if this bin does not exist */
+ if (Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress == (ULONG_PTR)NULL)
+ {
+ continue;
+ }
+
+ /*
+ * Capture this bin and ensure that such
+ * bin is within the offset and the size
+ * is not bogus.
+ */
+ Bin = GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex);
+ if (Bin->Size > (StorageLength * HBLOCK_SIZE) ||
+ (Bin->FileOffset / HBLOCK_SIZE) != BlockIndex)
+ {
+ DPRINT1("Bin size or offset is corrupt (bin size %lu, file offset %lu, storage length %lu)\n",
+ Bin->Size, Bin->FileOffset, StorageLength);
+ return CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT;
+ }
+
+ /* Validate the rest of the bin */
+ CmStatusCode = HvValidateBin(Hive, Bin);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ DPRINT1("This bin is not valid (bin 0x%p)\n", Bin);
+ return CmStatusCode;
+ }
+
+ /* Go to the next block */
+ BlockIndex += Bin->Size / HBLOCK_SIZE;
+ }
+ }
+
+ return CM_CHECK_REGISTRY_GOOD;
+}
+
+/**
+ * @brief
+ * Checks the registry that is consistent and its
+ * contents valid and not corrupted. More specifically
+ * this function performs a deep check of the registry
+ * for the following properties:
+ *
+ * - That the security cache cell of the registry is OK
+ * - That bins and cells are consistent with each other
+ * - That the child subkey cell points to the parent
+ * - That the key itself has sane sizes
+ * - That the class, values and subkeys lists are valid
+ * - Much more
+ *
+ * @param[in] Hive
+ * A pointer to a CM hive of the registry to be checked
+ * in question.
+ *
+ * @param[in] Flags
+ * A bit mask flag used to influence the process of volatile
+ * keys purging. See Remarks for further information.
+ *
+ * @return
+ * This function returns a CM (Configuration Manager) check
+ * registry status code. A code of CM_CHECK_REGISTRY_GOOD of
+ * value 0 indicates the registry hive is valid and not corrupted.
+ * A non zero unsigned integer value indicates a failure. Consult
+ * other private routines in this file for other failure status
+ * codes.
+ *
+ * @remarks
+ * During a load operation CmCheckRegistry can purge the volatile
+ * data of registry (or not) depending on the submitted flag bit mask
+ * by the caller. The following supported flags are:
+ *
+ * CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES -- Tells the function that
+ * volatile data purging must not be done.
+ *
+ * CM_CHECK_REGISTRY_PURGE_VOLATILES - Tells the function to purge out
+ * volatile information data from a registry hive, on demand. Purging
+ * doesn't come into action if no volatile data has been found.
+ *
+ * CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES - A special flag used
+ * by FreeLdr and Environ. When this flag is set the function will not
+ * clean up the volatile storage but it will unintialize the storage
+ * instead (this is the case if the given registry hive for validation
+ * is a XP Beta 1 hive or newer). Otherwise it will perform a normal
+ * cleanup of the volatile storage.
+ *
+ * CM_CHECK_REGISTRY_VALIDATE_HIVE - Tells the function to perform a
+ * thorough analysation of the underlying hive's bins and cells before
+ * doing validation of the registry tree. HvValidateHive function is called
+ * in this case.
+ *
+ * CM_CHECK_REGISTRY_FIX_HIVE - Tells the function to fix the target registry
+ * hive if it is damaged. Usually this flag comes from a registry repair tool
+ * where the user asked to for its damaged hive to be fixed. In this case
+ * a self-heal procedure against the hive is performed.
+ */
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+CmCheckRegistry(
+ _In_ PCMHIVE RegistryHive,
+ _In_ ULONG Flags)
+{
+ CM_CHECK_REGISTRY_STATUS CmStatusCode;
+ PHHIVE Hive;
+ BOOLEAN ShouldFixHive = FALSE;
+
+ PAGED_CODE();
+
+ /* Bail out if the caller did not give a hive */
+ if (!RegistryHive)
+ {
+ DPRINT1("No registry hive given for check\n");
+ return CM_CHECK_REGISTRY_INVALID_PARAMETER;
+ }
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ /*
+ * The master hive is the root of the registry,
+ * it holds all other hives together. So do not
+ * do any validation checks.
+ */
+ if (RegistryHive == CmiVolatileHive)
+ {
+ DPRINT("This is master registry hive, don't do anything\n");
+ return CM_CHECK_REGISTRY_GOOD;
+ }
+#endif
+
+ /* Bail out if no valid flag is given */
+ if (Flags & ~(CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES |
+ CM_CHECK_REGISTRY_PURGE_VOLATILES |
+ CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES |
+ CM_CHECK_REGISTRY_VALIDATE_HIVE |
+ CM_CHECK_REGISTRY_FIX_HIVE))
+ {
+ DPRINT1("Invalid flag for registry check given (flag %lu)\n", Flags);
+ return CM_CHECK_REGISTRY_INVALID_PARAMETER;
+ }
+
+ /*
+ * Obtain the hive and check if the caller wants
+ * that the hive to be validated.
+ */
+ Hive = GET_HHIVE(RegistryHive);
+ if (Flags & CM_CHECK_REGISTRY_VALIDATE_HIVE)
+ {
+ CmStatusCode = HvValidateHive(Hive);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ DPRINT1("The hive is not valid (hive 0x%p, check status code %lu)\n", Hive, CmStatusCode);
+ return CmStatusCode;
+ }
+ }
+
+ /*
+ * A registry repair tool such as the ReactOS Check Registry
+ * Utility wants the damaged hive to be fixed as we check the
+ * target hive.
+ */
+ if (Flags & CM_CHECK_REGISTRY_FIX_HIVE)
+ {
+ ShouldFixHive = TRUE;
+ }
+
+ /*
+ * FIXME: Currently ReactOS does not implement security
+ * caching algorithms so it's pretty pointless to implement
+ * security descriptors validation checks at this moment.
+ * When the time comes to implement these, we would need
+ * to implement security checks here as well.
+ */
+
+ /* Call the internal API to do the rest of the work bulk */
+ CmStatusCode = CmpValidateRegistryInternal(Hive, Flags, FALSE, ShouldFixHive);
+ if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
+ {
+ DPRINT1("The hive is not valid (hive 0x%p, check status code %lu)\n", Hive, CmStatusCode);
+ return CmStatusCode;
+ }
+
+ return CmStatusCode;
+}
+
+/* EOF */
diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h
index 65f66f581b0..77911739ab6 100644
--- a/sdk/lib/cmlib/cmlib.h
+++ b/sdk/lib/cmlib/cmlib.h
@@ -205,13 +205,77 @@
#endif
#endif
-#define TAG_CM ' MC'
-#define TAG_KCB 'bkMC'
-#define TAG_CMHIVE 'vHMC'
-#define TAG_CMSD 'DSMC'
+#define TAG_CM ' MC'
+#define TAG_KCB 'bkMC'
+#define TAG_CMHIVE 'vHMC'
+#define TAG_CMSD 'DSMC'
+#define TAG_REGISTRY_STACK 'sRMC'
#define CMAPI NTAPI
+//
+// Check Registry status type definition
+//
+typedef ULONG CM_CHECK_REGISTRY_STATUS;
+
+//
+// Check Registry flags
+//
+#define CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES 0x0
+#define CM_CHECK_REGISTRY_PURGE_VOLATILES 0x2
+#define CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES 0x4
+#define CM_CHECK_REGISTRY_VALIDATE_HIVE 0x8
+#define CM_CHECK_REGISTRY_FIX_HIVE 0x10
+
+//
+// Check Registry status codes
+//
+#define CM_CHECK_REGISTRY_GOOD 0
+#define CM_CHECK_REGISTRY_INVALID_PARAMETER 1
+#define CM_CHECK_REGISTRY_SD_INVALID 2
+#define CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE 3
+#define CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT 4
+#define CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT 5
+#define CM_CHECK_REGISTRY_BAD_FREE_CELL 6
+#define CM_CHECK_REGISTRY_BAD_ALLOC_CELL 7
+#define CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL 8
+#define CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND 9
+#define CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER 10
+#define CM_CHECK_REGISTRY_NODE_NOT_FOUND 11
+#define CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND 12
+#define CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS 13
+#define CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED 14
+#define CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND 15
+#define CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE 16
+#define CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO 17
+#define CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL 18
+#define CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT 19
+#define CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE 20
+#define CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED 21
+#define CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED 22
+#define CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND 23
+#define CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE 24
+#define CM_CHECK_REGISTRY_VALUE_CELL_NIL 25
+#define CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED 26
+#define CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND 27
+#define CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE 28
+#define CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA 29
+#define CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED 30
+#define CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE 31
+#define CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE 32
+#define CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED 33
+#define CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX 34
+#define CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT 35
+#define CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED 36
+#define CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT 37
+#define CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE 38
+#define CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE 39
+
+//
+// Check Registry success macro
+//
+#define CM_CHECK_REGISTRY_SUCCESS(StatusCode) ((ULONG)(StatusCode) == CM_CHECK_REGISTRY_GOOD)
+
#include <wine/unicode.h>
#include <wchar.h>
#include "hivedata.h"
@@ -570,6 +634,26 @@ CmPrepareHive(
/* NT-style Public Cm functions */
+//
+// Check Registry Routines
+//
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+HvValidateBin(
+ _In_ PHHIVE Hive,
+ _In_ PHBIN Bin);
+
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+HvValidateHive(
+ _In_ PHHIVE Hive);
+
+CM_CHECK_REGISTRY_STATUS
+NTAPI
+CmCheckRegistry(
+ _In_ PCMHIVE RegistryHive,
+ _In_ ULONG Flags);
+
//
// Cell Index Routines
//
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=54c552392f62f7369c6d7…
commit 54c552392f62f7369c6d7e8395a671fe593afda1
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 19:21:49 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:27 2023 +0100
[SDK][CMLIB] Implement self-heal registry helpers
This implements cmheal.c file which provides the basic registry self-heal infrastructure needed by the public CmCheckRegistry function. The infrastructure provides a range of various self-heal helpers for the hive, such as subkey, class, values and node healing functions.
---
sdk/lib/cmlib/CMakeLists.txt | 1 +
sdk/lib/cmlib/cmheal.c | 912 +++++++++++++++++++++++++++++++++++++++++++
sdk/lib/cmlib/cmlib.h | 73 ++++
3 files changed, 986 insertions(+)
diff --git a/sdk/lib/cmlib/CMakeLists.txt b/sdk/lib/cmlib/CMakeLists.txt
index 2ed0bc304b1..91ccbe6c3e5 100644
--- a/sdk/lib/cmlib/CMakeLists.txt
+++ b/sdk/lib/cmlib/CMakeLists.txt
@@ -5,6 +5,7 @@ add_definitions(
list(APPEND SOURCE
cminit.c
+ cmheal.c
cmindex.c
cmkeydel.c
cmname.c
diff --git a/sdk/lib/cmlib/cmheal.c b/sdk/lib/cmlib/cmheal.c
new file mode 100644
index 00000000000..9489d621bed
--- /dev/null
+++ b/sdk/lib/cmlib/cmheal.c
@@ -0,0 +1,912 @@
+/*
+ * PROJECT: ReactOS Kernel
+ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Configuration Manager Library - Registry Self-Heal Routines
+ * COPYRIGHT: Copyright 2022 George Bișoc <george.bisoc(a)reactos.org>
+ */
+
+#include "cmlib.h"
+#define NDEBUG
+#include <debug.h>
+
+/* GLOBALS ********************************************************************/
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+extern BOOLEAN CmpSelfHeal;
+extern ULONG CmpBootType;
+#endif
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+/**
+ * @brief
+ * Removes a cell from a fast key index.
+ *
+ * @param[in,out] FastIndex
+ * The fast key index where a cell has to be removed.
+ *
+ * @param[in] Index
+ * The index which points to the location of the
+ * cell that is to be removed.
+ *
+ * @param[in] UpdateCount
+ * If set to TRUE, the function will update the fast
+ * index count accordingly by one value less. If set
+ * to FALSE, the count won't be updated. See Remarks
+ * for further information.
+ *
+ * @remarks
+ * In case where the fast index count is not updated is
+ * when the key index is not a root but a leaf. In such
+ * scenario such leaf is the actual key index itself
+ * so updating the fast index count is not necessary (aka
+ * UpdateCount is set to FALSE).
+ */
+static
+VOID
+CmpRemoveFastIndexKeyCell(
+ _Inout_ PCM_KEY_FAST_INDEX FastIndex,
+ _In_ ULONG Index,
+ _In_ BOOLEAN UpdateCount)
+{
+ ULONG MoveCount;
+ ASSERT(Index < FastIndex->Count);
+
+ /* Calculate the number of trailing cells */
+ MoveCount = FastIndex->Count - Index - 1;
+ if (MoveCount != 0)
+ {
+ /* Remove the cell now by moving one location ahead */
+ RtlMoveMemory(&FastIndex->List[Index],
+ &FastIndex->List[Index + 1],
+ MoveCount * sizeof(CM_INDEX));
+ }
+
+ /* Update the fast index count if asked */
+ if (UpdateCount)
+ FastIndex->Count--;
+}
+
+/**
+ * @brief
+ * Removes a cell from a normal key index.
+ *
+ * @param[in,out] KeyIndex
+ * The key index where a cell has to be removed.
+ *
+ * @param[in] Index
+ * The index which points to the location of the
+ * cell that is to be removed.
+ */
+static
+VOID
+CmpRemoveIndexKeyCell(
+ _Inout_ PCM_KEY_INDEX KeyIndex,
+ _In_ ULONG Index)
+{
+ ULONG MoveCount;
+ ASSERT(Index < KeyIndex->Count);
+
+ /* Calculate the number of trailing cells */
+ MoveCount = KeyIndex->Count - Index - 1;
+ if (MoveCount != 0)
+ {
+ /* Remove the cell now by moving one location ahead */
+ RtlMoveMemory(&KeyIndex->List[Index],
+ &KeyIndex->List[Index + 1],
+ MoveCount * sizeof(HCELL_INDEX));
+ }
+
+ /* Update the key index count */
+ KeyIndex->Count--;
+}
+
+/**
+ * @brief
+ * Removes a cell from a key value list node.
+ *
+ * @param[in,out] ValueListNode
+ * The value list node which is used by the
+ * function to update the value list count.
+ *
+ * @param[in,out] ValueListData
+ * The value list data of which a cell has to be removed.
+ *
+ * @param[in] Index
+ * The index which points to the location of the
+ * cell that is to be removed.
+ */
+static
+VOID
+CmpRemoveValueFromValueList(
+ _Inout_ PCM_KEY_NODE ValueListNode,
+ _Inout_ PCELL_DATA ValueListData,
+ _In_ ULONG Index)
+{
+ ULONG MoveCount;
+ ASSERT(Index < ValueListNode->ValueList.Count);
+
+ /* Calculate the number of trailing values */
+ MoveCount = ValueListNode->ValueList.Count - Index - 1;
+ if (MoveCount != 0)
+ {
+ /* Remove the value now by moving one location ahead */
+ RtlMoveMemory(&ValueListData->u.KeyList[Index],
+ &ValueListData->u.KeyList[Index + 1],
+ MoveCount * sizeof(HCELL_INDEX));
+ }
+
+ /* Update the value list count */
+ ValueListNode->ValueList.Count--;
+}
+
+/**
+ * @brief
+ * Removes the offending subkey from a root index.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] RootIndex
+ * The root index where a leaf is obtained from. Such
+ * leaf is used to check deep down the leaf for the offending
+ * subkey.
+ *
+ * @param[in] TargetKey
+ * The offending target subkey to be removed.
+ *
+ * @return
+ * Returns TRUE if the function successfully removed the target
+ * key, FALSE otherwise.
+ */
+static
+BOOLEAN
+CmpRemoveSubkeyInRoot(
+ _In_ PHHIVE Hive,
+ _In_ PCM_KEY_INDEX RootIndex,
+ _In_ HCELL_INDEX TargetKey)
+{
+ PCM_KEY_INDEX Leaf;
+ PCM_KEY_FAST_INDEX FastIndex;
+ HCELL_INDEX LeafCell;
+ ULONG RootCountIndex;
+ ULONG LeafCountIndex;
+
+ PAGED_CODE();
+
+ ASSERT(RootIndex);
+
+ /* Loop the root index */
+ for (RootCountIndex = 0; RootCountIndex < RootIndex->Count; RootCountIndex++)
+ {
+ /*
+ * Release the leaf cell from previous iteration
+ * of the loop. Make sure what we're releasing is
+ * valid to begin with.
+ */
+ if (RootCountIndex)
+ {
+ ASSERT(Leaf);
+ ASSERT(LeafCell == RootIndex->List[RootCountIndex - 1]);
+ HvReleaseCell(Hive, LeafCell);
+ }
+
+ /* Get the leaf cell and the leaf for this index */
+ LeafCell = RootIndex->List[RootCountIndex];
+ Leaf = (PCM_KEY_INDEX)HvGetCell(Hive, LeafCell);
+ if (!Leaf)
+ {
+ DPRINT1("Couldn't get the leaf from cell\n");
+ return FALSE;
+ }
+
+ /* Start looping the leaf */
+ for (LeafCountIndex = 0; LeafCountIndex < Leaf->Count; LeafCountIndex++)
+ {
+ /* Is the leaf a fast leaf or a hash one? */
+ if ((Leaf->Signature == CM_KEY_FAST_LEAF) ||
+ (Leaf->Signature == CM_KEY_HASH_LEAF))
+ {
+ /* It is one of the two, get the fast index */
+ FastIndex = (PCM_KEY_FAST_INDEX)Leaf;
+
+ /*
+ * Is the subkey cell from the fast
+ * index the one we one we're actually
+ * searching?
+ */
+ if (FastIndex->List[LeafCountIndex].Cell == TargetKey)
+ {
+ HvReleaseCell(Hive, LeafCell);
+ HvMarkCellDirty(Hive, LeafCell, FALSE);
+ CmpRemoveFastIndexKeyCell(FastIndex, LeafCountIndex, TRUE);
+ DPRINT1("The offending key cell has BEEN FOUND in fast index (fast index 0x%p, index %lu)\n",
+ FastIndex, LeafCountIndex);
+ return TRUE;
+ }
+ }
+ else
+ {
+ /*
+ * The leaf is neither of the two. Check if
+ * the target offending cell is inside the leaf
+ * itself.
+ */
+ if (Leaf->List[LeafCountIndex] == TargetKey)
+ {
+ HvReleaseCell(Hive, LeafCell);
+ HvMarkCellDirty(Hive, LeafCell, FALSE);
+ CmpRemoveIndexKeyCell(Leaf, LeafCountIndex);
+ DPRINT1("The offending key cell has BEEN FOUND in leaf (leaf 0x%p, index %lu)\n",
+ Leaf, LeafCountIndex);
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ /*
+ * We have searched everywhere but we couldn't
+ * hunt the offending target key cell.
+ */
+ DPRINT1("No target key has been found to remove\n");
+ return FALSE;
+}
+
+/**
+ * @brief
+ * Removes the offending subkey from a leaf index.
+ *
+ * @param[in] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] KeyNode
+ * A pointer to a key node of the parent. This node is
+ * used by the function to mark the whole subkeys list
+ * of the parent dirty.
+ *
+ * @param[in] Leaf
+ * A pointer to a leaf key index of which the offending
+ * subkey is to be removed from.
+ *
+ * @param[in] TargetKey
+ * The offending target subkey to remove.
+ *
+ * @return
+ * Returns TRUE if the function successfully removed the target
+ * key, FALSE otherwise.
+ */
+static
+BOOLEAN
+CmpRemoveSubKeyInLeaf(
+ _In_ PHHIVE Hive,
+ _In_ PCM_KEY_NODE KeyNode,
+ _In_ PCM_KEY_INDEX Leaf,
+ _In_ HCELL_INDEX TargetKey)
+{
+ PCM_KEY_FAST_INDEX FastIndex;
+ ULONG LeafIndex;
+
+ /* Loop the leaf index */
+ for (LeafIndex = 0; LeafIndex < Leaf->Count; LeafIndex++)
+ {
+ /*
+ * Check if the main leaf is a fast
+ * leaf or a hash one.
+ */
+ if ((Leaf->Signature == CM_KEY_FAST_LEAF) ||
+ (Leaf->Signature == CM_KEY_HASH_LEAF))
+ {
+ /* It is one of the two, get the fast index */
+ FastIndex = (PCM_KEY_FAST_INDEX)Leaf;
+
+ /*
+ * Is the subkey cell from the fast
+ * index the one we're actually
+ * searching?
+ */
+ if (FastIndex->List[LeafIndex].Cell == TargetKey)
+ {
+ HvMarkCellDirty(Hive, KeyNode->SubKeyLists[Stable], FALSE);
+ CmpRemoveFastIndexKeyCell(FastIndex, LeafIndex, FALSE);
+
+ /*
+ * Since this fast index actually came from the
+ * actual leaf index itself, just update its count
+ * rather than that of the fast index.
+ */
+ Leaf->Count--;
+ DPRINT1("The offending key cell has BEEN FOUND in fast index (fast index 0x%p, leaf index %lu)\n",
+ FastIndex, LeafIndex);
+ return TRUE;
+ }
+ }
+ else
+ {
+ /*
+ * The leaf is neither of the two. The offending
+ * cell must come directly from the normal leaf
+ * at this point.
+ */
+ if (Leaf->List[LeafIndex] == TargetKey)
+ {
+ HvMarkCellDirty(Hive, KeyNode->SubKeyLists[Stable], FALSE);
+ CmpRemoveIndexKeyCell(Leaf, LeafIndex);
+ DPRINT1("The offending key cell has BEEN FOUND in leaf (leaf 0x%p, index %lu)\n",
+ Leaf, LeafIndex);
+ return TRUE;
+ }
+ }
+ }
+
+ /*
+ * We have searched everywhere but we couldn't
+ * hunt the offending target key cell.
+ */
+ DPRINT1("No target key has been found to remove\n");
+ return FALSE;
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+/**
+ * @brief
+ * Checks if self healing is permitted by the kernel and/or
+ * bootloader. Self healing is also triggered if such a
+ * request was prompted by the user to fix a broken hive.
+ * Such a request tipically comes from a registry repair
+ * tool such as the ReactOS Check Registry Utility.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if self healing is possible, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmIsSelfHealEnabled(
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ if (FixHive)
+ return TRUE;
+
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ if (CmpSelfHeal || (CmpBootType & HBOOT_TYPE_SELF_HEAL))
+ return TRUE;
+#endif
+
+ return FALSE;
+}
+
+/**
+ * @brief
+ * Repairs the parent key from damage by removing the
+ * offending subkey cell.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] TargetKey
+ * The offending target cell to remove from the parent.
+ *
+ * @param[in] ParentKey
+ * The damaged parent key cell to heal.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the parent
+ * key, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairParentKey(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX TargetKey,
+ _In_ HCELL_INDEX ParentKey,
+ _In_ BOOLEAN FixHive)
+{
+ PCM_KEY_INDEX KeyIndex;
+ PCM_KEY_NODE KeyNode;
+ BOOLEAN ParentRepaired;
+
+ PAGED_CODE();
+
+ /* The target key must NEVER be NIL! */
+ ASSERT(TargetKey != HCELL_NIL);
+
+ /* Assume the parent hasn't been repaired yet */
+ ParentRepaired = FALSE;
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return ParentRepaired;
+ }
+
+ /* Obtain a node from the parent */
+ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, ParentKey);
+ if (!KeyNode)
+ {
+ DPRINT1("Couldn't get the parent key node\n");
+ return ParentRepaired;
+ }
+
+ /* Obtain the index as well since we got the parent node */
+ KeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, KeyNode->SubKeyLists[Stable]);
+ if (!KeyIndex)
+ {
+ DPRINT1("Couldn't get the key index from parent node\n");
+ HvReleaseCell(Hive, ParentKey);
+ return ParentRepaired;
+ }
+
+ /* Check if this is a root */
+ if (KeyIndex->Signature == CM_KEY_INDEX_ROOT)
+ {
+ /* It is, call the specific helper to discard the damaged key down the root */
+ ParentRepaired = CmpRemoveSubkeyInRoot(Hive,
+ KeyIndex,
+ TargetKey);
+ }
+ else if ((KeyIndex->Signature == CM_KEY_INDEX_LEAF) ||
+ (KeyIndex->Signature == CM_KEY_FAST_LEAF) ||
+ (KeyIndex->Signature == CM_KEY_HASH_LEAF))
+ {
+ /* Otherwise call the leaf helper */
+ ParentRepaired = CmpRemoveSubKeyInLeaf(Hive,
+ KeyNode,
+ KeyIndex,
+ TargetKey);
+ }
+ else
+ {
+ /*
+ * Generally CmCheckRegistry detects if a key index
+ * in the subkeys list is totally broken (we understand
+ * that if its signature is not root or leaf) and it will
+ * purge the whole subkeys list in such cases. With that
+ * being said, we should never reach this code path. But
+ * if for whatever reason we reach here then something
+ * is seriously wrong.
+ */
+ DPRINT1("The key index signature is invalid (KeyIndex->Signature == %lu)", KeyIndex->Signature);
+ ASSERT(FALSE);
+ }
+
+ /*
+ * If we successfully removed the offending key
+ * cell mark down the parent as dirty and punt down
+ * the subkey count as well. Mark the hive as in
+ * self heal mode as well.
+ */
+ if (ParentRepaired)
+ {
+ HvMarkCellDirty(Hive, ParentKey, FALSE);
+ KeyNode->SubKeyCounts[Stable]--;
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ DPRINT1("The subkey has been removed, the parent is now repaired\n");
+ }
+
+ HvReleaseCell(Hive, KeyNode->SubKeyLists[Stable]);
+ HvReleaseCell(Hive, ParentKey);
+ return ParentRepaired;
+}
+
+/**
+ * @brief
+ * Repairs the parent of the node from damage due
+ * to parent cell and parent node incosistency.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in] ParentCell
+ * The sane parent cell which is used by the
+ * function for new parent node assignment.
+ *
+ * @param[in,out] CellData
+ * The cell data of the current cell of which
+ * its parent node is to be repaired.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * parent node, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairParentNode(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ HCELL_INDEX ParentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the cell where we got the actual
+ * cell data as dirty and fix the node.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.Parent = ParentCell;
+
+ /* Mark the hive as in self healing mode since we repaired it */
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the key node signature from damage
+ * due to signature corruption.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in,out] CellData
+ * The cell data of the current cell of which
+ * its signature is to be repaired.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * key node signature, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairKeyNodeSignature(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the cell where we got the actual
+ * cell data as dirty and fix the key signature.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.Signature = CM_KEY_NODE_SIGNATURE;
+
+ /* Mark the hive as in self healing mode since we repaired it */
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the class from damage due to class
+ * corruption within the node key.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in,out] CellData
+ * The cell data of the current cell of which
+ * its class is to be repaired.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * class of node key, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairClassOfNodeKey(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the cell where we got the actual
+ * cell data as dirty and fix the class field
+ * of key node.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.Class = HCELL_NIL;
+ CellData->u.KeyNode.ClassLength = 0;
+
+ /* Mark the hive as in self healing mode since we repaired it */
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the value list count of key due to
+ * corruption. The process involves by removing
+ * one damaged value less from the list.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in] ListCountIndex
+ * The value count index which points to the actual
+ * value in the list to be removed.
+ *
+ * @param[in,out] ValueListData
+ * The value list cell data containing the actual list
+ * of which the damaged is to be removed from.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * value list count, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairValueListCount(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG ListCountIndex,
+ _Inout_ PCELL_DATA ValueListData,
+ _In_ BOOLEAN FixHive)
+{
+ PCM_KEY_NODE ValueListNode;
+
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Obtain a node from the cell that we mark it as dirty.
+ * The node is that of the current cell of which its
+ * value list is being validated.
+ */
+ ValueListNode = (PCM_KEY_NODE)HvGetCell(Hive, CurrentCell);
+ if (!ValueListNode)
+ {
+ DPRINT1("Could not get a node from the current cell\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the current cell and value list as dirty
+ * as we will be making changes onto them.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ HvMarkCellDirty(Hive, ValueListNode->ValueList.List, FALSE);
+
+ /*
+ * Now remove the value from the list and mark the
+ * hive as in self healing mode.
+ */
+ CmpRemoveValueFromValueList(ValueListNode, ValueListData, ListCountIndex);
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ HvReleaseCell(Hive, CurrentCell);
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the value list due to corruption. The
+ * process involes by purging the whole damaged
+ * list.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * value list, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairValueList(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ BOOLEAN FixHive)
+{
+ PCM_KEY_NODE ValueListNode;
+
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /* Obtain a node */
+ ValueListNode = (PCM_KEY_NODE)HvGetCell(Hive, CurrentCell);
+ if (!ValueListNode)
+ {
+ DPRINT1("Could not get a node from the current cell\n");
+ return FALSE;
+ }
+
+ /* Purge out the whole list */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ ValueListNode->ValueList.List = HCELL_NIL;
+ ValueListNode->ValueList.Count = 0;
+
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ HvReleaseCell(Hive, CurrentCell);
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the subkey list count due to corruption.
+ * The process involves by fixing the count itself
+ * with a sane count.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in] Count
+ * The healthy count which is used by the function
+ * to fix the subkeys list count.
+ *
+ * @param[in,out] CellData
+ * The cell data of the current cell of which its
+ * subkeys list is to be fixed.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * subkeys list count, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairSubKeyCounts(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG Count,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the cell where we got the actual
+ * cell data as dirty and fix the subkey
+ * counts.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.SubKeyCounts[Stable] = Count;
+
+ /* Mark the hive as in self healing mode since we repaired it */
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ return TRUE;
+}
+
+/**
+ * @brief
+ * Repairs the subkey list due to corruption. The process
+ * involves by purging the whole damaged subkeys list.
+ *
+ * @param[in,out] Hive
+ * A pointer to a hive descriptor containing faulty data.
+ *
+ * @param[in] CurrentCell
+ * The current cell to be marked as dirty.
+ *
+ * @param[in,out] CellData
+ * The cell data of the current cell of which its
+ * subkeys list is to be fixed.
+ *
+ * @param[in] FixHive
+ * If set to TRUE, self heal is triggered and the target
+ * hive will be fixed. Otherwise the hive will not be fixed.
+ *
+ * @return
+ * Returns TRUE if the function successfully healed the
+ * subkeys list, FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+CmpRepairSubKeyList(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive)
+{
+ PAGED_CODE();
+
+ /* Is self healing possible? */
+ if (!CmIsSelfHealEnabled(FixHive))
+ {
+ DPRINT1("Self healing not possible\n");
+ return FALSE;
+ }
+
+ /*
+ * Mark the cell where we got the actual
+ * cell data as dirty and fix the subkey
+ * list.
+ */
+ HvMarkCellDirty(Hive, CurrentCell, FALSE);
+ CellData->u.KeyNode.SubKeyLists[Stable] = HCELL_NIL;
+ CellData->u.KeyNode.SubKeyCounts[Stable] = 0;
+
+ /* Mark the hive as in self healing mode since we repaired it */
+ Hive->BaseBlock->BootType |= HBOOT_TYPE_SELF_HEAL;
+ return TRUE;
+}
+
+/* EOF */
diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h
index 4f02daf32e5..65f66f581b0 100644
--- a/sdk/lib/cmlib/cmlib.h
+++ b/sdk/lib/cmlib/cmlib.h
@@ -482,6 +482,79 @@ ULONG CMAPI
HvpHiveHeaderChecksum(
PHBASE_BLOCK HiveHeader);
+//
+// Registry Self-Heal Routines
+//
+BOOLEAN
+CMAPI
+CmIsSelfHealEnabled(
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairParentKey(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX TargetKey,
+ _In_ HCELL_INDEX ParentKey,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairParentNode(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX DirtyCell,
+ _In_ HCELL_INDEX ParentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairKeyNodeSignature(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX DirtyCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairClassOfNodeKey(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX DirtyCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairValueList(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairValueListCount(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG ListCountIndex,
+ _Inout_ PCELL_DATA ValueListData,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairSubKeyCounts(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _In_ ULONG Count,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive);
+
+BOOLEAN
+CMAPI
+CmpRepairSubKeyList(
+ _Inout_ PHHIVE Hive,
+ _In_ HCELL_INDEX CurrentCell,
+ _Inout_ PCELL_DATA CellData,
+ _In_ BOOLEAN FixHive);
/* Old-style Public "Cmlib" functions */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=bfcb28787d635fd2765c7…
commit bfcb28787d635fd2765c7d4b3ea8de250142f48d
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 19:00:08 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:26 2023 +0100
[NTOS:CM] Disable hard errors when setting up a new size for a hive file / annotate CmpFileSetSize parameters with SAL
During a I/O failure of whatever kind the upper-level driver, namely a FSD, can raise a hard error and a deadlock can occur. We wouldn't want that to happen for particular files like hives or logs so in such cases we must disable hard errors before toying with hives until we're done.
In addition to that, annotate the CmpFileSetSize function's parameters with SAL.
---
ntoskrnl/config/cmwraprs.c | 37 ++++++++++++++++++++++++++++++-------
ntoskrnl/include/internal/cm.h | 8 ++++----
2 files changed, 34 insertions(+), 11 deletions(-)
diff --git a/ntoskrnl/config/cmwraprs.c b/ntoskrnl/config/cmwraprs.c
index b23092e0cd9..f75a52fbcb2 100644
--- a/ntoskrnl/config/cmwraprs.c
+++ b/ntoskrnl/config/cmwraprs.c
@@ -129,21 +129,32 @@ CmpFileWrite(IN PHHIVE RegistryHive,
BOOLEAN
NTAPI
-CmpFileSetSize(IN PHHIVE RegistryHive,
- IN ULONG FileType,
- IN ULONG FileSize,
- IN ULONG OldFileSize)
+CmpFileSetSize(
+ _In_ PHHIVE RegistryHive,
+ _In_ ULONG FileType,
+ _In_ ULONG FileSize,
+ _In_ ULONG OldFileSize)
{
PCMHIVE CmHive = (PCMHIVE)RegistryHive;
HANDLE HiveHandle = CmHive->FileHandles[FileType];
FILE_END_OF_FILE_INFORMATION EndOfFileInfo;
FILE_ALLOCATION_INFORMATION FileAllocationInfo;
IO_STATUS_BLOCK IoStatusBlock;
+ BOOLEAN HardErrors;
NTSTATUS Status;
/* Just return success if no file is associated with this hive */
if (HiveHandle == NULL)
+ {
+ DPRINT1("No hive handle associated with the given hive\n");
return TRUE;
+ }
+
+ /*
+ * Disable hard errors so that we don't deadlock
+ * when touching with the hive files.
+ */
+ HardErrors = IoSetThreadHardErrorMode(FALSE);
EndOfFileInfo.EndOfFile.QuadPart = FileSize;
Status = ZwSetInformationFile(HiveHandle,
@@ -151,7 +162,12 @@ CmpFileSetSize(IN PHHIVE RegistryHive,
&EndOfFileInfo,
sizeof(FILE_END_OF_FILE_INFORMATION),
FileEndOfFileInformation);
- if (!NT_SUCCESS(Status)) return FALSE;
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ZwSetInformationFile failed to set new size of end of file (Status 0x%lx)\n", Status);
+ IoSetThreadHardErrorMode(HardErrors);
+ return FALSE;
+ }
FileAllocationInfo.AllocationSize.QuadPart = FileSize;
Status = ZwSetInformationFile(HiveHandle,
@@ -159,8 +175,15 @@ CmpFileSetSize(IN PHHIVE RegistryHive,
&FileAllocationInfo,
sizeof(FILE_ALLOCATION_INFORMATION),
FileAllocationInformation);
- if (!NT_SUCCESS(Status)) return FALSE;
-
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ZwSetInformationFile failed to set new of allocation file (Status 0x%lx)\n", Status);
+ IoSetThreadHardErrorMode(HardErrors);
+ return FALSE;
+ }
+
+ /* Reset the hard errors back */
+ IoSetThreadHardErrorMode(HardErrors);
return TRUE;
}
diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h
index e8fee77faf4..5c2f352f753 100644
--- a/ntoskrnl/include/internal/cm.h
+++ b/ntoskrnl/include/internal/cm.h
@@ -1236,10 +1236,10 @@ CmpFileWrite(
BOOLEAN
NTAPI
CmpFileSetSize(
- IN PHHIVE RegistryHive,
- IN ULONG FileType,
- IN ULONG FileSize,
- IN ULONG OldFileSize
+ _In_ PHHIVE RegistryHive,
+ _In_ ULONG FileType,
+ _In_ ULONG FileSize,
+ _In_ ULONG OldFileSize
);
BOOLEAN
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=0d776beac91f215a324d0…
commit 0d776beac91f215a324d069477090cb9708dfcd7
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Oct 26 18:51:09 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:26 2023 +0100
[NTOS:CM] Ignore syncing/flushing requests after registry shutdown
When shutting down the registry of the system we don't want that the registry in question gets poked again, such as flushing the hives or syncing the hives and respective logs for example. The reasoning behind this is very simple, during a complete shutdown the system does final check-ups and stuff until the computer
shuts down.
Any writing operations done to the registry can lead to erratic behaviors. CmShutdownSystem call already invokes a final flushing of all the hives on the backing storage which is more than enough to ensure consistency of the last session configuration. So after that final flushing, mark HvShutdownComplete as TRUE indicating
that any eventual flushing or syncying (in the case where HvSyncHive gets called) request is outright ignored.
---
ntoskrnl/config/cmsysini.c | 10 +++++++++-
ntoskrnl/include/internal/cm.h | 1 +
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/ntoskrnl/config/cmsysini.c b/ntoskrnl/config/cmsysini.c
index 32450c366a6..0e3a647de66 100644
--- a/ntoskrnl/config/cmsysini.c
+++ b/ntoskrnl/config/cmsysini.c
@@ -21,7 +21,6 @@ LIST_ENTRY CmpSelfHealQueueListHead;
KEVENT CmpLoadWorkerEvent;
LONG CmpLoadWorkerIncrement;
PEPROCESS CmpSystemProcess;
-BOOLEAN HvShutdownComplete;
PVOID CmpRegistryLockCallerCaller, CmpRegistryLockCaller;
BOOLEAN CmpFlushOnLockRelease;
BOOLEAN CmpSpecialBootCondition;
@@ -30,6 +29,7 @@ BOOLEAN CmpWasSetupBoot;
BOOLEAN CmpProfileLoaded;
BOOLEAN CmpNoVolatileCreates;
ULONG CmpTraceLevel = 0;
+BOOLEAN HvShutdownComplete = FALSE;
extern LONG CmpFlushStarveWriters;
extern BOOLEAN CmFirstTime;
@@ -2049,6 +2049,14 @@ CmShutdownSystem(VOID)
ListEntry = ListEntry->Flink;
}
+ /*
+ * As we flushed all the hives on the disk,
+ * tell the system we do not want any further
+ * registry flushing or syncing at this point
+ * since we are shutting down the registry anyway.
+ */
+ HvShutdownComplete = TRUE;
+
CmpUnlockRegistry();
}
diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h
index db702bd8b28..e8fee77faf4 100644
--- a/ntoskrnl/include/internal/cm.h
+++ b/ntoskrnl/include/internal/cm.h
@@ -1473,6 +1473,7 @@ extern BOOLEAN CmpProfileLoaded;
extern PCMHIVE CmiVolatileHive;
extern LIST_ENTRY CmiKeyObjectListHead;
extern BOOLEAN CmpHoldLazyFlush;
+extern BOOLEAN HvShutdownComplete;
//
// Inlined functions