Reactos

[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.

+239 -39
+2
ntoskrnl/config/cmapi.c
··· 2703 2703 NULL, 2704 2704 NULL, 2705 2705 NULL, 2706 + NULL, 2706 2707 CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES); 2707 2708 if (!NT_SUCCESS(Status)) goto Cleanup; 2708 2709 ··· 2790 2791 HINIT_CREATE, 2791 2792 HIVE_VOLATILE, 2792 2793 HFILE_TYPE_PRIMARY, 2794 + NULL, 2793 2795 NULL, 2794 2796 NULL, 2795 2797 NULL,
+18 -11
ntoskrnl/config/cminit.c
··· 16 16 17 17 NTSTATUS 18 18 NTAPI 19 - CmpInitializeHive(OUT PCMHIVE *CmHive, 20 - IN ULONG OperationType, 21 - IN ULONG HiveFlags, 22 - IN ULONG FileType, 23 - IN PVOID HiveData OPTIONAL, 24 - IN HANDLE Primary, 25 - IN HANDLE Log, 26 - IN HANDLE External, 27 - IN PCUNICODE_STRING FileName OPTIONAL, 28 - IN ULONG CheckFlags) 19 + CmpInitializeHive( 20 + _Out_ PCMHIVE *CmHive, 21 + _In_ ULONG OperationType, 22 + _In_ ULONG HiveFlags, 23 + _In_ ULONG FileType, 24 + _In_opt_ PVOID HiveData, 25 + _In_ HANDLE Primary, 26 + _In_ HANDLE Log, 27 + _In_ HANDLE External, 28 + _In_ HANDLE Alternate, 29 + _In_opt_ PCUNICODE_STRING FileName, 30 + _In_ ULONG CheckFlags) 29 31 { 30 32 PCMHIVE Hive; 31 33 IO_STATUS_BLOCK IoStatusBlock; ··· 44 46 * unless this hive is a shared system hive. 45 47 * - An in-memory initialization without hive data. 46 48 * - A log hive that is not linked to a correct file type. 49 + * - An alternate hive that is not linked to a correct file type. 50 + * - A lonely alternate hive not backed up with its corresponding primary hive. 47 51 */ 48 52 if (((External) && ((Primary) || (Log))) || 49 53 ((Log) && !(Primary)) || 50 54 (!(CmpShareSystemHives) && (HiveFlags & HIVE_VOLATILE) && 51 55 ((Primary) || (External) || (Log))) || 52 56 ((OperationType == HINIT_MEMORY) && (!HiveData)) || 53 - ((Log) && (FileType != HFILE_TYPE_LOG))) 57 + ((Log) && (FileType != HFILE_TYPE_LOG)) || 58 + ((Alternate) && (FileType != HFILE_TYPE_ALTERNATE)) || 59 + ((Alternate) && !(Primary))) 54 60 { 55 61 /* Fail the request */ 56 62 return STATUS_INVALID_PARAMETER; ··· 140 146 Hive->FileHandles[HFILE_TYPE_PRIMARY] = Primary; 141 147 Hive->FileHandles[HFILE_TYPE_LOG] = Log; 142 148 Hive->FileHandles[HFILE_TYPE_EXTERNAL] = External; 149 + Hive->FileHandles[HFILE_TYPE_ALTERNATE] = Alternate; 143 150 144 151 /* Initailize the guarded mutex */ 145 152 KeInitializeGuardedMutex(Hive->ViewLock);
+112 -7
ntoskrnl/config/cmsysini.c
··· 359 359 FileHandle, 360 360 LogHandle, 361 361 NULL, 362 + NULL, 362 363 HiveName, 363 364 CheckFlags); 364 365 if (!NT_SUCCESS(Status)) ··· 906 907 Status = CmpInitializeHive(&SystemHive, 907 908 HiveBase ? HINIT_MEMORY : HINIT_CREATE, 908 909 HIVE_NOLAZYFLUSH, 909 - HFILE_TYPE_LOG, 910 + HFILE_TYPE_ALTERNATE, 910 911 HiveBase, 912 + NULL, 911 913 NULL, 912 914 NULL, 913 915 NULL, ··· 1181 1183 return ConfigPath; 1182 1184 } 1183 1185 1186 + /** 1187 + * @brief 1188 + * Checks if the primary and alternate backing hive are 1189 + * the same, by determining the time stamp of both hives. 1190 + * 1191 + * @param[in] FileName 1192 + * A pointer to a string containing the file name of the 1193 + * primary hive. 1194 + * 1195 + * @param[in] CmMainmHive 1196 + * A pointer to a CM hive descriptor associated with the 1197 + * primary hive. 1198 + * 1199 + * @param[in] AlternateHandle 1200 + * A handle to a file that represents the alternate hive. 1201 + * 1202 + * @param[in] Diverged 1203 + * A pointer to a boolean value, if both hives are the same 1204 + * it returns TRUE. Otherwise it returns FALSE. 1205 + */ 1206 + static 1207 + VOID 1208 + CmpHasAlternateHiveDiverged( 1209 + _In_ PCUNICODE_STRING FileName, 1210 + _In_ PCMHIVE CmMainmHive, 1211 + _In_ HANDLE AlternateHandle, 1212 + _Out_ PBOOLEAN Diverged) 1213 + { 1214 + PHHIVE Hive, AlternateHive; 1215 + NTSTATUS Status; 1216 + PCMHIVE CmiAlternateHive; 1217 + 1218 + /* Assume it has not diverged */ 1219 + *Diverged = FALSE; 1220 + 1221 + /* Initialize the SYSTEM alternate hive */ 1222 + Status = CmpInitializeHive(&CmiAlternateHive, 1223 + HINIT_FILE, 1224 + 0, 1225 + HFILE_TYPE_PRIMARY, 1226 + NULL, 1227 + AlternateHandle, 1228 + NULL, 1229 + NULL, 1230 + NULL, 1231 + FileName, 1232 + CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES); 1233 + if (!NT_SUCCESS(Status)) 1234 + { 1235 + /* Assume it has diverged... */ 1236 + DPRINT1("Failed to initialize the alternate hive to check for diversion (Status 0x%lx)\n", Status); 1237 + *Diverged = TRUE; 1238 + return; 1239 + } 1240 + 1241 + /* 1242 + * Check the timestamp of both hives. If they do not match they 1243 + * have diverged, the kernel has to synchronize the both hives. 1244 + */ 1245 + Hive = &CmMainmHive->Hive; 1246 + AlternateHive = &CmiAlternateHive->Hive; 1247 + if (AlternateHive->BaseBlock->TimeStamp.QuadPart != 1248 + Hive->BaseBlock->TimeStamp.QuadPart) 1249 + { 1250 + *Diverged = TRUE; 1251 + } 1252 + 1253 + CmpDestroyHive(CmiAlternateHive); 1254 + } 1255 + 1184 1256 _Function_class_(KSTART_ROUTINE) 1185 1257 VOID 1186 1258 NTAPI ··· 1193 1265 USHORT FileStart; 1194 1266 ULONG PrimaryDisposition, SecondaryDisposition, ClusterSize; 1195 1267 PCMHIVE CmHive; 1196 - HANDLE PrimaryHandle = NULL, LogHandle = NULL; 1268 + HANDLE PrimaryHandle = NULL, AlternateHandle = NULL; 1197 1269 NTSTATUS Status = STATUS_SUCCESS; 1198 1270 PVOID ErrorParameters; 1271 + BOOLEAN HasDiverged; 1199 1272 PAGED_CODE(); 1200 1273 1201 1274 /* Get the hive index, make sure it makes sense */ ··· 1274 1347 { 1275 1348 /* It's now, open the hive file and log */ 1276 1349 Status = CmpOpenHiveFiles(&FileName, 1277 - L".LOG", 1350 + L".ALT", 1278 1351 &PrimaryHandle, 1279 - &LogHandle, 1352 + &AlternateHandle, 1280 1353 &PrimaryDisposition, 1281 1354 &SecondaryDisposition, 1282 1355 TRUE, 1283 1356 TRUE, 1284 1357 FALSE, 1285 1358 &ClusterSize); 1286 - if (!(NT_SUCCESS(Status)) || !(LogHandle)) 1359 + if (!(NT_SUCCESS(Status)) || !(AlternateHandle)) 1287 1360 { 1288 - /* Couldn't open the hive or its log file, raise a hard error */ 1361 + /* Couldn't open the hive or its alternate file, raise a hard error */ 1289 1362 ErrorParameters = &FileName; 1290 1363 NtRaiseHardError(STATUS_CANNOT_LOAD_REGISTRY_FILE, 1291 1364 1, ··· 1299 1372 } 1300 1373 1301 1374 /* Save the file handles. This should remove our sync hacks */ 1302 - CmHive->FileHandles[HFILE_TYPE_LOG] = LogHandle; 1375 + /* 1376 + * FIXME: Any hive that relies on the alternate hive for recovery purposes 1377 + * will only get an alternate hive. As a result, the LOG file would never 1378 + * get synced each time a write is done to the hive. In the future it would 1379 + * be best to adapt the code so that a primary hive can use a LOG and ALT 1380 + * hives at the same time. 1381 + */ 1382 + CmHive->FileHandles[HFILE_TYPE_ALTERNATE] = AlternateHandle; 1303 1383 CmHive->FileHandles[HFILE_TYPE_PRIMARY] = PrimaryHandle; 1304 1384 1305 1385 /* Allow lazy flushing since the handles are there -- remove sync hacks */ ··· 1332 1412 CmHive->Hive.DirtyCount = CmHive->Hive.DirtyVector.SizeOfBitMap; 1333 1413 HvSyncHive((PHHIVE)CmHive); 1334 1414 } 1415 + else 1416 + { 1417 + /* 1418 + * Check whether the both primary and alternate hives are the same, 1419 + * or that the primary or alternate were created for the first time. 1420 + * Do a write against the alternate hive in these cases. 1421 + */ 1422 + CmpHasAlternateHiveDiverged(&FileName, 1423 + CmHive, 1424 + AlternateHandle, 1425 + &HasDiverged); 1426 + if (HasDiverged || 1427 + PrimaryDisposition == FILE_CREATED || 1428 + SecondaryDisposition == FILE_CREATED) 1429 + { 1430 + if (!HvWriteAlternateHive((PHHIVE)CmHive)) 1431 + { 1432 + DPRINT1("Failed to write to alternate hive\n"); 1433 + goto Exit; 1434 + } 1435 + } 1436 + } 1335 1437 1336 1438 /* Finally, set our allocated hive to the same hive we've had */ 1337 1439 CmpMachineHiveList[i].CmHive2 = CmHive; ··· 1339 1441 } 1340 1442 } 1341 1443 1444 + Exit: 1342 1445 /* We're done */ 1343 1446 CmpMachineHiveList[i].ThreadFinished = TRUE; 1344 1447 ··· 1571 1674 NULL, 1572 1675 NULL, 1573 1676 NULL, 1677 + NULL, 1574 1678 CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES); 1575 1679 if (!NT_SUCCESS(Status)) 1576 1680 { ··· 1657 1761 HINIT_CREATE, 1658 1762 HIVE_VOLATILE, 1659 1763 HFILE_TYPE_PRIMARY, 1764 + NULL, 1660 1765 NULL, 1661 1766 NULL, 1662 1767 NULL,
+11 -10
ntoskrnl/include/internal/cm.h
··· 747 747 NTSTATUS 748 748 NTAPI 749 749 CmpInitializeHive( 750 - OUT PCMHIVE *CmHive, 751 - IN ULONG OperationType, 752 - IN ULONG HiveFlags, 753 - IN ULONG FileType, 754 - IN PVOID HiveData OPTIONAL, 755 - IN HANDLE Primary, 756 - IN HANDLE Log, 757 - IN HANDLE External, 758 - IN PCUNICODE_STRING FileName OPTIONAL, 759 - IN ULONG CheckFlags 750 + _Out_ PCMHIVE *CmHive, 751 + _In_ ULONG OperationType, 752 + _In_ ULONG HiveFlags, 753 + _In_ ULONG FileType, 754 + _In_opt_ PVOID HiveData, 755 + _In_ HANDLE Primary, 756 + _In_ HANDLE Log, 757 + _In_ HANDLE External, 758 + _In_ HANDLE Alternate, 759 + _In_opt_ PCUNICODE_STRING FileName, 760 + _In_ ULONG CheckFlags 760 761 ); 761 762 762 763 NTSTATUS
+9
sdk/lib/cmlib/cmlib.h
··· 121 121 IN ULONG NumberToSet); 122 122 123 123 VOID NTAPI 124 + RtlSetAllBits( 125 + IN PRTL_BITMAP BitMapHeader); 126 + 127 + VOID NTAPI 124 128 RtlClearAllBits( 125 129 IN PRTL_BITMAP BitMapHeader); 126 130 ··· 508 512 BOOLEAN CMAPI 509 513 HvWriteHive( 510 514 PHHIVE RegistryHive); 515 + 516 + BOOLEAN 517 + CMAPI 518 + HvWriteAlternateHive( 519 + _In_ PHHIVE RegistryHive); 511 520 512 521 BOOLEAN 513 522 CMAPI
+3 -1
sdk/lib/cmlib/hivedata.h
··· 33 33 #define HFILE_TYPE_PRIMARY 0 34 34 #define HFILE_TYPE_LOG 1 35 35 #define HFILE_TYPE_EXTERNAL 2 36 - #define HFILE_TYPE_MAX 3 36 + #define HFILE_TYPE_ALTERNATE 3 // Technically a HFILE_TYPE_PRIMARY but for mirror backup hives. ONLY USED for the SYSTEM hive! 37 + #define HFILE_TYPE_MAX 4 37 38 38 39 // 39 40 // Hive sizes ··· 334 335 BOOLEAN ReadOnly; 335 336 #if (NTDDI_VERSION < NTDDI_VISTA) // NTDDI_LONGHORN 336 337 BOOLEAN Log; 338 + BOOLEAN Alternate; 337 339 #endif 338 340 BOOLEAN DirtyFlag; 339 341 #if (NTDDI_VERSION >= NTDDI_VISTA) // NTDDI_LONGHORN
+13
sdk/lib/cmlib/hiveinit.c
··· 441 441 RtlInitializeBitMap(&Hive->DirtyVector, BitmapBuffer, BitmapSize * 8); 442 442 RtlClearAllBits(&Hive->DirtyVector); 443 443 444 + /* 445 + * Mark the entire hive as dirty. Indeed we understand if we charged up 446 + * the alternate variant of the primary hive (e.g. SYSTEM.ALT) because 447 + * FreeLdr could not load the main SYSTEM hive, due to corruptions, and 448 + * repairing it with a LOG did not help at all. 449 + */ 450 + if (ChunkBase->BootRecover == HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE) 451 + { 452 + RtlSetAllBits(&Hive->DirtyVector); 453 + Hive->DirtyCount = Hive->DirtyVector.SizeOfBitMap; 454 + } 455 + 444 456 HvpInitFileName(Hive->BaseBlock, FileName); 445 457 446 458 return STATUS_SUCCESS; ··· 1377 1389 Hive->Version = HSYS_MINOR; 1378 1390 #if (NTDDI_VERSION < NTDDI_VISTA) 1379 1391 Hive->Log = (FileType == HFILE_TYPE_LOG); 1392 + Hive->Alternate = (FileType == HFILE_TYPE_ALTERNATE); 1380 1393 #endif 1381 1394 Hive->HiveFlags = HiveFlags & ~HIVE_NOLAZYFLUSH; 1382 1395
+71 -10
sdk/lib/cmlib/hivewrt.c
··· 1 1 /* 2 2 * PROJECT: ReactOS Kernel 3 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 - * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log Writing 4 + * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log/Alternate Writing 5 5 * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl 6 6 * Copyright 2005 Filip Navara <navaraf@reactos.org> 7 7 * Copyright 2021 Max Korostil ··· 297 297 * data to be written to the primary hive, otherwise if 298 298 * it's set to FALSE then the function writes all the data. 299 299 * 300 + * @param[in] FileType 301 + * The file type of a registry hive. This can be HFILE_TYPE_PRIMARY 302 + * or HFILE_TYPE_ALTERNATE. 303 + * 300 304 * @return 301 305 * Returns TRUE if writing to hive has succeeded, 302 306 * FALSE otherwise. 307 + * 308 + * @remarks 309 + * The on-disk header metadata of a hive is already written with type 310 + * of HFILE_TYPE_PRIMARY, regardless of what file type the caller submits, 311 + * as an alternate hive is basically a mirror of the primary hive. 303 312 */ 304 313 static 305 314 BOOLEAN 306 315 CMAPI 307 316 HvpWriteHive( 308 317 _In_ PHHIVE RegistryHive, 309 - _In_ BOOLEAN OnlyDirty) 318 + _In_ BOOLEAN OnlyDirty, 319 + _In_ ULONG FileType) 310 320 { 311 321 BOOLEAN Success; 312 322 ULONG FileOffset; ··· 348 358 349 359 /* Write hive block */ 350 360 FileOffset = 0; 351 - Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY, 361 + Success = RegistryHive->FileWrite(RegistryHive, FileType, 352 362 &FileOffset, RegistryHive->BaseBlock, 353 363 sizeof(HBASE_BLOCK)); 354 364 if (!Success) ··· 384 394 FileOffset = (BlockIndex + 1) * HBLOCK_SIZE; 385 395 386 396 /* Now write this block to primary hive file */ 387 - Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY, 397 + Success = RegistryHive->FileWrite(RegistryHive, FileType, 388 398 &FileOffset, Block, HBLOCK_SIZE); 389 399 if (!Success) 390 400 { ··· 401 411 * We wrote all the hive contents to the file, we 402 412 * must flush the changes to disk now. 403 413 */ 404 - Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0); 414 + Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); 405 415 if (!Success) 406 416 { 407 417 DPRINT1("Failed to flush the primary hive\n"); ··· 420 430 421 431 /* Write hive block */ 422 432 FileOffset = 0; 423 - Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY, 433 + Success = RegistryHive->FileWrite(RegistryHive, FileType, 424 434 &FileOffset, RegistryHive->BaseBlock, 425 435 sizeof(HBASE_BLOCK)); 426 436 if (!Success) ··· 430 440 } 431 441 432 442 /* Flush the hive immediately */ 433 - Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0); 443 + Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); 434 444 if (!Success) 435 445 { 436 446 DPRINT1("Failed to flush the primary hive\n"); ··· 526 536 } 527 537 528 538 /* Update the primary hive file */ 529 - if (!HvpWriteHive(RegistryHive, TRUE)) 539 + if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY)) 530 540 { 531 541 DPRINT1("Failed to write the primary hive\n"); 532 542 #if !defined(CMLIB_HOST) && !defined(_BLDR_) ··· 535 545 return FALSE; 536 546 } 537 547 548 + /* Update the alternate hive file if present */ 549 + if (RegistryHive->Alternate == TRUE) 550 + { 551 + if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_ALTERNATE)) 552 + { 553 + DPRINT1("Failed to write the alternate hive\n"); 554 + #if !defined(CMLIB_HOST) && !defined(_BLDR_) 555 + IoSetThreadHardErrorMode(HardErrors); 556 + #endif 557 + return FALSE; 558 + } 559 + } 560 + 538 561 /* Clear dirty bitmap. */ 539 562 RtlClearAllBits(&RegistryHive->DirtyVector); 540 563 RegistryHive->DirtyCount = 0; ··· 601 624 #endif 602 625 603 626 /* Update hive file */ 604 - if (!HvpWriteHive(RegistryHive, FALSE)) 627 + if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_PRIMARY)) 605 628 { 606 629 DPRINT1("Failed to write the hive\n"); 607 630 return FALSE; ··· 610 633 return TRUE; 611 634 } 612 635 636 + /** 637 + * @brief 638 + * Writes data to an alternate registry hive. 639 + * An alternate hive is usually backed up by a primary 640 + * hive. This function is tipically used to force write 641 + * data into the alternate hive if both hives no longer match. 642 + * 643 + * @param[in] RegistryHive 644 + * A pointer to a hive descriptor where data 645 + * is to be written into. 646 + * 647 + * @return 648 + * Returns TRUE if hive writing has succeeded, 649 + * FALSE otherwise. 650 + */ 651 + BOOLEAN 652 + CMAPI 653 + HvWriteAlternateHive( 654 + _In_ PHHIVE RegistryHive) 655 + { 656 + ASSERT(RegistryHive->ReadOnly == FALSE); 657 + ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 658 + ASSERT(RegistryHive->Alternate == TRUE); 659 + 660 + #if !defined(_BLDR_) 661 + /* Update hive header modification time */ 662 + KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); 663 + #endif 664 + 665 + /* Update hive file */ 666 + if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_ALTERNATE)) 667 + { 668 + DPRINT1("Failed to write the alternate hive\n"); 669 + return FALSE; 670 + } 671 + 672 + return TRUE; 673 + } 613 674 614 675 /** 615 676 * @brief ··· 634 695 ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 635 696 636 697 /* Call the private API call to do the deed for us */ 637 - return HvpWriteHive(RegistryHive, TRUE); 698 + return HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY); 638 699 } 639 700 640 701 /* EOF */