2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Configuration Manager Library - Registry Hive Loading & Initialization
5 * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl
6 * Copyright 2005 Filip Navara <navaraf@reactos.org>
7 * Copyright 2021 Max Korostil
8 * Copyright 2022 George Bișoc <george.bisoc@reactos.org>
15 /* ENUMERATIONS *************************************************************/
28 /* PRIVATE FUNCTIONS ********************************************************/
32 * Validates the base block header of a registry
35 * @param[in] BaseBlock
36 * A pointer to a base block header to
40 * The file type of a registry file to check
41 * against the file type of the base block.
44 * Returns TRUE if the base block header is valid,
50 _In_ PHBASE_BLOCK BaseBlock
,
53 if (BaseBlock
->Signature
!= HV_HBLOCK_SIGNATURE
||
54 BaseBlock
->Major
!= HSYS_MAJOR
||
55 BaseBlock
->Minor
< HSYS_MINOR
||
56 BaseBlock
->Type
!= FileType
||
57 BaseBlock
->Format
!= HBASE_FORMAT_MEMORY
||
58 BaseBlock
->Cluster
!= 1 ||
59 BaseBlock
->Sequence1
!= BaseBlock
->Sequence2
||
60 HvpHiveHeaderChecksum(BaseBlock
) != BaseBlock
->CheckSum
)
62 DPRINT1("Verify Hive Header failed:\n");
63 DPRINT1(" Signature: 0x%x, expected 0x%x; Major: 0x%x, expected 0x%x\n",
64 BaseBlock
->Signature
, HV_HBLOCK_SIGNATURE
, BaseBlock
->Major
, HSYS_MAJOR
);
65 DPRINT1(" Minor: 0x%x expected to be >= 0x%x; Type: 0x%x, expected 0x%x\n",
66 BaseBlock
->Minor
, HSYS_MINOR
, BaseBlock
->Type
, FileType
);
67 DPRINT1(" Format: 0x%x, expected 0x%x; Cluster: 0x%x, expected 1\n",
68 BaseBlock
->Format
, HBASE_FORMAT_MEMORY
, BaseBlock
->Cluster
);
69 DPRINT1(" Sequence: 0x%x, expected 0x%x; Checksum: 0x%x, expected 0x%x\n",
70 BaseBlock
->Sequence1
, BaseBlock
->Sequence2
,
71 HvpHiveHeaderChecksum(BaseBlock
), BaseBlock
->CheckSum
);
81 * Frees all the bins within storage space
82 * associated with a hive descriptor.
85 * A pointer to a hive descriptor where
86 * all the bins are to be freed.
97 for (Storage
= 0; Storage
< Hive
->StorageTypeCount
; Storage
++)
100 for (i
= 0; i
< Hive
->Storage
[Storage
].Length
; i
++)
102 if (Hive
->Storage
[Storage
].BlockList
[i
].BinAddress
== (ULONG_PTR
)NULL
)
104 if (Hive
->Storage
[Storage
].BlockList
[i
].BinAddress
!= (ULONG_PTR
)Bin
)
106 Bin
= (PHBIN
)Hive
->Storage
[Storage
].BlockList
[i
].BinAddress
;
107 Hive
->Free((PHBIN
)Hive
->Storage
[Storage
].BlockList
[i
].BinAddress
, 0);
109 Hive
->Storage
[Storage
].BlockList
[i
].BinAddress
= (ULONG_PTR
)NULL
;
110 Hive
->Storage
[Storage
].BlockList
[i
].BlockAddress
= (ULONG_PTR
)NULL
;
113 if (Hive
->Storage
[Storage
].Length
)
114 Hive
->Free(Hive
->Storage
[Storage
].BlockList
, 0);
120 * Allocates a cluster-aligned hive base header block.
123 * A pointer to a hive descriptor where
124 * the header block allocator function is to
128 * If set to TRUE, the allocated base block will reside
129 * in paged pool, otherwise it will reside in non paged
133 * A tag name to supply for the allocated memory block
134 * for identification. This is for debugging purposes.
137 * Returns an allocated base block header if the function
138 * succeeds, otherwise it returns NULL.
143 HvpAllocBaseBlockAligned(
148 PHBASE_BLOCK BaseBlock
;
151 ASSERT(sizeof(HBASE_BLOCK
) >= (HSECTOR_SIZE
* Hive
->Cluster
));
153 /* Allocate the buffer */
154 BaseBlock
= Hive
->Allocate(Hive
->BaseBlockAlloc
, Paged
, Tag
);
155 if (!BaseBlock
) return NULL
;
157 /* Check for, and enforce, alignment */
158 Alignment
= Hive
->Cluster
* HSECTOR_SIZE
-1;
159 if ((ULONG_PTR
)BaseBlock
& Alignment
)
161 /* Free the old header and reallocate a new one, always paged */
162 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
163 BaseBlock
= Hive
->Allocate(PAGE_SIZE
, TRUE
, Tag
);
164 if (!BaseBlock
) return NULL
;
166 Hive
->BaseBlockAlloc
= PAGE_SIZE
;
174 * Initializes a NULL-terminated Unicode hive file name
175 * of a hive header by copying the last 31 characters of
176 * the hive file name. Mainly used for debugging purposes.
178 * @param[in,out] BaseBlock
179 * A pointer to a base block header where the hive
180 * file name is to be copied to.
182 * @param[in] FileName
183 * A pointer to a Unicode string structure containing
184 * the hive file name to be copied from. If this argument
185 * is NULL, the base block will not have any hive file name.
190 _Inout_ PHBASE_BLOCK BaseBlock
,
191 _In_opt_ PCUNICODE_STRING FileName
)
196 /* Always NULL-initialize */
197 RtlZeroMemory(BaseBlock
->FileName
, (HIVE_FILENAME_MAXLEN
+ 1) * sizeof(WCHAR
));
199 /* Copy the 31 last characters of the hive file name if any */
200 if (!FileName
) return;
202 if (FileName
->Length
/ sizeof(WCHAR
) <= HIVE_FILENAME_MAXLEN
)
205 Length
= FileName
->Length
;
209 Offset
= FileName
->Length
/ sizeof(WCHAR
) - HIVE_FILENAME_MAXLEN
;
210 Length
= HIVE_FILENAME_MAXLEN
* sizeof(WCHAR
);
213 RtlCopyMemory(BaseBlock
->FileName
, FileName
->Buffer
+ Offset
, Length
);
218 * Initializes a hive descriptor structure for a
219 * newly created hive in memory.
221 * @param[in,out] RegistryHive
222 * A pointer to a registry hive descriptor where
223 * the internal structures field are to be initialized
226 * @param[in] FileName
227 * A pointer to a Unicode string structure containing
228 * the hive file name to be copied from. If this argument
229 * is NULL, the base block will not have any hive file name.
232 * Returns STATUS_SUCCESS if the function has created the
233 * hive descriptor successfully. STATUS_NO_MEMORY is returned
234 * if the base header block could not be allocated.
239 _Inout_ PHHIVE RegistryHive
,
240 _In_opt_ PCUNICODE_STRING FileName
)
242 PHBASE_BLOCK BaseBlock
;
245 /* Allocate the base block */
246 BaseBlock
= HvpAllocBaseBlockAligned(RegistryHive
, FALSE
, TAG_CM
);
247 if (BaseBlock
== NULL
)
248 return STATUS_NO_MEMORY
;
251 RtlZeroMemory(BaseBlock
, RegistryHive
->BaseBlockAlloc
);
253 BaseBlock
->Signature
= HV_HBLOCK_SIGNATURE
;
254 BaseBlock
->Major
= HSYS_MAJOR
;
255 BaseBlock
->Minor
= HSYS_MINOR
;
256 BaseBlock
->Type
= HFILE_TYPE_PRIMARY
;
257 BaseBlock
->Format
= HBASE_FORMAT_MEMORY
;
258 BaseBlock
->Cluster
= 1;
259 BaseBlock
->RootCell
= HCELL_NIL
;
260 BaseBlock
->Length
= 0;
261 BaseBlock
->Sequence1
= 1;
262 BaseBlock
->Sequence2
= 1;
263 BaseBlock
->TimeStamp
.QuadPart
= 0ULL;
266 * No need to compute the checksum since
267 * the hive resides only in memory so far.
269 BaseBlock
->CheckSum
= 0;
271 /* Set default boot type */
272 BaseBlock
->BootType
= HBOOT_TYPE_REGULAR
;
274 /* Setup hive data */
275 RegistryHive
->BaseBlock
= BaseBlock
;
276 RegistryHive
->Version
= BaseBlock
->Minor
; // == HSYS_MINOR
278 for (Index
= 0; Index
< 24; Index
++)
280 RegistryHive
->Storage
[Stable
].FreeDisplay
[Index
] = HCELL_NIL
;
281 RegistryHive
->Storage
[Volatile
].FreeDisplay
[Index
] = HCELL_NIL
;
284 HvpInitFileName(BaseBlock
, FileName
);
286 return STATUS_SUCCESS
;
291 * Initializes a hive descriptor from an already loaded
292 * registry hive stored in memory. The data of the hive is
293 * copied and it is prepared for read/write access.
296 * A pointer to a registry hive descriptor where
297 * the internal structures field are to be initialized
298 * from hive data that is already loaded in memory.
300 * @param[in] ChunkBase
301 * A pointer to a valid base block header containing
302 * registry header data for initialization.
304 * @param[in] FileName
305 * A pointer to a Unicode string structure containing
306 * the hive file name to be copied from. If this argument
307 * is NULL, the base block will not have any hive file name.
310 * Returns STATUS_SUCCESS if the function has initialized the
311 * hive descriptor successfully. STATUS_REGISTRY_CORRUPT is
312 * returned if the base block header contains invalid header
313 * data. STATUS_NO_MEMORY is returned if memory could not
314 * be allocated for registry stuff.
318 HvpInitializeMemoryHive(
320 _In_ PHBASE_BLOCK ChunkBase
,
321 _In_opt_ PCUNICODE_STRING FileName
)
330 ChunkSize
= ChunkBase
->Length
;
331 DPRINT("ChunkSize: %zx\n", ChunkSize
);
333 if (ChunkSize
< sizeof(HBASE_BLOCK
) ||
334 !HvpVerifyHiveHeader(ChunkBase
, HFILE_TYPE_PRIMARY
))
336 DPRINT1("Registry is corrupt: ChunkSize 0x%zx < sizeof(HBASE_BLOCK) 0x%zx, "
337 "or HvpVerifyHiveHeader() failed\n", ChunkSize
, sizeof(HBASE_BLOCK
));
338 return STATUS_REGISTRY_CORRUPT
;
341 /* Allocate the base block */
342 Hive
->BaseBlock
= HvpAllocBaseBlockAligned(Hive
, FALSE
, TAG_CM
);
343 if (Hive
->BaseBlock
== NULL
)
344 return STATUS_NO_MEMORY
;
346 RtlCopyMemory(Hive
->BaseBlock
, ChunkBase
, sizeof(HBASE_BLOCK
));
348 /* Setup hive data */
349 Hive
->Version
= ChunkBase
->Minor
;
352 * Build a block list from the in-memory chunk and copy the data as
356 Hive
->Storage
[Stable
].Length
= (ULONG
)(ChunkSize
/ HBLOCK_SIZE
);
357 Hive
->Storage
[Stable
].BlockList
=
358 Hive
->Allocate(Hive
->Storage
[Stable
].Length
*
359 sizeof(HMAP_ENTRY
), FALSE
, TAG_CM
);
360 if (Hive
->Storage
[Stable
].BlockList
== NULL
)
362 DPRINT1("Allocating block list failed\n");
363 Hive
->Free(Hive
->BaseBlock
, Hive
->BaseBlockAlloc
);
364 return STATUS_NO_MEMORY
;
367 for (BlockIndex
= 0; BlockIndex
< Hive
->Storage
[Stable
].Length
; )
369 Bin
= (PHBIN
)((ULONG_PTR
)ChunkBase
+ (BlockIndex
+ 1) * HBLOCK_SIZE
);
370 if (Bin
->Signature
!= HV_HBIN_SIGNATURE
||
371 (Bin
->Size
% HBLOCK_SIZE
) != 0 ||
372 (Bin
->FileOffset
/ HBLOCK_SIZE
) != BlockIndex
)
375 * Bin is toast but luckily either the signature, size or offset
376 * is out of order. For the signature it is obvious what we are going
377 * to do, for the offset we are re-positioning the bin back to where it
378 * was and for the size we will set it up to a block size, since technically
379 * a hive bin is large as a block itself to accommodate cells.
381 if (!CmIsSelfHealEnabled(FALSE
))
383 DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x. Self-heal not possible!\n",
384 (unsigned long)BlockIndex
, (unsigned)Bin
->Signature
, (unsigned)Bin
->Size
);
385 Hive
->Free(Hive
->Storage
[Stable
].BlockList
, 0);
386 Hive
->Free(Hive
->BaseBlock
, Hive
->BaseBlockAlloc
);
387 return STATUS_REGISTRY_CORRUPT
;
391 Bin
->Signature
= HV_HBIN_SIGNATURE
;
392 Bin
->Size
= HBLOCK_SIZE
;
393 Bin
->FileOffset
= BlockIndex
* HBLOCK_SIZE
;
394 ChunkBase
->BootType
|= HBOOT_TYPE_SELF_HEAL
;
395 DPRINT1("Bin at index %lu is corrupt and it has been repaired!\n", (unsigned long)BlockIndex
);
398 NewBin
= Hive
->Allocate(Bin
->Size
, TRUE
, TAG_CM
);
401 Hive
->Free(Hive
->Storage
[Stable
].BlockList
, 0);
402 Hive
->Free(Hive
->BaseBlock
, Hive
->BaseBlockAlloc
);
403 return STATUS_NO_MEMORY
;
406 Hive
->Storage
[Stable
].BlockList
[BlockIndex
].BinAddress
= (ULONG_PTR
)NewBin
;
407 Hive
->Storage
[Stable
].BlockList
[BlockIndex
].BlockAddress
= (ULONG_PTR
)NewBin
;
409 RtlCopyMemory(NewBin
, Bin
, Bin
->Size
);
411 if (Bin
->Size
> HBLOCK_SIZE
)
413 for (i
= 1; i
< Bin
->Size
/ HBLOCK_SIZE
; i
++)
415 Hive
->Storage
[Stable
].BlockList
[BlockIndex
+ i
].BinAddress
= (ULONG_PTR
)NewBin
;
416 Hive
->Storage
[Stable
].BlockList
[BlockIndex
+ i
].BlockAddress
=
417 ((ULONG_PTR
)NewBin
+ (i
* HBLOCK_SIZE
));
421 BlockIndex
+= Bin
->Size
/ HBLOCK_SIZE
;
424 if (!NT_SUCCESS(HvpCreateHiveFreeCellList(Hive
)))
426 HvpFreeHiveBins(Hive
);
427 Hive
->Free(Hive
->BaseBlock
, Hive
->BaseBlockAlloc
);
428 return STATUS_NO_MEMORY
;
431 BitmapSize
= ROUND_UP(Hive
->Storage
[Stable
].Length
,
432 sizeof(ULONG
) * 8) / 8;
433 BitmapBuffer
= (PULONG
)Hive
->Allocate(BitmapSize
, TRUE
, TAG_CM
);
434 if (BitmapBuffer
== NULL
)
436 HvpFreeHiveBins(Hive
);
437 Hive
->Free(Hive
->BaseBlock
, Hive
->BaseBlockAlloc
);
438 return STATUS_NO_MEMORY
;
441 RtlInitializeBitMap(&Hive
->DirtyVector
, BitmapBuffer
, BitmapSize
* 8);
442 RtlClearAllBits(&Hive
->DirtyVector
);
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.
450 if (ChunkBase
->BootRecover
== HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE
)
452 RtlSetAllBits(&Hive
->DirtyVector
);
453 Hive
->DirtyCount
= Hive
->DirtyVector
.SizeOfBitMap
;
456 HvpInitFileName(Hive
->BaseBlock
, FileName
);
458 return STATUS_SUCCESS
;
463 * Initializes a hive descriptor for an already loaded hive
464 * that is stored in memory. This descriptor serves to denote
465 * such hive as being "flat", that is, the data and properties
466 * can be only read and not written into.
469 * A pointer to a registry hive descriptor where
470 * the internal structures fields are to be initialized
471 * from hive data that is already loaded in memory. Such
472 * hive descriptor will become read-only and flat.
474 * @param[in] ChunkBase
475 * A pointer to a valid base block header containing
476 * registry header data for initialization.
479 * Returns STATUS_SUCCESS if the function has initialized the
480 * flat hive descriptor. STATUS_REGISTRY_CORRUPT is returned if
481 * the base block header contains invalid header data.
485 HvpInitializeFlatHive(
487 _In_ PHBASE_BLOCK ChunkBase
)
489 if (!HvpVerifyHiveHeader(ChunkBase
, HFILE_TYPE_PRIMARY
))
490 return STATUS_REGISTRY_CORRUPT
;
492 /* Setup hive data */
493 Hive
->BaseBlock
= ChunkBase
;
494 Hive
->Version
= ChunkBase
->Minor
;
496 Hive
->ReadOnly
= TRUE
;
498 Hive
->StorageTypeCount
= 1;
500 /* Set default boot type */
501 ChunkBase
->BootType
= HBOOT_TYPE_REGULAR
;
503 return STATUS_SUCCESS
;
508 * Retrieves the base block hive header from the
509 * primary hive file stored in physical backing storage.
510 * This function may invoke a self-healing warning if
511 * hive header couldn't be obtained. See Return and Remarks
512 * sections for further information.
515 * A pointer to a registry hive descriptor that points
516 * to the primary hive being loaded. This descriptor is
517 * needed to obtain the hive header block from said hive.
519 * @param[in,out] HiveBaseBlock
520 * A pointer returned by the function that contains
521 * the hive header base block buffer obtained from
522 * the primary hive file pointed by the Hive argument.
523 * This parameter must not be NULL!
525 * @param[in,out] TimeStamp
526 * A pointer returned by the function that contains
527 * the time-stamp of the registry hive file at the
528 * moment of creation or modification. This parameter
532 * This function returns a result indicator. That is,
533 * HiveSuccess is returned if the hive header was obtained
534 * successfully. NoMemory is returned if the hive base block
535 * could not be allocated. NotHive is returned if the hive file
536 * that's been read isn't actually a hive. RecoverHeader is
537 * returned if the header needs to be recovered. RecoverData
538 * is returned if the hive data needs to be returned.
541 * RecoverHeader and RecoverData are status indicators that
542 * invoke a self-healing procedure if the hive header could not
543 * be obtained in a normal way and as a matter of fact the whole
544 * registry initialization procedure is orchestrated. RecoverHeader
545 * implies that the base block header of a hive is corrupt and it
546 * needs to be recovered, whereas RecoverData implies the registry
547 * data is corrupt. The latter status indicator is less severe unlike
548 * the former because the system can cope with data loss.
554 _Inout_ PHBASE_BLOCK
*HiveBaseBlock
,
555 _Inout_ PLARGE_INTEGER TimeStamp
)
557 PHBASE_BLOCK BaseBlock
;
562 ASSERT(sizeof(HBASE_BLOCK
) >= (HSECTOR_SIZE
* Hive
->Cluster
));
564 /* Assume failure and allocate the base block */
565 *HiveBaseBlock
= NULL
;
566 BaseBlock
= HvpAllocBaseBlockAligned(Hive
, TRUE
, TAG_CM
);
569 DPRINT1("Failed to allocate an aligned base block buffer\n");
574 RtlZeroMemory(BaseBlock
, sizeof(HBASE_BLOCK
));
576 /* Now read it from disk */
578 Result
= Hive
->FileRead(Hive
,
582 Hive
->Cluster
* HSECTOR_SIZE
);
586 * Don't assume the hive is ultimately destroyed
587 * but instead try to read the first block of
588 * the first bin hive. So that we're sure of
589 * ourselves we can somewhat recover this hive.
591 FileOffset
= HBLOCK_SIZE
;
592 Result
= Hive
->FileRead(Hive
,
596 Hive
->Cluster
* HSECTOR_SIZE
);
599 DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n");
600 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
605 * Deconstruct the casted buffer we got
606 * into a hive bin. Check if the offset
607 * position is in the right place (namely
608 * its offset must be 0 because it's the first
609 * bin) and it should have a sane signature.
611 FirstBin
= (PHBIN
)BaseBlock
;
612 if (FirstBin
->Signature
!= HV_HBIN_SIGNATURE
||
613 FirstBin
->FileOffset
!= 0)
615 DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n");
616 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
621 * There's still hope for this hive so acknowledge the
622 * caller this hive needs a recoverable header.
624 *TimeStamp
= BaseBlock
->TimeStamp
;
625 DPRINT1("The hive is not fully corrupt, the base block needs to be RECOVERED\n");
626 return RecoverHeader
;
630 * This hive has a base block that's not maimed
631 * but is the header data valid?
633 * FIXME: We must check if primary and secondary
634 * sequences mismatch separately and fire up RecoverData
635 * in that case but due to a hack in HvLoadHive, let
636 * HvpVerifyHiveHeader check the sequences for now.
638 if (!HvpVerifyHiveHeader(BaseBlock
, HFILE_TYPE_PRIMARY
))
640 DPRINT1("The hive base header block needs to be RECOVERED\n");
641 *TimeStamp
= BaseBlock
->TimeStamp
;
642 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
643 return RecoverHeader
;
646 /* Return information */
647 *HiveBaseBlock
= BaseBlock
;
648 *TimeStamp
= BaseBlock
->TimeStamp
;
653 * FIXME: Disable compilation for AMD64 for now since it makes
654 * the FreeLdr binary size so large it makes booting impossible.
656 #if !defined(_M_AMD64)
659 * Computes the hive space size by querying
660 * the file size of the associated hive file.
663 * A pointer to a hive descriptor where the
664 * hive length size is to be calculated.
667 * Returns the computed hive size.
674 #if !defined(CMLIB_HOST) && !defined(_BLDR_)
676 FILE_STANDARD_INFORMATION FileStandard
;
677 IO_STATUS_BLOCK IoStatusBlock
;
682 * Query the file size of the physical hive
683 * file. We need that information in order
684 * to ensure how big the hive actually is.
686 #if !defined(CMLIB_HOST) && !defined(_BLDR_)
687 Status
= ZwQueryInformationFile(((PCMHIVE
)Hive
)->FileHandles
[HFILE_TYPE_PRIMARY
],
690 sizeof(FILE_STANDARD_INFORMATION
),
691 FileStandardInformation
);
692 if (!NT_SUCCESS(Status
))
694 DPRINT1("ZwQueryInformationFile returned 0x%lx\n", Status
);
698 /* Now compute the hive size */
699 HiveSize
= FileStandard
.EndOfFile
.u
.LowPart
- HBLOCK_SIZE
;
706 * Recovers the base block header by obtaining
707 * it from a log file associated with the hive.
710 * A pointer to a hive descriptor associated
711 * with the log file where the hive header is
714 * @param[in] TimeStamp
715 * A pointer to a time-stamp used to check
716 * if the provided time matches with that
719 * @param[in,out] BaseBlock
720 * A pointer returned by the caller that contains
721 * the base block header that was read from the log.
722 * This base block could be also made manually by hand.
723 * See Remarks for further information.
726 * Returns HiveSuccess if the header was obtained
727 * normally from the log. NoMemory is returned if
728 * the base block header could not be allocated.
729 * Fail is returned if self-healing mode is disabled
730 * and the log couldn't be read or a write attempt
731 * to the primary hive has failed. SelfHeal is returned
732 * to indicate that self-heal mode goes further.
735 * When SelfHeal is returned this indicates that
736 * even the log we have gotten at hand is corrupt
737 * but since we do have at least a log our only hope
738 * is to reconstruct the pieces of the base header
743 HvpRecoverHeaderFromLog(
745 _In_ PLARGE_INTEGER TimeStamp
,
746 _Inout_ PHBASE_BLOCK
*BaseBlock
)
749 PHBASE_BLOCK LogHeader
;
752 BOOLEAN HeaderResuscitated
;
755 * The cluster must not be greater than what the
756 * base block can permit.
758 ASSERT(sizeof(HBASE_BLOCK
) >= (HSECTOR_SIZE
* Hive
->Cluster
));
760 /* Assume we haven't resuscitated the header */
761 HeaderResuscitated
= FALSE
;
763 /* Allocate an aligned buffer for the log header */
764 LogHeader
= HvpAllocBaseBlockAligned(Hive
, TRUE
, TAG_CM
);
767 DPRINT1("Failed to allocate memory for the log header\n");
771 /* Zero out our header buffer */
772 RtlZeroMemory(LogHeader
, HSECTOR_SIZE
);
774 /* Get the base header from the log */
776 Success
= Hive
->FileRead(Hive
,
780 Hive
->Cluster
* HSECTOR_SIZE
);
782 !HvpVerifyHiveHeader(LogHeader
, HFILE_TYPE_LOG
) ||
783 TimeStamp
->HighPart
!= LogHeader
->TimeStamp
.HighPart
||
784 TimeStamp
->LowPart
!= LogHeader
->TimeStamp
.LowPart
)
787 * We failed to read the base block header from
788 * the log, or the header itself or timestamp is
789 * invalid. Check if self healing is enabled.
791 if (!CmIsSelfHealEnabled(FALSE
))
793 DPRINT1("The log couldn't be read and self-healing mode is disabled\n");
794 Hive
->Free(LogHeader
, Hive
->BaseBlockAlloc
);
799 * Determine the size of this hive so that
800 * we can estabilish the length of the base
801 * block we are trying to resuscitate.
803 HiveSize
= HvpQueryHiveSize(Hive
);
806 DPRINT1("Failed to query the hive size\n");
807 Hive
->Free(LogHeader
, Hive
->BaseBlockAlloc
);
812 * We can still resuscitate the base header if we
813 * could not grab one from the log by reconstructing
814 * the header internals by hand (this assumes the
815 * root cell is not NIL nor damaged). CmCheckRegistry
816 * does the ultimate judgement whether the root cell
817 * is fatally kaput or not after the hive has been
818 * initialized and loaded.
820 * For more information about base block header
821 * resuscitation, see https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md#notes-4.
823 LogHeader
->Signature
= HV_HBLOCK_SIGNATURE
;
824 LogHeader
->Sequence1
= 1;
825 LogHeader
->Sequence2
= 1;
826 LogHeader
->Cluster
= 1;
827 LogHeader
->Length
= HiveSize
;
828 LogHeader
->CheckSum
= HvpHiveHeaderChecksum(LogHeader
);
831 * Acknowledge that we have resuscitated
834 HeaderResuscitated
= TRUE
;
835 DPRINT1("Header has been resuscitated, triggering self-heal mode\n");
839 * Tag this log header as a primary hive before
840 * writing it to the hive.
842 LogHeader
->Type
= HFILE_TYPE_PRIMARY
;
845 * If we have not made attempts of recovering
846 * the header due to log corruption then we
847 * have to compute the checksum. This is
848 * already done when the header has been resuscitated
849 * so don't try to do it twice.
851 if (!HeaderResuscitated
)
853 LogHeader
->CheckSum
= HvpHiveHeaderChecksum(LogHeader
);
856 /* Write the header back to hive now */
857 Success
= Hive
->FileWrite(Hive
,
861 Hive
->Cluster
* HSECTOR_SIZE
);
864 DPRINT1("Couldn't write the base header to primary hive\n");
865 Hive
->Free(LogHeader
, Hive
->BaseBlockAlloc
);
869 *BaseBlock
= LogHeader
;
870 return HeaderResuscitated
? SelfHeal
: HiveSuccess
;
875 * Recovers the registry data by obtaining it
876 * from a log that is associated with the hive.
879 * A pointer to a hive descriptor associated
880 * with the log file where the hive data is to
883 * @param[in] BaseBlock
884 * A pointer to a base block header.
887 * Returns HiveSuccess if the data was obtained
888 * normally from the log. Fail is returned if
889 * self-healing is disabled and we couldn't be
890 * able to read the data from the log or the
891 * dirty vector signature is garbage or we
892 * failed to write the data block to the primary
893 * hive. SelfHeal is returned to indicate that
894 * the log is corrupt and the system will continue
895 * to be recovered at the expense of data loss.
899 HvpRecoverDataFromLog(
901 _In_ PHBASE_BLOCK BaseBlock
)
908 UCHAR DirtyVector
[HSECTOR_SIZE
];
909 UCHAR Buffer
[HBLOCK_SIZE
];
911 /* Read the dirty data from the log */
912 FileOffset
= HV_LOG_HEADER_SIZE
;
913 Success
= Hive
->FileRead(Hive
,
920 if (!CmIsSelfHealEnabled(FALSE
))
922 DPRINT1("The log couldn't be read and self-healing mode is disabled\n");
927 * There's nothing we can do on a situation
928 * where dirty data could not be read from
929 * the log. It does not make much sense to
930 * behead the system on such scenario so
931 * trigger a self-heal and go on. The worst
932 * thing that can happen? Data loss, that's it.
934 DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n");
938 /* Check the dirty vector */
939 if (*((PULONG
)DirtyVector
) != HV_LOG_DIRTY_SIGNATURE
)
941 if (!CmIsSelfHealEnabled(FALSE
))
943 DPRINT1("The log's dirty vector signature is not valid\n");
948 * Trigger a self-heal like above. If the
949 * vector signature is garbage then logically
950 * whatever comes after the signature is also
953 DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n");
957 /* Now read each data individually and write it back to hive */
959 StorageLength
= BaseBlock
->Length
/ HBLOCK_SIZE
;
960 for (BlockIndex
= 0; BlockIndex
< StorageLength
; BlockIndex
++)
962 /* Skip this block if it's not dirty and go to the next one */
963 if (DirtyVector
[BlockIndex
+ sizeof(HV_LOG_DIRTY_SIGNATURE
)] != HV_LOG_DIRTY_BLOCK
)
968 FileOffset
= HSECTOR_SIZE
+ HSECTOR_SIZE
+ LogIndex
* HBLOCK_SIZE
;
969 Success
= Hive
->FileRead(Hive
,
976 DPRINT1("Failed to read the dirty block (index %lu)\n", BlockIndex
);
980 FileOffset
= HBLOCK_SIZE
+ BlockIndex
* HBLOCK_SIZE
;
981 Success
= Hive
->FileWrite(Hive
,
988 DPRINT1("Failed to write dirty block to hive (index %lu)\n", BlockIndex
);
992 /* Increment the index in log as we continue further */
1002 * Loads a registry hive from a physical hive file
1003 * within the physical backing storage. Base block
1004 * and registry data are read from the said physical
1005 * hive file. This function can perform registry recovery
1006 * if hive loading could not be done normally.
1009 * A pointer to a hive descriptor where the said hive
1010 * is to be loaded from the physical hive file.
1012 * @param[in] FileName
1013 * A pointer to a NULL-terminated Unicode string structure
1014 * containing the hive file name to be copied from.
1017 * STATUS_SUCCESS is returned if the hive has been loaded
1018 * successfully. STATUS_INSUFFICIENT_RESOURCES is returned
1019 * if there's not enough memory resources to satisfy registry
1020 * operations and/or requests. STATUS_NOT_REGISTRY_FILE is returned
1021 * if the hive is not actually a hive file. STATUS_REGISTRY_CORRUPT
1022 * is returned if the hive has subdued previous damage and
1023 * the hive could not be recovered because there's no
1024 * log present or self healing is disabled. STATUS_REGISTRY_RECOVERED
1025 * is returned if the hive has been recovered. An eventual flush
1026 * of the registry is needed after the hive's been fully loaded.
1032 _In_opt_ PCUNICODE_STRING FileName
)
1036 PHBASE_BLOCK BaseBlock
= NULL
;
1037 /* FIXME: See the comment above (near HvpQueryHiveSize) */
1038 #if defined(_M_AMD64)
1041 ULONG Result
, Result2
;
1043 LARGE_INTEGER TimeStamp
;
1047 BOOLEAN HiveSelfHeal
= FALSE
;
1049 /* Get the hive header */
1050 Result
= HvpGetHiveHeader(Hive
, &BaseBlock
, &TimeStamp
);
1057 DPRINT1("There's no enough memory to get the header\n");
1058 return STATUS_INSUFFICIENT_RESOURCES
;
1065 DPRINT1("The hive is not an actual registry hive file\n");
1066 return STATUS_NOT_REGISTRY_FILE
;
1069 /* Hive data needs a repair */
1073 * FIXME: We must be handling this status
1074 * case if the header isn't corrupt but
1075 * the counter sequences do not match but
1076 * due to a hack in HvLoadHive we have
1077 * to do both a header + data recovery.
1078 * RecoverHeader also implies RecoverData
1079 * anyway. When HvLoadHive gets rid of
1080 * that hack, data recovery must be done
1081 * after we read the hive block by block.
1086 /* Hive header needs a repair */
1088 /* FIXME: See the comment above (near HvpQueryHiveSize) */
1089 #if defined(_M_AMD64)
1091 return STATUS_REGISTRY_CORRUPT
;
1095 /* Check if this hive has a log at hand to begin with */
1096 #if (NTDDI_VERSION < NTDDI_VISTA)
1099 DPRINT1("The hive has no log for header recovery\n");
1100 return STATUS_REGISTRY_CORRUPT
;
1104 /* The header needs to be recovered so do it */
1105 DPRINT1("Attempting to heal the header...\n");
1106 Result2
= HvpRecoverHeaderFromLog(Hive
, &TimeStamp
, &BaseBlock
);
1107 if (Result2
== NoMemory
)
1109 DPRINT1("There's no enough memory to recover header from log\n");
1110 return STATUS_INSUFFICIENT_RESOURCES
;
1114 if (Result2
== Fail
)
1116 DPRINT1("Failed to recover the hive header\n");
1117 return STATUS_REGISTRY_CORRUPT
;
1120 /* Did we trigger the self-heal mode? */
1121 if (Result2
== SelfHeal
)
1123 HiveSelfHeal
= TRUE
;
1126 /* Now recover the data */
1127 Result2
= HvpRecoverDataFromLog(Hive
, BaseBlock
);
1128 if (Result2
== Fail
)
1130 DPRINT1("Failed to recover the hive data\n");
1131 return STATUS_REGISTRY_CORRUPT
;
1134 /* Tag the boot as self heal if we haven't done it before */
1135 if ((Result2
== SelfHeal
) && (!HiveSelfHeal
))
1137 HiveSelfHeal
= TRUE
;
1145 /* Set the boot type */
1146 BaseBlock
->BootType
= HiveSelfHeal
? HBOOT_TYPE_SELF_HEAL
: HBOOT_TYPE_REGULAR
;
1148 /* Setup hive data */
1149 Hive
->BaseBlock
= BaseBlock
;
1150 Hive
->Version
= BaseBlock
->Minor
;
1152 /* Allocate a buffer large enough to hold the hive */
1153 FileSize
= HBLOCK_SIZE
+ BaseBlock
->Length
; // == sizeof(HBASE_BLOCK) + BaseBlock->Length;
1154 HiveData
= Hive
->Allocate(FileSize
, TRUE
, TAG_CM
);
1157 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
1158 DPRINT1("There's no enough memory to allocate hive data\n");
1159 return STATUS_INSUFFICIENT_RESOURCES
;
1162 /* HACK (see explanation below): Now read the whole hive */
1163 Success
= Hive
->FileRead(Hive
,
1170 DPRINT1("Failed to read the whole hive\n");
1171 Hive
->Free(HiveData
, FileSize
);
1172 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
1173 return STATUS_NOT_REGISTRY_FILE
;
1177 * HACK (FIXME): Free our base block... it's useless in
1178 * this implementation.
1180 * And it's useless because while the idea of reading the
1181 * hive from physical file is correct, the implementation
1182 * is hacky and incorrect. Instead of reading the whole hive,
1183 * we should be instead reading the hive block by block,
1184 * deconstruct the block buffer and enlist the bins and
1185 * prepare the storage for the hive. What we currently do
1186 * is we try to initialize the hive storage and bins enlistment
1187 * by calling HvpInitializeMemoryHive below. This mixes
1188 * HINIT_FILE and HINIT_MEMORY together which is disgusting
1189 * because HINIT_FILE implementation shouldn't be calling
1190 * HvpInitializeMemoryHive.
1192 Hive
->Free(BaseBlock
, Hive
->BaseBlockAlloc
);
1193 Status
= HvpInitializeMemoryHive(Hive
, HiveData
, FileName
);
1194 if (!NT_SUCCESS(Status
))
1196 DPRINT1("Failed to initialize hive from memory\n");
1197 Hive
->Free(HiveData
, FileSize
);
1202 * If we have done some sort of recovery against
1203 * the hive we were going to load it from file,
1204 * tell the caller we did recover it. The caller
1205 * is responsible to flush the data later on.
1207 return (Result
== RecoverHeader
) ? STATUS_REGISTRY_RECOVERED
: STATUS_SUCCESS
;
1212 * Initializes a registry hive. It allocates a hive
1213 * descriptor and sets up the hive type depending
1214 * on the type chosen by the caller.
1216 * @param[in,out] RegistryHive
1217 * A pointer to a hive descriptor to be initialized.
1219 * @param[in] OperationType
1220 * The operation type to choose for hive initialization.
1221 * For further information about this, see Remarks.
1223 * @param[in] HiveFlags
1224 * A hive flag. Such flag is used to determine what kind
1225 * of action must be taken into the hive or what aspects
1226 * must be taken into account for such hive. For further
1227 * information, see Remarks.
1229 * @param[in] FileType
1230 * Hive file type. For the newly initialized hive, you can
1231 * choose from three different types for the hive:
1233 * HFILE_TYPE_PRIMARY - Initializes a hive as primary hive
1236 * HFILE_TYPE_LOG - The newly created hive is a hive log.
1237 * Logs don't exist per se but they're accompanied with their
1238 * associated primary hives. The Log field member of the hive
1239 * descriptor is set to TRUE.
1241 * HFILE_TYPE_EXTERNAL - The newly created hive is a portable
1242 * hive, that can be used and copied for different machines,
1243 * unlike primary hives.
1245 * HFILE_TYPE_ALTERNATE - The newly created hive is an alternate hive.
1246 * Technically speaking it is the same as a primary hive (the representation
1247 * of on-disk image of the registry header is HFILE_TYPE_PRIMARY), with
1248 * the purpose is to serve as a backup hive. The Alternate field of the
1249 * hive descriptor is set to TRUE. Only the SYSTEM hive has a backup
1252 * @param[in] HiveData
1253 * An arbitrary pointer that points to the hive data. Usually this
1254 * data is in form of a hive base block given by the caller of this
1257 * @param[in] Allocate
1258 * A pointer to a ALLOCATE_ROUTINE function that describes
1259 * the main allocation routine for this hive. This parameter
1263 * A pointer to a FREE_ROUTINE function that describes the
1264 * the main memory freeing routine for this hive. This parameter
1267 * @param[in] FileSetSize
1268 * A pointer to a FILE_SET_SIZE_ROUTINE function that describes
1269 * the file set size routine for this hive. This parameter
1272 * @param[in] FileWrite
1273 * A pointer to a FILE_WRITE_ROUTINE function that describes
1274 * the file writing routine for this hive. This parameter
1277 * @param[in] FileRead
1278 * A pointer to a FILE_READ_ROUTINE function that describes
1279 * the file reading routine for this hive. This parameter
1282 * @param[in] FileFlush
1283 * A pointer to a FILE_FLUSH_ROUTINE function that describes
1284 * the file flushing routine for this hive. This parameter
1287 * @param[in] Cluster
1288 * The registry hive cluster to be set. Usually this value
1291 * @param[in] FileName
1292 * A to a NULL-terminated Unicode string structure containing
1293 * the hive file name. This parameter can be NULL.
1296 * Returns STATUS_SUCCESS if the function has successfully
1297 * initialized the hive. STATUS_REGISTRY_RECOVERED is returned
1298 * if the hive has subdued previous damage and it's been recovered.
1299 * This function will perform a hive writing and flushing with
1300 * healthy and recovered data in that case. STATUS_REGISTRY_IO_FAILED
1301 * is returned if registry hive writing/flushing of recovered data
1302 * has failed. STATUS_INVALID_PARAMETER is returned if an invalid
1303 * operation type pointed by OperationType parameter has been
1304 * submitted. A failure NTSTATUS code is returned otherwise.
1307 * OperationType parameter influences how should the hive be
1308 * initialized. These are the following supported operation
1311 * HINIT_CREATE -- Creates a new fresh hive.
1313 * HINIT_MEMORY -- Initializes a registry hive that already exists
1314 * from memory. The hive data is copied from the
1315 * loaded hive in memory and used for read/write
1318 * HINIT_FLAT -- Initializes a flat registry hive, with data that can
1319 * only be read and not written into. Cells are always
1320 * allocated on a flat hive.
1322 * HINIT_FILE -- Initializes a hive from a hive file from the physical
1323 * backing storage of the system. In this situation the
1324 * function will perform self-healing and resuscitation
1325 * procedures if data read from the physical hive file
1328 * HINIT_MEMORY_INPLACE -- This operation type is similar to HINIT_FLAT,
1329 * with the difference is that the hive is initialized
1330 * with hive data from memory. The hive can only be read
1331 * and not written into.
1333 * HINIT_MAPFILE -- Initializes a hive from a hive file from the physical
1334 * backing storage of the system. Unlike HINIT_FILE, the
1335 * initialized hive is not backed to paged pool in memory
1336 * but rather through mapping views.
1338 * Alongside the operation type, the hive flags also influence the aspect
1339 * of the newly initialized hive. These are the following supported hive
1342 * HIVE_VOLATILE -- Tells the function that this hive will be volatile, that
1343 * is, the data stored inside the hive space resides only
1344 * in volatile memory of the system, aka the RAM, and the
1345 * data will be erased upon shutdown of the system.
1347 * HIVE_NOLAZYFLUSH -- Tells the function that no lazy flushing must be
1348 * done to this hive.
1353 _Inout_ PHHIVE RegistryHive
,
1354 _In_ ULONG OperationType
,
1355 _In_ ULONG HiveFlags
,
1356 _In_ ULONG FileType
,
1357 _In_opt_ PVOID HiveData
,
1358 _In_opt_ PALLOCATE_ROUTINE Allocate
,
1359 _In_opt_ PFREE_ROUTINE Free
,
1360 _In_opt_ PFILE_SET_SIZE_ROUTINE FileSetSize
,
1361 _In_opt_ PFILE_WRITE_ROUTINE FileWrite
,
1362 _In_opt_ PFILE_READ_ROUTINE FileRead
,
1363 _In_opt_ PFILE_FLUSH_ROUTINE FileFlush
,
1365 _In_opt_ PCUNICODE_STRING FileName
)
1368 PHHIVE Hive
= RegistryHive
;
1371 * Create a new hive structure that will hold all the maintenance data.
1374 RtlZeroMemory(Hive
, sizeof(HHIVE
));
1375 Hive
->Signature
= HV_HHIVE_SIGNATURE
;
1377 Hive
->Allocate
= Allocate
;
1379 Hive
->FileSetSize
= FileSetSize
;
1380 Hive
->FileWrite
= FileWrite
;
1381 Hive
->FileRead
= FileRead
;
1382 Hive
->FileFlush
= FileFlush
;
1384 Hive
->RefreshCount
= 0;
1385 Hive
->StorageTypeCount
= HTYPE_COUNT
;
1386 Hive
->Cluster
= Cluster
;
1387 Hive
->BaseBlockAlloc
= sizeof(HBASE_BLOCK
); // == HBLOCK_SIZE
1389 Hive
->Version
= HSYS_MINOR
;
1390 #if (NTDDI_VERSION < NTDDI_VISTA)
1391 Hive
->Log
= (FileType
== HFILE_TYPE_LOG
);
1392 Hive
->Alternate
= (FileType
== HFILE_TYPE_ALTERNATE
);
1394 Hive
->HiveFlags
= HiveFlags
& ~HIVE_NOLAZYFLUSH
;
1396 // TODO: The CellRoutines point to different callbacks
1397 // depending on the OperationType.
1398 Hive
->GetCellRoutine
= HvpGetCellData
;
1399 Hive
->ReleaseCellRoutine
= NULL
;
1401 switch (OperationType
)
1405 /* Create a new fresh hive */
1406 Status
= HvpCreateHive(Hive
, FileName
);
1412 /* Initialize a hive from memory */
1413 Status
= HvpInitializeMemoryHive(Hive
, HiveData
, FileName
);
1419 /* Initialize a flat read-only hive */
1420 Status
= HvpInitializeFlatHive(Hive
, HiveData
);
1426 /* Initialize a hive by loading it from physical file in backing storage */
1427 Status
= HvLoadHive(Hive
, FileName
);
1428 if ((Status
!= STATUS_SUCCESS
) &&
1429 (Status
!= STATUS_REGISTRY_RECOVERED
))
1431 /* Unrecoverable failure */
1432 DPRINT1("Registry hive couldn't be initialized, it's corrupt (hive 0x%p)\n", Hive
);
1436 /* FIXME: See the comment above (near HvpQueryHiveSize) */
1437 #if !defined(_M_AMD64)
1439 * Check if we have recovered this hive. We are responsible to
1440 * flush the primary hive back to backing storage afterwards.
1442 if (Status
== STATUS_REGISTRY_RECOVERED
)
1444 if (!HvSyncHiveFromRecover(Hive
))
1446 DPRINT1("Fail to write healthy data back to hive\n");
1447 return STATUS_REGISTRY_IO_FAILED
;
1451 * We are saved from hell, now clear out the
1452 * dirty bits and dirty count.
1454 * FIXME: We must as well clear out the log
1455 * and reset its size to 0 but we are lacking
1456 * in code that deals with log growing/shrinking
1457 * management. When the time comes to implement
1458 * this stuff we must set the LogSize and file size
1461 RtlClearAllBits(&Hive
->DirtyVector
);
1462 Hive
->DirtyCount
= 0;
1465 * Masquerade the status code as success.
1466 * STATUS_REGISTRY_RECOVERED is not a failure
1467 * code but not STATUS_SUCCESS either so the caller
1468 * thinks we failed at our job.
1470 Status
= STATUS_SUCCESS
;
1476 case HINIT_MEMORY_INPLACE
:
1478 // Status = HvpInitializeMemoryInplaceHive(Hive, HiveData);
1480 DPRINT1("HINIT_MEMORY_INPLACE is UNIMPLEMENTED\n");
1481 return STATUS_NOT_IMPLEMENTED
;
1486 DPRINT1("HINIT_MAPFILE is UNIMPLEMENTED\n");
1487 return STATUS_NOT_IMPLEMENTED
;
1492 DPRINT1("Invalid operation type (OperationType = %lu)\n", OperationType
);
1493 return STATUS_INVALID_PARAMETER
;
1502 * Frees all the bins within the storage, the dirty vector
1503 * and the base block associated with the given registry
1506 * @param[in] RegistryHive
1507 * A pointer to a hive descriptor where all of its data
1513 _In_ PHHIVE RegistryHive
)
1515 if (!RegistryHive
->ReadOnly
)
1517 /* Release hive bitmap */
1518 if (RegistryHive
->DirtyVector
.Buffer
)
1520 RegistryHive
->Free(RegistryHive
->DirtyVector
.Buffer
, 0);
1523 HvpFreeHiveBins(RegistryHive
);
1525 /* Free the BaseBlock */
1526 if (RegistryHive
->BaseBlock
)
1528 RegistryHive
->Free(RegistryHive
->BaseBlock
, RegistryHive
->BaseBlockAlloc
);
1529 RegistryHive
->BaseBlock
= NULL
;