Skip to content

Commit

Permalink
[NTOS:CM] Implement support for alternate registry hives
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
GeoB99 committed Oct 31, 2023
1 parent be28a05 commit a2b41e9
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 39 deletions.
2 changes: 2 additions & 0 deletions ntoskrnl/config/cmapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
29 changes: 18 additions & 11 deletions ntoskrnl/config/cminit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
119 changes: 112 additions & 7 deletions ntoskrnl/config/cmsysini.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ CmpInitHiveFromFile(IN PCUNICODE_STRING HiveName,
FileHandle,
LogHandle,
NULL,
NULL,
HiveName,
CheckFlags);
if (!NT_SUCCESS(Status))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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 */
Expand Down Expand Up @@ -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,
Expand All @@ -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 */
Expand Down Expand Up @@ -1332,13 +1412,36 @@ 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;
ASSERT(CmpMachineHiveList[i].CmHive == CmpMachineHiveList[i].CmHive2);
}
}

Exit:
/* We're done */
CmpMachineHiveList[i].ThreadFinished = TRUE;

Expand Down Expand Up @@ -1571,6 +1674,7 @@ CmInitSystem1(VOID)
NULL,
NULL,
NULL,
NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
{
Expand Down Expand Up @@ -1662,6 +1766,7 @@ CmInitSystem1(VOID)
NULL,
NULL,
NULL,
NULL,
CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES);
if (!NT_SUCCESS(Status))
{
Expand Down
21 changes: 11 additions & 10 deletions ntoskrnl/include/internal/cm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions sdk/lib/cmlib/cmlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@
IN ULONG StartingIndex,
IN ULONG NumberToSet);

VOID NTAPI
RtlSetAllBits(
_In_ PRTL_BITMAP BitMapHeader);

VOID NTAPI
RtlClearAllBits(
IN PRTL_BITMAP BitMapHeader);
Expand Down Expand Up @@ -508,6 +512,11 @@ BOOLEAN CMAPI
HvWriteHive(
PHHIVE RegistryHive);

BOOLEAN
CMAPI
HvWriteAlternateHive(
_In_ PHHIVE RegistryHive);

BOOLEAN
CMAPI
HvSyncHiveFromRecover(
Expand Down
4 changes: 3 additions & 1 deletion sdk/lib/cmlib/hivedata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions sdk/lib/cmlib/hiveinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,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;
Expand Down Expand Up @@ -1359,6 +1371,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;

Expand Down
Loading

0 comments on commit a2b41e9

Please sign in to comment.