https://git.reactos.org/?p=reactos.git;a=commitdiff;h=f3141fb29ebc26bcf1771…
commit f3141fb29ebc26bcf177163edd48ae0408bb381b
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Mon Oct 30 18:07:21 2023 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:29 2023 +0100
[NTOS:CM] Implement support for alternate registry hives
Sometimes repairing a broken hive with a hive log does not always guarantee the hive
in question has fully recovered. In worst cases it could happen the LOG itself is even
corrupt too and that would certainly lead to a total unbootable system. This is most likely
if the victim hive is the SYSTEM hive.
This can be anyhow solved by the help of a mirror hive, or also called an "alternate hive".
Alternate hives serve the purpose as backup hives for primary hives of which there is still
a risk that is not worth taking. For now only the SYSTEM hive is granted the right to have
a backup alternate hive.
=== NOTE ===
Currently the SYSTEM hive can only base upon the alternate SYSTEM.ALT hive, which means the
corresponding LOG file never gets updated. When time comes the existing code must be adapted
to allow the possibility to use .ALT and .LOG hives simultaneously.
---
ntoskrnl/config/cmapi.c | 2 +
ntoskrnl/config/cminit.c | 29 ++++++----
ntoskrnl/config/cmsysini.c | 119 ++++++++++++++++++++++++++++++++++++++---
ntoskrnl/include/internal/cm.h | 21 ++++----
sdk/lib/cmlib/cmlib.h | 9 ++++
sdk/lib/cmlib/hivedata.h | 4 +-
sdk/lib/cmlib/hiveinit.c | 13 +++++
sdk/lib/cmlib/hivewrt.c | 81 ++++++++++++++++++++++++----
8 files changed, 239 insertions(+), 39 deletions(-)
diff --git a/ntoskrnl/config/cmapi.c b/ntoskrnl/config/cmapi.c
index 287caecf245..8e3e3edd69a 100644
--- a/ntoskrnl/config/cmapi.c
+++ b/ntoskrnl/config/cmapi.c
@@ -2703,6 +2703,7 @@ CmSaveKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
NULL,
NULL,
NULL,
+ NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status)) goto Cleanup;
@@ -2795,6 +2796,7 @@ CmSaveMergedKeys(IN PCM_KEY_CONTROL_BLOCK HighKcb,
NULL,
NULL,
NULL,
+ NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
goto done;
diff --git a/ntoskrnl/config/cminit.c b/ntoskrnl/config/cminit.c
index 3406bfea302..15dc4f0d77c 100644
--- a/ntoskrnl/config/cminit.c
+++ b/ntoskrnl/config/cminit.c
@@ -16,16 +16,18 @@
NTSTATUS
NTAPI
-CmpInitializeHive(OUT PCMHIVE *CmHive,
- IN ULONG OperationType,
- IN ULONG HiveFlags,
- IN ULONG FileType,
- IN PVOID HiveData OPTIONAL,
- IN HANDLE Primary,
- IN HANDLE Log,
- IN HANDLE External,
- IN PCUNICODE_STRING FileName OPTIONAL,
- IN ULONG CheckFlags)
+CmpInitializeHive(
+ _Out_ PCMHIVE *CmHive,
+ _In_ ULONG OperationType,
+ _In_ ULONG HiveFlags,
+ _In_ ULONG FileType,
+ _In_opt_ PVOID HiveData,
+ _In_ HANDLE Primary,
+ _In_ HANDLE Log,
+ _In_ HANDLE External,
+ _In_ HANDLE Alternate,
+ _In_opt_ PCUNICODE_STRING FileName,
+ _In_ ULONG CheckFlags)
{
PCMHIVE Hive;
IO_STATUS_BLOCK IoStatusBlock;
@@ -44,13 +46,17 @@ CmpInitializeHive(OUT PCMHIVE *CmHive,
* unless this hive is a shared system hive.
* - An in-memory initialization without hive data.
* - A log hive that is not linked to a correct file type.
+ * - An alternate hive that is not linked to a correct file type.
+ * - A lonely alternate hive not backed up with its corresponding primary hive.
*/
if (((External) && ((Primary) || (Log))) ||
((Log) && !(Primary)) ||
(!(CmpShareSystemHives) && (HiveFlags & HIVE_VOLATILE) &&
((Primary) || (External) || (Log))) ||
((OperationType == HINIT_MEMORY) && (!HiveData)) ||
- ((Log) && (FileType != HFILE_TYPE_LOG)))
+ ((Log) && (FileType != HFILE_TYPE_LOG)) ||
+ ((Alternate) && (FileType != HFILE_TYPE_ALTERNATE)) ||
+ ((Alternate) && !(Primary)))
{
/* Fail the request */
return STATUS_INVALID_PARAMETER;
@@ -140,6 +146,7 @@ CmpInitializeHive(OUT PCMHIVE *CmHive,
Hive->FileHandles[HFILE_TYPE_PRIMARY] = Primary;
Hive->FileHandles[HFILE_TYPE_LOG] = Log;
Hive->FileHandles[HFILE_TYPE_EXTERNAL] = External;
+ Hive->FileHandles[HFILE_TYPE_ALTERNATE] = Alternate;
/* Initailize the guarded mutex */
KeInitializeGuardedMutex(Hive->ViewLock);
diff --git a/ntoskrnl/config/cmsysini.c b/ntoskrnl/config/cmsysini.c
index d74fffe07da..d28a5b5d20d 100644
--- a/ntoskrnl/config/cmsysini.c
+++ b/ntoskrnl/config/cmsysini.c
@@ -359,6 +359,7 @@ CmpInitHiveFromFile(IN PCUNICODE_STRING HiveName,
FileHandle,
LogHandle,
NULL,
+ NULL,
HiveName,
CheckFlags);
if (!NT_SUCCESS(Status))
@@ -906,11 +907,12 @@ CmpInitializeSystemHive(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
Status = CmpInitializeHive(&SystemHive,
HiveBase ? HINIT_MEMORY : HINIT_CREATE,
HIVE_NOLAZYFLUSH,
- HFILE_TYPE_LOG,
+ HFILE_TYPE_ALTERNATE,
HiveBase,
NULL,
NULL,
NULL,
+ NULL,
&HiveName,
HiveBase ? CM_CHECK_REGISTRY_PURGE_VOLATILES : CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
@@ -1181,6 +1183,76 @@ CmpGetRegistryPath(VOID)
return ConfigPath;
}
+/**
+ * @brief
+ * Checks if the primary and alternate backing hive are
+ * the same, by determining the time stamp of both hives.
+ *
+ * @param[in] FileName
+ * A pointer to a string containing the file name of the
+ * primary hive.
+ *
+ * @param[in] CmMainmHive
+ * A pointer to a CM hive descriptor associated with the
+ * primary hive.
+ *
+ * @param[in] AlternateHandle
+ * A handle to a file that represents the alternate hive.
+ *
+ * @param[in] Diverged
+ * A pointer to a boolean value, if both hives are the same
+ * it returns TRUE. Otherwise it returns FALSE.
+ */
+static
+VOID
+CmpHasAlternateHiveDiverged(
+ _In_ PCUNICODE_STRING FileName,
+ _In_ PCMHIVE CmMainmHive,
+ _In_ HANDLE AlternateHandle,
+ _Out_ PBOOLEAN Diverged)
+{
+ PHHIVE Hive, AlternateHive;
+ NTSTATUS Status;
+ PCMHIVE CmiAlternateHive;
+
+ /* Assume it has not diverged */
+ *Diverged = FALSE;
+
+ /* Initialize the SYSTEM alternate hive */
+ Status = CmpInitializeHive(&CmiAlternateHive,
+ HINIT_FILE,
+ 0,
+ HFILE_TYPE_PRIMARY,
+ NULL,
+ AlternateHandle,
+ NULL,
+ NULL,
+ NULL,
+ FileName,
+ CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
+ if (!NT_SUCCESS(Status))
+ {
+ /* Assume it has diverged... */
+ DPRINT1("Failed to initialize the alternate hive to check for diversion (Status 0x%lx)\n", Status);
+ *Diverged = TRUE;
+ return;
+ }
+
+ /*
+ * Check the timestamp of both hives. If they do not match they
+ * have diverged, the kernel has to synchronize the both hives.
+ */
+ Hive = &CmMainmHive->Hive;
+ AlternateHive = &CmiAlternateHive->Hive;
+ if (AlternateHive->BaseBlock->TimeStamp.QuadPart !=
+ Hive->BaseBlock->TimeStamp.QuadPart)
+ {
+ *Diverged = TRUE;
+ }
+
+ CmpDestroyHive(CmiAlternateHive);
+}
+
_Function_class_(KSTART_ROUTINE)
VOID
NTAPI
@@ -1193,9 +1265,10 @@ CmpLoadHiveThread(IN PVOID StartContext)
USHORT FileStart;
ULONG PrimaryDisposition, SecondaryDisposition, ClusterSize;
PCMHIVE CmHive;
- HANDLE PrimaryHandle = NULL, LogHandle = NULL;
+ HANDLE PrimaryHandle = NULL, AlternateHandle = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PVOID ErrorParameters;
+ BOOLEAN HasDiverged;
PAGED_CODE();
/* Get the hive index, make sure it makes sense */
@@ -1274,18 +1347,18 @@ CmpLoadHiveThread(IN PVOID StartContext)
{
/* It's now, open the hive file and log */
Status = CmpOpenHiveFiles(&FileName,
- L".LOG",
+ L".ALT",
&PrimaryHandle,
- &LogHandle,
+ &AlternateHandle,
&PrimaryDisposition,
&SecondaryDisposition,
TRUE,
TRUE,
FALSE,
&ClusterSize);
- if (!(NT_SUCCESS(Status)) || !(LogHandle))
+ if (!(NT_SUCCESS(Status)) || !(AlternateHandle))
{
- /* Couldn't open the hive or its log file, raise a hard error */
+ /* Couldn't open the hive or its alternate file, raise a hard error */
ErrorParameters = &FileName;
NtRaiseHardError(STATUS_CANNOT_LOAD_REGISTRY_FILE,
1,
@@ -1299,7 +1372,14 @@ CmpLoadHiveThread(IN PVOID StartContext)
}
/* Save the file handles. This should remove our sync hacks */
- CmHive->FileHandles[HFILE_TYPE_LOG] = LogHandle;
+ /*
+ * FIXME: Any hive that relies on the alternate hive for recovery purposes
+ * will only get an alternate hive. As a result, the LOG file would never
+ * get synced each time a write is done to the hive. In the future it would
+ * be best to adapt the code so that a primary hive can use a LOG and ALT
+ * hives at the same time.
+ */
+ CmHive->FileHandles[HFILE_TYPE_ALTERNATE] = AlternateHandle;
CmHive->FileHandles[HFILE_TYPE_PRIMARY] = PrimaryHandle;
/* Allow lazy flushing since the handles are there -- remove sync hacks */
@@ -1332,6 +1412,28 @@ CmpLoadHiveThread(IN PVOID StartContext)
CmHive->Hive.DirtyCount = CmHive->Hive.DirtyVector.SizeOfBitMap;
HvSyncHive((PHHIVE)CmHive);
}
+ else
+ {
+ /*
+ * Check whether the both primary and alternate hives are the same,
+ * or that the primary or alternate were created for the first time.
+ * Do a write against the alternate hive in these cases.
+ */
+ CmpHasAlternateHiveDiverged(&FileName,
+ CmHive,
+ AlternateHandle,
+ &HasDiverged);
+ if (HasDiverged ||
+ PrimaryDisposition == FILE_CREATED ||
+ SecondaryDisposition == FILE_CREATED)
+ {
+ if (!HvWriteAlternateHive((PHHIVE)CmHive))
+ {
+ DPRINT1("Failed to write to alternate hive\n");
+ goto Exit;
+ }
+ }
+ }
/* Finally, set our allocated hive to the same hive we've had */
CmpMachineHiveList[i].CmHive2 = CmHive;
@@ -1339,6 +1441,7 @@ CmpLoadHiveThread(IN PVOID StartContext)
}
}
+Exit:
/* We're done */
CmpMachineHiveList[i].ThreadFinished = TRUE;
@@ -1571,6 +1674,7 @@ CmInitSystem1(VOID)
NULL,
NULL,
NULL,
+ NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
{
@@ -1662,6 +1766,7 @@ CmInitSystem1(VOID)
NULL,
NULL,
NULL,
+ NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
{
diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h
index 04869a812bc..7657fc77fb9 100644
--- a/ntoskrnl/include/internal/cm.h
+++ b/ntoskrnl/include/internal/cm.h
@@ -747,16 +747,17 @@ CmpQueryKeyName(
NTSTATUS
NTAPI
CmpInitializeHive(
- OUT PCMHIVE *CmHive,
- IN ULONG OperationType,
- IN ULONG HiveFlags,
- IN ULONG FileType,
- IN PVOID HiveData OPTIONAL,
- IN HANDLE Primary,
- IN HANDLE Log,
- IN HANDLE External,
- IN PCUNICODE_STRING FileName OPTIONAL,
- IN ULONG CheckFlags
+ _Out_ PCMHIVE *CmHive,
+ _In_ ULONG OperationType,
+ _In_ ULONG HiveFlags,
+ _In_ ULONG FileType,
+ _In_opt_ PVOID HiveData,
+ _In_ HANDLE Primary,
+ _In_ HANDLE Log,
+ _In_ HANDLE External,
+ _In_ HANDLE Alternate,
+ _In_opt_ PCUNICODE_STRING FileName,
+ _In_ ULONG CheckFlags
);
NTSTATUS
diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h
index fc73e5a58ac..aee69f58460 100644
--- a/sdk/lib/cmlib/cmlib.h
+++ b/sdk/lib/cmlib/cmlib.h
@@ -120,6 +120,10 @@
IN ULONG StartingIndex,
IN ULONG NumberToSet);
+ VOID NTAPI
+ RtlSetAllBits(
+ IN PRTL_BITMAP BitMapHeader);
+
VOID NTAPI
RtlClearAllBits(
IN PRTL_BITMAP BitMapHeader);
@@ -509,6 +513,11 @@ BOOLEAN CMAPI
HvWriteHive(
PHHIVE RegistryHive);
+BOOLEAN
+CMAPI
+HvWriteAlternateHive(
+ _In_ PHHIVE RegistryHive);
+
BOOLEAN
CMAPI
HvSyncHiveFromRecover(
diff --git a/sdk/lib/cmlib/hivedata.h b/sdk/lib/cmlib/hivedata.h
index 16f96de6b4f..83c9d53a5cd 100644
--- a/sdk/lib/cmlib/hivedata.h
+++ b/sdk/lib/cmlib/hivedata.h
@@ -33,7 +33,8 @@
#define HFILE_TYPE_PRIMARY 0
#define HFILE_TYPE_LOG 1
#define HFILE_TYPE_EXTERNAL 2
-#define HFILE_TYPE_MAX 3
+#define HFILE_TYPE_ALTERNATE 3 // Technically a HFILE_TYPE_PRIMARY but for mirror backup hives. ONLY USED for the SYSTEM hive!
+#define HFILE_TYPE_MAX 4
//
// Hive sizes
@@ -334,6 +335,7 @@ typedef struct _HHIVE
BOOLEAN ReadOnly;
#if (NTDDI_VERSION < NTDDI_VISTA) // NTDDI_LONGHORN
BOOLEAN Log;
+ BOOLEAN Alternate;
#endif
BOOLEAN DirtyFlag;
#if (NTDDI_VERSION >= NTDDI_VISTA) // NTDDI_LONGHORN
diff --git a/sdk/lib/cmlib/hiveinit.c b/sdk/lib/cmlib/hiveinit.c
index a89a5050c21..d79c9dadad3 100644
--- a/sdk/lib/cmlib/hiveinit.c
+++ b/sdk/lib/cmlib/hiveinit.c
@@ -441,6 +441,18 @@ HvpInitializeMemoryHive(
RtlInitializeBitMap(&Hive->DirtyVector, BitmapBuffer, BitmapSize * 8);
RtlClearAllBits(&Hive->DirtyVector);
+ /*
+ * Mark the entire hive as dirty. Indeed we understand if we charged up
+ * the alternate variant of the primary hive (e.g. SYSTEM.ALT) because
+ * FreeLdr could not load the main SYSTEM hive, due to corruptions, and
+ * repairing it with a LOG did not help at all.
+ */
+ if (ChunkBase->BootRecover == HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE)
+ {
+ RtlSetAllBits(&Hive->DirtyVector);
+ Hive->DirtyCount = Hive->DirtyVector.SizeOfBitMap;
+ }
+
HvpInitFileName(Hive->BaseBlock, FileName);
return STATUS_SUCCESS;
@@ -1377,6 +1389,7 @@ HvInitialize(
Hive->Version = HSYS_MINOR;
#if (NTDDI_VERSION < NTDDI_VISTA)
Hive->Log = (FileType == HFILE_TYPE_LOG);
+ Hive->Alternate = (FileType == HFILE_TYPE_ALTERNATE);
#endif
Hive->HiveFlags = HiveFlags & ~HIVE_NOLAZYFLUSH;
diff --git a/sdk/lib/cmlib/hivewrt.c b/sdk/lib/cmlib/hivewrt.c
index 4fea5fa70e6..d95e8559196 100644
--- a/sdk/lib/cmlib/hivewrt.c
+++ b/sdk/lib/cmlib/hivewrt.c
@@ -1,7 +1,7 @@
/*
* 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
+ * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log/Alternate Writing
* COPYRIGHT: Copyright 2001 - 2005 Eric Kohl
* Copyright 2005 Filip Navara <navaraf(a)reactos.org>
* Copyright 2021 Max Korostil
@@ -297,16 +297,26 @@ HvpWriteLog(
* data to be written to the primary hive, otherwise if
* it's set to FALSE then the function writes all the data.
*
+ * @param[in] FileType
+ * The file type of a registry hive. This can be HFILE_TYPE_PRIMARY
+ * or HFILE_TYPE_ALTERNATE.
+ *
* @return
* Returns TRUE if writing to hive has succeeded,
* FALSE otherwise.
+ *
+ * @remarks
+ * The on-disk header metadata of a hive is already written with type
+ * of HFILE_TYPE_PRIMARY, regardless of what file type the caller submits,
+ * as an alternate hive is basically a mirror of the primary hive.
*/
static
BOOLEAN
CMAPI
HvpWriteHive(
_In_ PHHIVE RegistryHive,
- _In_ BOOLEAN OnlyDirty)
+ _In_ BOOLEAN OnlyDirty,
+ _In_ ULONG FileType)
{
BOOLEAN Success;
ULONG FileOffset;
@@ -348,7 +358,7 @@ HvpWriteHive(
/* Write hive block */
FileOffset = 0;
- Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
+ Success = RegistryHive->FileWrite(RegistryHive, FileType,
&FileOffset, RegistryHive->BaseBlock,
sizeof(HBASE_BLOCK));
if (!Success)
@@ -384,7 +394,7 @@ HvpWriteHive(
FileOffset = (BlockIndex + 1) * HBLOCK_SIZE;
/* Now write this block to primary hive file */
- Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
+ Success = RegistryHive->FileWrite(RegistryHive, FileType,
&FileOffset, Block, HBLOCK_SIZE);
if (!Success)
{
@@ -401,7 +411,7 @@ HvpWriteHive(
* 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);
+ Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0);
if (!Success)
{
DPRINT1("Failed to flush the primary hive\n");
@@ -420,7 +430,7 @@ HvpWriteHive(
/* Write hive block */
FileOffset = 0;
- Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
+ Success = RegistryHive->FileWrite(RegistryHive, FileType,
&FileOffset, RegistryHive->BaseBlock,
sizeof(HBASE_BLOCK));
if (!Success)
@@ -430,7 +440,7 @@ HvpWriteHive(
}
/* Flush the hive immediately */
- Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
+ Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0);
if (!Success)
{
DPRINT1("Failed to flush the primary hive\n");
@@ -526,7 +536,7 @@ HvSyncHive(
}
/* Update the primary hive file */
- if (!HvpWriteHive(RegistryHive, TRUE))
+ if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY))
{
DPRINT1("Failed to write the primary hive\n");
#if !defined(CMLIB_HOST) && !defined(_BLDR_)
@@ -535,6 +545,19 @@ HvSyncHive(
return FALSE;
}
+ /* Update the alternate hive file if present */
+ if (RegistryHive->Alternate == TRUE)
+ {
+ if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_ALTERNATE))
+ {
+ DPRINT1("Failed to write the alternate hive\n");
+#if !defined(CMLIB_HOST) && !defined(_BLDR_)
+ IoSetThreadHardErrorMode(HardErrors);
+#endif
+ return FALSE;
+ }
+ }
+
/* Clear dirty bitmap. */
RtlClearAllBits(&RegistryHive->DirtyVector);
RegistryHive->DirtyCount = 0;
@@ -601,7 +624,7 @@ HvWriteHive(
#endif
/* Update hive file */
- if (!HvpWriteHive(RegistryHive, FALSE))
+ if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_PRIMARY))
{
DPRINT1("Failed to write the hive\n");
return FALSE;
@@ -610,6 +633,44 @@ HvWriteHive(
return TRUE;
}
+/**
+ * @brief
+ * Writes data to an alternate registry hive.
+ * An alternate hive is usually backed up by a primary
+ * hive. This function is tipically used to force write
+ * data into the alternate hive if both hives no longer match.
+ *
+ * @param[in] RegistryHive
+ * A pointer to a hive descriptor where data
+ * is to be written into.
+ *
+ * @return
+ * Returns TRUE if hive writing has succeeded,
+ * FALSE otherwise.
+ */
+BOOLEAN
+CMAPI
+HvWriteAlternateHive(
+ _In_ PHHIVE RegistryHive)
+{
+ ASSERT(RegistryHive->ReadOnly == FALSE);
+ ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE);
+ ASSERT(RegistryHive->Alternate == TRUE);
+
+#if !defined(_BLDR_)
+ /* Update hive header modification time */
+ KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);
+#endif
+
+ /* Update hive file */
+ if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_ALTERNATE))
+ {
+ DPRINT1("Failed to write the alternate hive\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
/**
* @brief
@@ -634,7 +695,7 @@ HvSyncHiveFromRecover(
ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE);
/* Call the private API call to do the deed for us */
- return HvpWriteHive(RegistryHive, TRUE);
+ return HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY);
}
/* EOF */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=279f8f88644246b95521f…
commit 279f8f88644246b95521f2deb7c99badcf726f12
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Nov 9 20:40:23 2023 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:29 2023 +0100
[CMLIB] Fix the bin during hive initialization from memory if it's corrupt
As we iterate over the chunk hive data pointer for hive bins that we are going
to enlist, we might encounter one or several bins that would get corrupted
during a premature abortion of a registry writing operation such as due to
a power outage of the system, hardware malfunction, etc.
Corruption at the level of hive bins is nasty because they contain actual cell
data of registry information such as keys, values etc. Assuming a bin is corrupt
in part we can fix it by recovering some of the bin properties that, theoretically,
could be fixed -- namely the signature, size and offset.
For size and offset we are more or less safe because a bin typically has a size
of a block, and the offset is the coordinate index of where a hive bin should lay at.
---
sdk/lib/cmlib/hiveinit.c | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/sdk/lib/cmlib/hiveinit.c b/sdk/lib/cmlib/hiveinit.c
index 0b03ddf258b..a89a5050c21 100644
--- a/sdk/lib/cmlib/hiveinit.c
+++ b/sdk/lib/cmlib/hiveinit.c
@@ -368,13 +368,31 @@ HvpInitializeMemoryHive(
{
Bin = (PHBIN)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HBLOCK_SIZE);
if (Bin->Signature != HV_HBIN_SIGNATURE ||
- (Bin->Size % HBLOCK_SIZE) != 0)
+ (Bin->Size % HBLOCK_SIZE) != 0 ||
+ (Bin->FileOffset / HBLOCK_SIZE) != BlockIndex)
{
- DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x\n",
+ /*
+ * Bin is toast but luckily either the signature, size or offset
+ * is out of order. For the signature it is obvious what we are going
+ * to do, for the offset we are re-positioning the bin back to where it
+ * was and for the size we will set it up to a block size, since technically
+ * a hive bin is large as a block itself to accommodate cells.
+ */
+ if (!CmIsSelfHealEnabled(FALSE))
+ {
+ DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x. Self-heal not possible!\n",
(unsigned long)BlockIndex, (unsigned)Bin->Signature, (unsigned)Bin->Size);
- Hive->Free(Hive->Storage[Stable].BlockList, 0);
- Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
- return STATUS_REGISTRY_CORRUPT;
+ Hive->Free(Hive->Storage[Stable].BlockList, 0);
+ Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
+ return STATUS_REGISTRY_CORRUPT;
+ }
+
+ /* Fix this bin */
+ Bin->Signature = HV_HBIN_SIGNATURE;
+ Bin->Size = HBLOCK_SIZE;
+ Bin->FileOffset = BlockIndex * HBLOCK_SIZE;
+ ChunkBase->BootType |= HBOOT_TYPE_SELF_HEAL;
+ DPRINT1("Bin at index %lu is corrupt and it has been repaired!\n", (unsigned long)BlockIndex);
}
NewBin = Hive->Allocate(Bin->Size, TRUE, TAG_CM);
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=27917c14ed31ea24cf0a0…
commit 27917c14ed31ea24cf0a022200d9afd4e665009b
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sun Oct 29 20:34:54 2023 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:29 2023 +0100
[NTOS:CM] Flush the dirty data to disk if the SYSTEM hive has been recovered by FreeLdr
If FreeLdr performed recovery against the SYSTEM hive with a log, all of its data is only present in volatile memory thus dirty. So the kernel is responsible to flush all the data that's been recovered within the SYSTEM hive into the backing storage.
---
ntoskrnl/config/cmsysini.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/ntoskrnl/config/cmsysini.c b/ntoskrnl/config/cmsysini.c
index f65449d1962..d74fffe07da 100644
--- a/ntoskrnl/config/cmsysini.c
+++ b/ntoskrnl/config/cmsysini.c
@@ -1324,8 +1324,14 @@ CmpLoadHiveThread(IN PVOID StartContext)
//ASSERT(FALSE);
//}
- /* Another thing we don't support is NTLDR-recovery */
- if (CmHive->Hive.BaseBlock->BootRecover) ASSERT(FALSE);
+ /* FreeLdr has recovered the hive with a log, we must do a flush */
+ if (CmHive->Hive.BaseBlock->BootRecover == HBOOT_BOOT_RECOVERED_BY_HIVE_LOG)
+ {
+ DPRINT1("FreeLdr recovered the hive (hive 0x%p)\n", CmHive);
+ RtlSetAllBits(&CmHive->Hive.DirtyVector);
+ CmHive->Hive.DirtyCount = CmHive->Hive.DirtyVector.SizeOfBitMap;
+ HvSyncHive((PHHIVE)CmHive);
+ }
/* Finally, set our allocated hive to the same hive we've had */
CmpMachineHiveList[i].CmHive2 = CmHive;
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=fa80176a628790d07dce2…
commit fa80176a628790d07dce2c6ee238e5c8a94c31c7
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sun Oct 22 21:28:39 2023 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 19 20:44:28 2023 +0100
[FREELDR][HACK] Temporarily disable registry recovery code for AMD64
The newly implemented code for registry recovery makes the FreeLdr binary to grow
in size, to the point that it would BSOD because the PE image is too big.
For now we have to temporarily disable any of the newly added code, until
either FreeLdr is split into a basic PE bootloader image itself and a
"FreeLdrlib" that is used by the PE image to access various bootloader APIs
or another proper solution is found.
---
boot/freeldr/freeldr/ntldr/registry.c | 22 ++++++++++++++++++++++
sdk/lib/cmlib/hiveinit.c | 22 +++++++++++++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/boot/freeldr/freeldr/ntldr/registry.c b/boot/freeldr/freeldr/ntldr/registry.c
index 0b81b71cf16..73d6f68097e 100644
--- a/boot/freeldr/freeldr/ntldr/registry.c
+++ b/boot/freeldr/freeldr/ntldr/registry.c
@@ -99,7 +99,14 @@ RegInitializeHive(
_In_ BOOLEAN LoadAlternate)
{
NTSTATUS Status;
+/*
+ * FIXME: Disable compilation of some parts of code for AMD64 for now,
+ * since it makes the FreeLdr binary size so large that it prevents
+ * x64 ROS from booting.
+ */
+#if !defined(_M_AMD64)
CM_CHECK_REGISTRY_STATUS CmStatusCode;
+#endif
/* Initialize the hive */
Status = HvInitialize(GET_HHIVE(CmHive),
@@ -121,6 +128,8 @@ RegInitializeHive(
return FALSE;
}
+/* FIXME: See the comment above */
+#if !defined(_M_AMD64)
/* 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))
@@ -128,10 +137,13 @@ RegInitializeHive(
ERR("CmCheckRegistry detected problems with the loaded flat hive (check code %lu)\n", CmStatusCode);
return FALSE;
}
+#endif
return TRUE;
}
+/* FIXME: See the comment above */
+#if !defined(_M_AMD64)
/**
* @brief
* Loads and reads a hive log at specified
@@ -406,6 +418,7 @@ RegRecoverDataHive(
HiveBaseBlock->CheckSum = HvpHiveHeaderChecksum(HiveBaseBlock);
return TRUE;
}
+#endif
/**
* @brief
@@ -451,6 +464,14 @@ RegImportBinaryHive(
CmSystemHive = FrLdrTempAlloc(sizeof(CMHIVE), 'eviH');
Success = RegInitializeHive(CmSystemHive, ChunkBase, LoadAlternate);
if (!Success)
+/* FIXME: See the comment above */
+#if defined(_M_AMD64)
+ {
+ ERR("Corrupted hive %p!\n", ChunkBase);
+ FrLdrTempFree(CmSystemHive, 'eviH');
+ return FALSE;
+ }
+#else
{
/* Free the buffer and retry again */
FrLdrTempFree(CmSystemHive, 'eviH');
@@ -484,6 +505,7 @@ RegImportBinaryHive(
*/
((PHBASE_BLOCK)ChunkBase)->BootRecover = HBOOT_BOOT_RECOVERED_BY_HIVE_LOG;
}
+#endif
/* Save the root key node */
SystemHive = GET_HHIVE(CmSystemHive);
diff --git a/sdk/lib/cmlib/hiveinit.c b/sdk/lib/cmlib/hiveinit.c
index d4adeb99518..0b03ddf258b 100644
--- a/sdk/lib/cmlib/hiveinit.c
+++ b/sdk/lib/cmlib/hiveinit.c
@@ -619,6 +619,11 @@ HvpGetHiveHeader(
return HiveSuccess;
}
+/*
+ * FIXME: Disable compilation for AMD64 for now since it makes
+ * the FreeLdr binary size so large it makes booting impossible.
+ */
+#if !defined(_M_AMD64)
/**
* @brief
* Computes the hive space size by querying
@@ -960,6 +965,7 @@ HvpRecoverDataFromLog(
return HiveSuccess;
}
+#endif
/**
* @brief
@@ -998,7 +1004,12 @@ HvLoadHive(
NTSTATUS Status;
BOOLEAN Success;
PHBASE_BLOCK BaseBlock = NULL;
+/* FIXME: See the comment above (near HvpQueryHiveSize) */
+#if defined(_M_AMD64)
+ ULONG Result;
+#else
ULONG Result, Result2;
+#endif
LARGE_INTEGER TimeStamp;
ULONG Offset = 0;
PVOID HiveData;
@@ -1044,6 +1055,12 @@ HvLoadHive(
/* Hive header needs a repair */
case RecoverHeader:
+/* FIXME: See the comment above (near HvpQueryHiveSize) */
+#if defined(_M_AMD64)
+ {
+ return STATUS_REGISTRY_CORRUPT;
+ }
+#else
{
/* Check if this hive has a log at hand to begin with */
#if (NTDDI_VERSION < NTDDI_VISTA)
@@ -1092,6 +1109,7 @@ HvLoadHive(
break;
}
+#endif
}
/* Set the boot type */
@@ -1384,6 +1402,8 @@ HvInitialize(
return Status;
}
+/* FIXME: See the comment above (near HvpQueryHiveSize) */
+#if !defined(_M_AMD64)
/*
* Check if we have recovered this hive. We are responsible to
* flush the primary hive back to backing storage afterwards.
@@ -1418,7 +1438,7 @@ HvInitialize(
*/
Status = STATUS_SUCCESS;
}
-
+#endif
break;
}
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))
{