[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.
This commit is contained in:
George Bișoc 2023-10-30 18:07:21 +01:00
parent 279f8f8864
commit f3141fb29e
No known key found for this signature in database
GPG key ID: 688C4FBE25D7DEF6
8 changed files with 239 additions and 39 deletions

View file

@ -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(

View file

@ -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

View file

@ -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;

View file

@ -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@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 */