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