[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

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

View file

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

View file

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

View file

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

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