mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 12:26:32 +00:00
ee5338ff13
CORE-19724 sdk/lib/cmlib/cmcheck.c: Print the HCELL_INDEX indices in hexadecimal. ------------------ sdk/lib/cmlib/cmcheck.c:321:60: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'ULONG' {aka 'unsigned int'} [-Wformat=] and at lines 341, 357, 374, 554, 579, 733, 760, 801, 984, 1003, 1458, 1476, 1521, 1551, 1670 sdk/lib/cmlib/cmcheck.c:1251:65: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'HCELL_INDEX' {aka 'unsigned int'} [-Wformat=] and at lines 1282, 1303, 1551 sdk/lib/cmlib/cmcheck.c:1326:79: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'LONG' {aka 'int'} [-Wformat=] sdk/lib/cmlib/cmcheck.c:1684:76: warning: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'CM_CHECK_REGISTRY_STATUS' {aka 'unsigned int'} [-Wformat=] and at line 1711 ------------------ sdk/lib/cmlib/cmheal.c:223:108: warning: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'ULONG' {aka 'unsigned int'} [-Wformat=] and at lines 240, 319, 335 sdk/lib/cmlib/cmheal.c:480:79: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'int' [-Wformat=] ------------------ sdk/lib/cmlib/hiveinit.c:976:62: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'ULONG' {aka 'unsigned int'} [-Wformat=] and at lines 988, 1492
1535 lines
49 KiB
C
1535 lines
49 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
* PURPOSE: Configuration Manager Library - Registry Hive Loading & Initialization
|
|
* COPYRIGHT: Copyright 2001 - 2005 Eric Kohl
|
|
* Copyright 2005 Filip Navara <navaraf@reactos.org>
|
|
* Copyright 2021 Max Korostil
|
|
* Copyright 2022 George Bișoc <george.bisoc@reactos.org>
|
|
*/
|
|
|
|
#include "cmlib.h"
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* ENUMERATIONS *************************************************************/
|
|
|
|
typedef enum _RESULT
|
|
{
|
|
NotHive,
|
|
Fail,
|
|
NoMemory,
|
|
HiveSuccess,
|
|
RecoverHeader,
|
|
RecoverData,
|
|
SelfHeal
|
|
} RESULT;
|
|
|
|
/* PRIVATE FUNCTIONS ********************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the base block header of a registry
|
|
* file (hive or log).
|
|
*
|
|
* @param[in] BaseBlock
|
|
* A pointer to a base block header to
|
|
* be validated.
|
|
*
|
|
* @param[in] FileType
|
|
* The file type of a registry file to check
|
|
* against the file type of the base block.
|
|
*
|
|
* @return
|
|
* Returns TRUE if the base block header is valid,
|
|
* FALSE otherwise.
|
|
*/
|
|
BOOLEAN
|
|
CMAPI
|
|
HvpVerifyHiveHeader(
|
|
_In_ PHBASE_BLOCK BaseBlock,
|
|
_In_ ULONG FileType)
|
|
{
|
|
if (BaseBlock->Signature != HV_HBLOCK_SIGNATURE ||
|
|
BaseBlock->Major != HSYS_MAJOR ||
|
|
BaseBlock->Minor < HSYS_MINOR ||
|
|
BaseBlock->Type != FileType ||
|
|
BaseBlock->Format != HBASE_FORMAT_MEMORY ||
|
|
BaseBlock->Cluster != 1 ||
|
|
BaseBlock->Sequence1 != BaseBlock->Sequence2 ||
|
|
HvpHiveHeaderChecksum(BaseBlock) != BaseBlock->CheckSum)
|
|
{
|
|
DPRINT1("Verify Hive Header failed:\n");
|
|
DPRINT1(" Signature: 0x%x, expected 0x%x; Major: 0x%x, expected 0x%x\n",
|
|
BaseBlock->Signature, HV_HBLOCK_SIGNATURE, BaseBlock->Major, HSYS_MAJOR);
|
|
DPRINT1(" Minor: 0x%x expected to be >= 0x%x; Type: 0x%x, expected 0x%x\n",
|
|
BaseBlock->Minor, HSYS_MINOR, BaseBlock->Type, FileType);
|
|
DPRINT1(" Format: 0x%x, expected 0x%x; Cluster: 0x%x, expected 1\n",
|
|
BaseBlock->Format, HBASE_FORMAT_MEMORY, BaseBlock->Cluster);
|
|
DPRINT1(" Sequence: 0x%x, expected 0x%x; Checksum: 0x%x, expected 0x%x\n",
|
|
BaseBlock->Sequence1, BaseBlock->Sequence2,
|
|
HvpHiveHeaderChecksum(BaseBlock), BaseBlock->CheckSum);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Frees all the bins within storage space
|
|
* associated with a hive descriptor.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor where
|
|
* all the bins are to be freed.
|
|
*/
|
|
VOID
|
|
CMAPI
|
|
HvpFreeHiveBins(
|
|
_In_ PHHIVE Hive)
|
|
{
|
|
ULONG i;
|
|
PHBIN Bin;
|
|
ULONG Storage;
|
|
|
|
for (Storage = 0; Storage < Hive->StorageTypeCount; Storage++)
|
|
{
|
|
Bin = NULL;
|
|
for (i = 0; i < Hive->Storage[Storage].Length; i++)
|
|
{
|
|
if (Hive->Storage[Storage].BlockList[i].BinAddress == (ULONG_PTR)NULL)
|
|
continue;
|
|
if (Hive->Storage[Storage].BlockList[i].BinAddress != (ULONG_PTR)Bin)
|
|
{
|
|
Bin = (PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress;
|
|
Hive->Free((PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress, 0);
|
|
}
|
|
Hive->Storage[Storage].BlockList[i].BinAddress = (ULONG_PTR)NULL;
|
|
Hive->Storage[Storage].BlockList[i].BlockAddress = (ULONG_PTR)NULL;
|
|
}
|
|
|
|
if (Hive->Storage[Storage].Length)
|
|
Hive->Free(Hive->Storage[Storage].BlockList, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Allocates a cluster-aligned hive base header block.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor where
|
|
* the header block allocator function is to
|
|
* be gathered from.
|
|
*
|
|
* @param[in] Paged
|
|
* If set to TRUE, the allocated base block will reside
|
|
* in paged pool, otherwise it will reside in non paged
|
|
* pool.
|
|
*
|
|
* @param[in] Tag
|
|
* A tag name to supply for the allocated memory block
|
|
* for identification. This is for debugging purposes.
|
|
*
|
|
* @return
|
|
* Returns an allocated base block header if the function
|
|
* succeeds, otherwise it returns NULL.
|
|
*/
|
|
static
|
|
__inline
|
|
PHBASE_BLOCK
|
|
HvpAllocBaseBlockAligned(
|
|
_In_ PHHIVE Hive,
|
|
_In_ BOOLEAN Paged,
|
|
_In_ ULONG Tag)
|
|
{
|
|
PHBASE_BLOCK BaseBlock;
|
|
ULONG Alignment;
|
|
|
|
ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster));
|
|
|
|
/* Allocate the buffer */
|
|
BaseBlock = Hive->Allocate(Hive->BaseBlockAlloc, Paged, Tag);
|
|
if (!BaseBlock) return NULL;
|
|
|
|
/* Check for, and enforce, alignment */
|
|
Alignment = Hive->Cluster * HSECTOR_SIZE -1;
|
|
if ((ULONG_PTR)BaseBlock & Alignment)
|
|
{
|
|
/* Free the old header and reallocate a new one, always paged */
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
BaseBlock = Hive->Allocate(PAGE_SIZE, TRUE, Tag);
|
|
if (!BaseBlock) return NULL;
|
|
|
|
Hive->BaseBlockAlloc = PAGE_SIZE;
|
|
}
|
|
|
|
return BaseBlock;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Initializes a NULL-terminated Unicode hive file name
|
|
* of a hive header by copying the last 31 characters of
|
|
* the hive file name. Mainly used for debugging purposes.
|
|
*
|
|
* @param[in,out] BaseBlock
|
|
* A pointer to a base block header where the hive
|
|
* file name is to be copied to.
|
|
*
|
|
* @param[in] FileName
|
|
* A pointer to a Unicode string structure containing
|
|
* the hive file name to be copied from. If this argument
|
|
* is NULL, the base block will not have any hive file name.
|
|
*/
|
|
static
|
|
VOID
|
|
HvpInitFileName(
|
|
_Inout_ PHBASE_BLOCK BaseBlock,
|
|
_In_opt_ PCUNICODE_STRING FileName)
|
|
{
|
|
ULONG_PTR Offset;
|
|
SIZE_T Length;
|
|
|
|
/* Always NULL-initialize */
|
|
RtlZeroMemory(BaseBlock->FileName, (HIVE_FILENAME_MAXLEN + 1) * sizeof(WCHAR));
|
|
|
|
/* Copy the 31 last characters of the hive file name if any */
|
|
if (!FileName) return;
|
|
|
|
if (FileName->Length / sizeof(WCHAR) <= HIVE_FILENAME_MAXLEN)
|
|
{
|
|
Offset = 0;
|
|
Length = FileName->Length;
|
|
}
|
|
else
|
|
{
|
|
Offset = FileName->Length / sizeof(WCHAR) - HIVE_FILENAME_MAXLEN;
|
|
Length = HIVE_FILENAME_MAXLEN * sizeof(WCHAR);
|
|
}
|
|
|
|
RtlCopyMemory(BaseBlock->FileName, FileName->Buffer + Offset, Length);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Initializes a hive descriptor structure for a
|
|
* newly created hive in memory.
|
|
*
|
|
* @param[in,out] RegistryHive
|
|
* A pointer to a registry hive descriptor where
|
|
* the internal structures field are to be initialized
|
|
* for the said hive.
|
|
*
|
|
* @param[in] FileName
|
|
* A pointer to a Unicode string structure containing
|
|
* the hive file name to be copied from. If this argument
|
|
* is NULL, the base block will not have any hive file name.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has created the
|
|
* hive descriptor successfully. STATUS_NO_MEMORY is returned
|
|
* if the base header block could not be allocated.
|
|
*/
|
|
NTSTATUS
|
|
CMAPI
|
|
HvpCreateHive(
|
|
_Inout_ PHHIVE RegistryHive,
|
|
_In_opt_ PCUNICODE_STRING FileName)
|
|
{
|
|
PHBASE_BLOCK BaseBlock;
|
|
ULONG Index;
|
|
|
|
/* Allocate the base block */
|
|
BaseBlock = HvpAllocBaseBlockAligned(RegistryHive, FALSE, TAG_CM);
|
|
if (BaseBlock == NULL)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
/* Clear it */
|
|
RtlZeroMemory(BaseBlock, RegistryHive->BaseBlockAlloc);
|
|
|
|
BaseBlock->Signature = HV_HBLOCK_SIGNATURE;
|
|
BaseBlock->Major = HSYS_MAJOR;
|
|
BaseBlock->Minor = HSYS_MINOR;
|
|
BaseBlock->Type = HFILE_TYPE_PRIMARY;
|
|
BaseBlock->Format = HBASE_FORMAT_MEMORY;
|
|
BaseBlock->Cluster = 1;
|
|
BaseBlock->RootCell = HCELL_NIL;
|
|
BaseBlock->Length = 0;
|
|
BaseBlock->Sequence1 = 1;
|
|
BaseBlock->Sequence2 = 1;
|
|
BaseBlock->TimeStamp.QuadPart = 0ULL;
|
|
|
|
/*
|
|
* No need to compute the checksum since
|
|
* the hive resides only in memory so far.
|
|
*/
|
|
BaseBlock->CheckSum = 0;
|
|
|
|
/* Set default boot type */
|
|
BaseBlock->BootType = HBOOT_TYPE_REGULAR;
|
|
|
|
/* Setup hive data */
|
|
RegistryHive->BaseBlock = BaseBlock;
|
|
RegistryHive->Version = BaseBlock->Minor; // == HSYS_MINOR
|
|
|
|
for (Index = 0; Index < 24; Index++)
|
|
{
|
|
RegistryHive->Storage[Stable].FreeDisplay[Index] = HCELL_NIL;
|
|
RegistryHive->Storage[Volatile].FreeDisplay[Index] = HCELL_NIL;
|
|
}
|
|
|
|
HvpInitFileName(BaseBlock, FileName);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Initializes a hive descriptor from an already loaded
|
|
* registry hive stored in memory. The data of the hive is
|
|
* copied and it is prepared for read/write access.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a registry hive descriptor where
|
|
* the internal structures field are to be initialized
|
|
* from hive data that is already loaded in memory.
|
|
*
|
|
* @param[in] ChunkBase
|
|
* A pointer to a valid base block header containing
|
|
* registry header data for initialization.
|
|
*
|
|
* @param[in] FileName
|
|
* A pointer to a Unicode string structure containing
|
|
* the hive file name to be copied from. If this argument
|
|
* is NULL, the base block will not have any hive file name.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has initialized the
|
|
* hive descriptor successfully. STATUS_REGISTRY_CORRUPT is
|
|
* returned if the base block header contains invalid header
|
|
* data. STATUS_NO_MEMORY is returned if memory could not
|
|
* be allocated for registry stuff.
|
|
*/
|
|
NTSTATUS
|
|
CMAPI
|
|
HvpInitializeMemoryHive(
|
|
_In_ PHHIVE Hive,
|
|
_In_ PHBASE_BLOCK ChunkBase,
|
|
_In_opt_ PCUNICODE_STRING FileName)
|
|
{
|
|
SIZE_T BlockIndex;
|
|
PHBIN Bin, NewBin;
|
|
ULONG i;
|
|
ULONG BitmapSize;
|
|
PULONG BitmapBuffer;
|
|
SIZE_T ChunkSize;
|
|
|
|
ChunkSize = ChunkBase->Length;
|
|
DPRINT("ChunkSize: %zx\n", ChunkSize);
|
|
|
|
if (ChunkSize < sizeof(HBASE_BLOCK) ||
|
|
!HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY))
|
|
{
|
|
DPRINT1("Registry is corrupt: ChunkSize 0x%zx < sizeof(HBASE_BLOCK) 0x%zx, "
|
|
"or HvpVerifyHiveHeader() failed\n", ChunkSize, sizeof(HBASE_BLOCK));
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
|
|
/* Allocate the base block */
|
|
Hive->BaseBlock = HvpAllocBaseBlockAligned(Hive, FALSE, TAG_CM);
|
|
if (Hive->BaseBlock == NULL)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
RtlCopyMemory(Hive->BaseBlock, ChunkBase, sizeof(HBASE_BLOCK));
|
|
|
|
/* Setup hive data */
|
|
Hive->Version = ChunkBase->Minor;
|
|
|
|
/*
|
|
* Build a block list from the in-memory chunk and copy the data as
|
|
* we go.
|
|
*/
|
|
|
|
Hive->Storage[Stable].Length = (ULONG)(ChunkSize / HBLOCK_SIZE);
|
|
Hive->Storage[Stable].BlockList =
|
|
Hive->Allocate(Hive->Storage[Stable].Length *
|
|
sizeof(HMAP_ENTRY), FALSE, TAG_CM);
|
|
if (Hive->Storage[Stable].BlockList == NULL)
|
|
{
|
|
DPRINT1("Allocating block list failed\n");
|
|
Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
for (BlockIndex = 0; BlockIndex < Hive->Storage[Stable].Length; )
|
|
{
|
|
Bin = (PHBIN)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HBLOCK_SIZE);
|
|
if (Bin->Signature != HV_HBIN_SIGNATURE ||
|
|
(Bin->Size % HBLOCK_SIZE) != 0 ||
|
|
(Bin->FileOffset / HBLOCK_SIZE) != BlockIndex)
|
|
{
|
|
/*
|
|
* Bin is toast but luckily either the signature, size or offset
|
|
* is out of order. For the signature it is obvious what we are going
|
|
* to do, for the offset we are re-positioning the bin back to where it
|
|
* was and for the size we will set it up to a block size, since technically
|
|
* a hive bin is large as a block itself to accommodate cells.
|
|
*/
|
|
if (!CmIsSelfHealEnabled(FALSE))
|
|
{
|
|
DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x. Self-heal not possible!\n",
|
|
(unsigned long)BlockIndex, (unsigned)Bin->Signature, (unsigned)Bin->Size);
|
|
Hive->Free(Hive->Storage[Stable].BlockList, 0);
|
|
Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
|
|
/* Fix this bin */
|
|
Bin->Signature = HV_HBIN_SIGNATURE;
|
|
Bin->Size = HBLOCK_SIZE;
|
|
Bin->FileOffset = BlockIndex * HBLOCK_SIZE;
|
|
ChunkBase->BootType |= HBOOT_TYPE_SELF_HEAL;
|
|
DPRINT1("Bin at index %lu is corrupt and it has been repaired!\n", (unsigned long)BlockIndex);
|
|
}
|
|
|
|
NewBin = Hive->Allocate(Bin->Size, TRUE, TAG_CM);
|
|
if (NewBin == NULL)
|
|
{
|
|
Hive->Free(Hive->Storage[Stable].BlockList, 0);
|
|
Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
Hive->Storage[Stable].BlockList[BlockIndex].BinAddress = (ULONG_PTR)NewBin;
|
|
Hive->Storage[Stable].BlockList[BlockIndex].BlockAddress = (ULONG_PTR)NewBin;
|
|
|
|
RtlCopyMemory(NewBin, Bin, Bin->Size);
|
|
|
|
if (Bin->Size > HBLOCK_SIZE)
|
|
{
|
|
for (i = 1; i < Bin->Size / HBLOCK_SIZE; i++)
|
|
{
|
|
Hive->Storage[Stable].BlockList[BlockIndex + i].BinAddress = (ULONG_PTR)NewBin;
|
|
Hive->Storage[Stable].BlockList[BlockIndex + i].BlockAddress =
|
|
((ULONG_PTR)NewBin + (i * HBLOCK_SIZE));
|
|
}
|
|
}
|
|
|
|
BlockIndex += Bin->Size / HBLOCK_SIZE;
|
|
}
|
|
|
|
if (!NT_SUCCESS(HvpCreateHiveFreeCellList(Hive)))
|
|
{
|
|
HvpFreeHiveBins(Hive);
|
|
Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
BitmapSize = ROUND_UP(Hive->Storage[Stable].Length,
|
|
sizeof(ULONG) * 8) / 8;
|
|
BitmapBuffer = (PULONG)Hive->Allocate(BitmapSize, TRUE, TAG_CM);
|
|
if (BitmapBuffer == NULL)
|
|
{
|
|
HvpFreeHiveBins(Hive);
|
|
Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Initializes a hive descriptor for an already loaded hive
|
|
* that is stored in memory. This descriptor serves to denote
|
|
* such hive as being "flat", that is, the data and properties
|
|
* can be only read and not written into.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a registry hive descriptor where
|
|
* the internal structures fields are to be initialized
|
|
* from hive data that is already loaded in memory. Such
|
|
* hive descriptor will become read-only and flat.
|
|
*
|
|
* @param[in] ChunkBase
|
|
* A pointer to a valid base block header containing
|
|
* registry header data for initialization.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has initialized the
|
|
* flat hive descriptor. STATUS_REGISTRY_CORRUPT is returned if
|
|
* the base block header contains invalid header data.
|
|
*/
|
|
NTSTATUS
|
|
CMAPI
|
|
HvpInitializeFlatHive(
|
|
_In_ PHHIVE Hive,
|
|
_In_ PHBASE_BLOCK ChunkBase)
|
|
{
|
|
if (!HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY))
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
|
|
/* Setup hive data */
|
|
Hive->BaseBlock = ChunkBase;
|
|
Hive->Version = ChunkBase->Minor;
|
|
Hive->Flat = TRUE;
|
|
Hive->ReadOnly = TRUE;
|
|
|
|
Hive->StorageTypeCount = 1;
|
|
|
|
/* Set default boot type */
|
|
ChunkBase->BootType = HBOOT_TYPE_REGULAR;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Retrieves the base block hive header from the
|
|
* primary hive file stored in physical backing storage.
|
|
* This function may invoke a self-healing warning if
|
|
* hive header couldn't be obtained. See Return and Remarks
|
|
* sections for further information.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a registry hive descriptor that points
|
|
* to the primary hive being loaded. This descriptor is
|
|
* needed to obtain the hive header block from said hive.
|
|
*
|
|
* @param[in,out] HiveBaseBlock
|
|
* A pointer returned by the function that contains
|
|
* the hive header base block buffer obtained from
|
|
* the primary hive file pointed by the Hive argument.
|
|
* This parameter must not be NULL!
|
|
*
|
|
* @param[in,out] TimeStamp
|
|
* A pointer returned by the function that contains
|
|
* the time-stamp of the registry hive file at the
|
|
* moment of creation or modification. This parameter
|
|
* must not be NULL!
|
|
*
|
|
* @return
|
|
* This function returns a result indicator. That is,
|
|
* HiveSuccess is returned if the hive header was obtained
|
|
* successfully. NoMemory is returned if the hive base block
|
|
* could not be allocated. NotHive is returned if the hive file
|
|
* that's been read isn't actually a hive. RecoverHeader is
|
|
* returned if the header needs to be recovered. RecoverData
|
|
* is returned if the hive data needs to be returned.
|
|
*
|
|
* @remarks
|
|
* RecoverHeader and RecoverData are status indicators that
|
|
* invoke a self-healing procedure if the hive header could not
|
|
* be obtained in a normal way and as a matter of fact the whole
|
|
* registry initialization procedure is orchestrated. RecoverHeader
|
|
* implies that the base block header of a hive is corrupt and it
|
|
* needs to be recovered, whereas RecoverData implies the registry
|
|
* data is corrupt. The latter status indicator is less severe unlike
|
|
* the former because the system can cope with data loss.
|
|
*/
|
|
RESULT
|
|
CMAPI
|
|
HvpGetHiveHeader(
|
|
_In_ PHHIVE Hive,
|
|
_Inout_ PHBASE_BLOCK *HiveBaseBlock,
|
|
_Inout_ PLARGE_INTEGER TimeStamp)
|
|
{
|
|
PHBASE_BLOCK BaseBlock;
|
|
ULONG Result;
|
|
ULONG FileOffset;
|
|
PHBIN FirstBin;
|
|
|
|
ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster));
|
|
|
|
/* Assume failure and allocate the base block */
|
|
*HiveBaseBlock = NULL;
|
|
BaseBlock = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM);
|
|
if (!BaseBlock)
|
|
{
|
|
DPRINT1("Failed to allocate an aligned base block buffer\n");
|
|
return NoMemory;
|
|
}
|
|
|
|
/* Clear it */
|
|
RtlZeroMemory(BaseBlock, sizeof(HBASE_BLOCK));
|
|
|
|
/* Now read it from disk */
|
|
FileOffset = 0;
|
|
Result = Hive->FileRead(Hive,
|
|
HFILE_TYPE_PRIMARY,
|
|
&FileOffset,
|
|
BaseBlock,
|
|
Hive->Cluster * HSECTOR_SIZE);
|
|
if (!Result)
|
|
{
|
|
/*
|
|
* Don't assume the hive is ultimately destroyed
|
|
* but instead try to read the first block of
|
|
* the first bin hive. So that we're sure of
|
|
* ourselves we can somewhat recover this hive.
|
|
*/
|
|
FileOffset = HBLOCK_SIZE;
|
|
Result = Hive->FileRead(Hive,
|
|
HFILE_TYPE_PRIMARY,
|
|
&FileOffset,
|
|
(PVOID)BaseBlock,
|
|
Hive->Cluster * HSECTOR_SIZE);
|
|
if (!Result)
|
|
{
|
|
DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n");
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
return NotHive;
|
|
}
|
|
|
|
/*
|
|
* Deconstruct the casted buffer we got
|
|
* into a hive bin. Check if the offset
|
|
* position is in the right place (namely
|
|
* its offset must be 0 because it's the first
|
|
* bin) and it should have a sane signature.
|
|
*/
|
|
FirstBin = (PHBIN)BaseBlock;
|
|
if (FirstBin->Signature != HV_HBIN_SIGNATURE ||
|
|
FirstBin->FileOffset != 0)
|
|
{
|
|
DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n");
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
return NotHive;
|
|
}
|
|
|
|
/*
|
|
* There's still hope for this hive so acknowledge the
|
|
* caller this hive needs a recoverable header.
|
|
*/
|
|
*TimeStamp = BaseBlock->TimeStamp;
|
|
DPRINT1("The hive is not fully corrupt, the base block needs to be RECOVERED\n");
|
|
return RecoverHeader;
|
|
}
|
|
|
|
/*
|
|
* This hive has a base block that's not maimed
|
|
* but is the header data valid?
|
|
*
|
|
* FIXME: We must check if primary and secondary
|
|
* sequences mismatch separately and fire up RecoverData
|
|
* in that case but due to a hack in HvLoadHive, let
|
|
* HvpVerifyHiveHeader check the sequences for now.
|
|
*/
|
|
if (!HvpVerifyHiveHeader(BaseBlock, HFILE_TYPE_PRIMARY))
|
|
{
|
|
DPRINT1("The hive base header block needs to be RECOVERED\n");
|
|
*TimeStamp = BaseBlock->TimeStamp;
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
return RecoverHeader;
|
|
}
|
|
|
|
/* Return information */
|
|
*HiveBaseBlock = BaseBlock;
|
|
*TimeStamp = BaseBlock->TimeStamp;
|
|
return HiveSuccess;
|
|
}
|
|
|
|
/*
|
|
* FIXME: Disable compilation for AMD64 for now since it makes
|
|
* the FreeLdr binary size so large it makes booting impossible.
|
|
*/
|
|
#if !defined(_M_AMD64)
|
|
/**
|
|
* @brief
|
|
* Computes the hive space size by querying
|
|
* the file size of the associated hive file.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor where the
|
|
* hive length size is to be calculated.
|
|
*
|
|
* @return
|
|
* Returns the computed hive size.
|
|
*/
|
|
ULONG
|
|
CMAPI
|
|
HvpQueryHiveSize(
|
|
_In_ PHHIVE Hive)
|
|
{
|
|
#if !defined(CMLIB_HOST) && !defined(_BLDR_)
|
|
NTSTATUS Status;
|
|
FILE_STANDARD_INFORMATION FileStandard;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
#endif
|
|
ULONG HiveSize = 0;
|
|
|
|
/*
|
|
* Query the file size of the physical hive
|
|
* file. We need that information in order
|
|
* to ensure how big the hive actually is.
|
|
*/
|
|
#if !defined(CMLIB_HOST) && !defined(_BLDR_)
|
|
Status = ZwQueryInformationFile(((PCMHIVE)Hive)->FileHandles[HFILE_TYPE_PRIMARY],
|
|
&IoStatusBlock,
|
|
&FileStandard,
|
|
sizeof(FILE_STANDARD_INFORMATION),
|
|
FileStandardInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwQueryInformationFile returned 0x%lx\n", Status);
|
|
return HiveSize;
|
|
}
|
|
|
|
/* Now compute the hive size */
|
|
HiveSize = FileStandard.EndOfFile.u.LowPart - HBLOCK_SIZE;
|
|
#endif
|
|
return HiveSize;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Recovers the base block header by obtaining
|
|
* it from a log file associated with the hive.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor associated
|
|
* with the log file where the hive header is
|
|
* to be read from.
|
|
*
|
|
* @param[in] TimeStamp
|
|
* A pointer to a time-stamp used to check
|
|
* if the provided time matches with that
|
|
* of the hive.
|
|
*
|
|
* @param[in,out] BaseBlock
|
|
* A pointer returned by the caller that contains
|
|
* the base block header that was read from the log.
|
|
* This base block could be also made manually by hand.
|
|
* See Remarks for further information.
|
|
*
|
|
* @return
|
|
* Returns HiveSuccess if the header was obtained
|
|
* normally from the log. NoMemory is returned if
|
|
* the base block header could not be allocated.
|
|
* Fail is returned if self-healing mode is disabled
|
|
* and the log couldn't be read or a write attempt
|
|
* to the primary hive has failed. SelfHeal is returned
|
|
* to indicate that self-heal mode goes further.
|
|
*
|
|
* @remarks
|
|
* When SelfHeal is returned this indicates that
|
|
* even the log we have gotten at hand is corrupt
|
|
* but since we do have at least a log our only hope
|
|
* is to reconstruct the pieces of the base header
|
|
* by hand.
|
|
*/
|
|
RESULT
|
|
CMAPI
|
|
HvpRecoverHeaderFromLog(
|
|
_In_ PHHIVE Hive,
|
|
_In_ PLARGE_INTEGER TimeStamp,
|
|
_Inout_ PHBASE_BLOCK *BaseBlock)
|
|
{
|
|
BOOLEAN Success;
|
|
PHBASE_BLOCK LogHeader;
|
|
ULONG FileOffset;
|
|
ULONG HiveSize;
|
|
BOOLEAN HeaderResuscitated;
|
|
|
|
/*
|
|
* The cluster must not be greater than what the
|
|
* base block can permit.
|
|
*/
|
|
ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster));
|
|
|
|
/* Assume we haven't resuscitated the header */
|
|
HeaderResuscitated = FALSE;
|
|
|
|
/* Allocate an aligned buffer for the log header */
|
|
LogHeader = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM);
|
|
if (!LogHeader)
|
|
{
|
|
DPRINT1("Failed to allocate memory for the log header\n");
|
|
return NoMemory;
|
|
}
|
|
|
|
/* Zero out our header buffer */
|
|
RtlZeroMemory(LogHeader, HSECTOR_SIZE);
|
|
|
|
/* Get the base header from the log */
|
|
FileOffset = 0;
|
|
Success = Hive->FileRead(Hive,
|
|
HFILE_TYPE_LOG,
|
|
&FileOffset,
|
|
LogHeader,
|
|
Hive->Cluster * HSECTOR_SIZE);
|
|
if (!Success ||
|
|
!HvpVerifyHiveHeader(LogHeader, HFILE_TYPE_LOG) ||
|
|
TimeStamp->HighPart != LogHeader->TimeStamp.HighPart ||
|
|
TimeStamp->LowPart != LogHeader->TimeStamp.LowPart)
|
|
{
|
|
/*
|
|
* We failed to read the base block header from
|
|
* the log, or the header itself or timestamp is
|
|
* invalid. Check if self healing is enabled.
|
|
*/
|
|
if (!CmIsSelfHealEnabled(FALSE))
|
|
{
|
|
DPRINT1("The log couldn't be read and self-healing mode is disabled\n");
|
|
Hive->Free(LogHeader, Hive->BaseBlockAlloc);
|
|
return Fail;
|
|
}
|
|
|
|
/*
|
|
* Determine the size of this hive so that
|
|
* we can estabilish the length of the base
|
|
* block we are trying to resuscitate.
|
|
*/
|
|
HiveSize = HvpQueryHiveSize(Hive);
|
|
if (HiveSize == 0)
|
|
{
|
|
DPRINT1("Failed to query the hive size\n");
|
|
Hive->Free(LogHeader, Hive->BaseBlockAlloc);
|
|
return Fail;
|
|
}
|
|
|
|
/*
|
|
* We can still resuscitate the base header if we
|
|
* could not grab one from the log by reconstructing
|
|
* the header internals by hand (this assumes the
|
|
* root cell is not NIL nor damaged). CmCheckRegistry
|
|
* does the ultimate judgement whether the root cell
|
|
* is fatally kaput or not after the hive has been
|
|
* initialized and loaded.
|
|
*
|
|
* For more information about base block header
|
|
* resuscitation, see https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md#notes-4.
|
|
*/
|
|
LogHeader->Signature = HV_HBLOCK_SIGNATURE;
|
|
LogHeader->Sequence1 = 1;
|
|
LogHeader->Sequence2 = 1;
|
|
LogHeader->Cluster = 1;
|
|
LogHeader->Length = HiveSize;
|
|
LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader);
|
|
|
|
/*
|
|
* Acknowledge that we have resuscitated
|
|
* the header.
|
|
*/
|
|
HeaderResuscitated = TRUE;
|
|
DPRINT1("Header has been resuscitated, triggering self-heal mode\n");
|
|
}
|
|
|
|
/*
|
|
* Tag this log header as a primary hive before
|
|
* writing it to the hive.
|
|
*/
|
|
LogHeader->Type = HFILE_TYPE_PRIMARY;
|
|
|
|
/*
|
|
* If we have not made attempts of recovering
|
|
* the header due to log corruption then we
|
|
* have to compute the checksum. This is
|
|
* already done when the header has been resuscitated
|
|
* so don't try to do it twice.
|
|
*/
|
|
if (!HeaderResuscitated)
|
|
{
|
|
LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader);
|
|
}
|
|
|
|
/* Write the header back to hive now */
|
|
Success = Hive->FileWrite(Hive,
|
|
HFILE_TYPE_PRIMARY,
|
|
&FileOffset,
|
|
LogHeader,
|
|
Hive->Cluster * HSECTOR_SIZE);
|
|
if (!Success)
|
|
{
|
|
DPRINT1("Couldn't write the base header to primary hive\n");
|
|
Hive->Free(LogHeader, Hive->BaseBlockAlloc);
|
|
return Fail;
|
|
}
|
|
|
|
*BaseBlock = LogHeader;
|
|
return HeaderResuscitated ? SelfHeal : HiveSuccess;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Recovers the registry data by obtaining it
|
|
* from a log that is associated with the hive.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor associated
|
|
* with the log file where the hive data is to
|
|
* be read from.
|
|
*
|
|
* @param[in] BaseBlock
|
|
* A pointer to a base block header.
|
|
*
|
|
* @return
|
|
* Returns HiveSuccess if the data was obtained
|
|
* normally from the log. Fail is returned if
|
|
* self-healing is disabled and we couldn't be
|
|
* able to read the data from the log or the
|
|
* dirty vector signature is garbage or we
|
|
* failed to write the data block to the primary
|
|
* hive. SelfHeal is returned to indicate that
|
|
* the log is corrupt and the system will continue
|
|
* to be recovered at the expense of data loss.
|
|
*/
|
|
RESULT
|
|
CMAPI
|
|
HvpRecoverDataFromLog(
|
|
_In_ PHHIVE Hive,
|
|
_In_ PHBASE_BLOCK BaseBlock)
|
|
{
|
|
BOOLEAN Success;
|
|
ULONG FileOffset;
|
|
ULONG BlockIndex;
|
|
ULONG LogIndex;
|
|
ULONG StorageLength;
|
|
UCHAR DirtyVector[HSECTOR_SIZE];
|
|
UCHAR Buffer[HBLOCK_SIZE];
|
|
|
|
/* Read the dirty data from the log */
|
|
FileOffset = HV_LOG_HEADER_SIZE;
|
|
Success = Hive->FileRead(Hive,
|
|
HFILE_TYPE_LOG,
|
|
&FileOffset,
|
|
DirtyVector,
|
|
HSECTOR_SIZE);
|
|
if (!Success)
|
|
{
|
|
if (!CmIsSelfHealEnabled(FALSE))
|
|
{
|
|
DPRINT1("The log couldn't be read and self-healing mode is disabled\n");
|
|
return Fail;
|
|
}
|
|
|
|
/*
|
|
* There's nothing we can do on a situation
|
|
* where dirty data could not be read from
|
|
* the log. It does not make much sense to
|
|
* behead the system on such scenario so
|
|
* trigger a self-heal and go on. The worst
|
|
* thing that can happen? Data loss, that's it.
|
|
*/
|
|
DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n");
|
|
return SelfHeal;
|
|
}
|
|
|
|
/* Check the dirty vector */
|
|
if (*((PULONG)DirtyVector) != HV_LOG_DIRTY_SIGNATURE)
|
|
{
|
|
if (!CmIsSelfHealEnabled(FALSE))
|
|
{
|
|
DPRINT1("The log's dirty vector signature is not valid\n");
|
|
return Fail;
|
|
}
|
|
|
|
/*
|
|
* Trigger a self-heal like above. If the
|
|
* vector signature is garbage then logically
|
|
* whatever comes after the signature is also
|
|
* garbage.
|
|
*/
|
|
DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n");
|
|
return SelfHeal;
|
|
}
|
|
|
|
/* Now read each data individually and write it back to hive */
|
|
LogIndex = 0;
|
|
StorageLength = BaseBlock->Length / HBLOCK_SIZE;
|
|
for (BlockIndex = 0; BlockIndex < StorageLength; BlockIndex++)
|
|
{
|
|
/* Skip this block if it's not dirty and go to the next one */
|
|
if (DirtyVector[BlockIndex + sizeof(HV_LOG_DIRTY_SIGNATURE)] != HV_LOG_DIRTY_BLOCK)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FileOffset = HSECTOR_SIZE + HSECTOR_SIZE + LogIndex * HBLOCK_SIZE;
|
|
Success = Hive->FileRead(Hive,
|
|
HFILE_TYPE_LOG,
|
|
&FileOffset,
|
|
Buffer,
|
|
HBLOCK_SIZE);
|
|
if (!Success)
|
|
{
|
|
DPRINT1("Failed to read the dirty block (index %u)\n", BlockIndex);
|
|
return Fail;
|
|
}
|
|
|
|
FileOffset = HBLOCK_SIZE + BlockIndex * HBLOCK_SIZE;
|
|
Success = Hive->FileWrite(Hive,
|
|
HFILE_TYPE_PRIMARY,
|
|
&FileOffset,
|
|
Buffer,
|
|
HBLOCK_SIZE);
|
|
if (!Success)
|
|
{
|
|
DPRINT1("Failed to write dirty block to hive (index %u)\n", BlockIndex);
|
|
return Fail;
|
|
}
|
|
|
|
/* Increment the index in log as we continue further */
|
|
LogIndex++;
|
|
}
|
|
|
|
return HiveSuccess;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief
|
|
* Loads a registry hive from a physical hive file
|
|
* within the physical backing storage. Base block
|
|
* and registry data are read from the said physical
|
|
* hive file. This function can perform registry recovery
|
|
* if hive loading could not be done normally.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor where the said hive
|
|
* is to be loaded from the physical hive file.
|
|
*
|
|
* @param[in] FileName
|
|
* A pointer to a NULL-terminated Unicode string structure
|
|
* containing the hive file name to be copied from.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS is returned if the hive has been loaded
|
|
* successfully. STATUS_INSUFFICIENT_RESOURCES is returned
|
|
* if there's not enough memory resources to satisfy registry
|
|
* operations and/or requests. STATUS_NOT_REGISTRY_FILE is returned
|
|
* if the hive is not actually a hive file. STATUS_REGISTRY_CORRUPT
|
|
* is returned if the hive has subdued previous damage and
|
|
* the hive could not be recovered because there's no
|
|
* log present or self healing is disabled. STATUS_REGISTRY_RECOVERED
|
|
* is returned if the hive has been recovered. An eventual flush
|
|
* of the registry is needed after the hive's been fully loaded.
|
|
*/
|
|
NTSTATUS
|
|
CMAPI
|
|
HvLoadHive(
|
|
_In_ PHHIVE Hive,
|
|
_In_opt_ PCUNICODE_STRING FileName)
|
|
{
|
|
NTSTATUS Status;
|
|
BOOLEAN Success;
|
|
PHBASE_BLOCK BaseBlock = NULL;
|
|
/* FIXME: See the comment above (near HvpQueryHiveSize) */
|
|
#if defined(_M_AMD64)
|
|
ULONG Result;
|
|
#else
|
|
ULONG Result, Result2;
|
|
#endif
|
|
LARGE_INTEGER TimeStamp;
|
|
ULONG Offset = 0;
|
|
PVOID HiveData;
|
|
ULONG FileSize;
|
|
BOOLEAN HiveSelfHeal = FALSE;
|
|
|
|
/* Get the hive header */
|
|
Result = HvpGetHiveHeader(Hive, &BaseBlock, &TimeStamp);
|
|
switch (Result)
|
|
{
|
|
/* Out of memory */
|
|
case NoMemory:
|
|
{
|
|
/* Fail */
|
|
DPRINT1("There's no enough memory to get the header\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Not a hive */
|
|
case NotHive:
|
|
{
|
|
/* Fail */
|
|
DPRINT1("The hive is not an actual registry hive file\n");
|
|
return STATUS_NOT_REGISTRY_FILE;
|
|
}
|
|
|
|
/* Hive data needs a repair */
|
|
case RecoverData:
|
|
{
|
|
/*
|
|
* FIXME: We must be handling this status
|
|
* case if the header isn't corrupt but
|
|
* the counter sequences do not match but
|
|
* due to a hack in HvLoadHive we have
|
|
* to do both a header + data recovery.
|
|
* RecoverHeader also implies RecoverData
|
|
* anyway. When HvLoadHive gets rid of
|
|
* that hack, data recovery must be done
|
|
* after we read the hive block by block.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
/* Hive header needs a repair */
|
|
case RecoverHeader:
|
|
/* FIXME: See the comment above (near HvpQueryHiveSize) */
|
|
#if defined(_M_AMD64)
|
|
{
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
#else
|
|
{
|
|
/* Check if this hive has a log at hand to begin with */
|
|
#if (NTDDI_VERSION < NTDDI_VISTA)
|
|
if (!Hive->Log)
|
|
{
|
|
DPRINT1("The hive has no log for header recovery\n");
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
#endif
|
|
|
|
/* The header needs to be recovered so do it */
|
|
DPRINT1("Attempting to heal the header...\n");
|
|
Result2 = HvpRecoverHeaderFromLog(Hive, &TimeStamp, &BaseBlock);
|
|
if (Result2 == NoMemory)
|
|
{
|
|
DPRINT1("There's no enough memory to recover header from log\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Did we fail? */
|
|
if (Result2 == Fail)
|
|
{
|
|
DPRINT1("Failed to recover the hive header\n");
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
|
|
/* Did we trigger the self-heal mode? */
|
|
if (Result2 == SelfHeal)
|
|
{
|
|
HiveSelfHeal = TRUE;
|
|
}
|
|
|
|
/* Now recover the data */
|
|
Result2 = HvpRecoverDataFromLog(Hive, BaseBlock);
|
|
if (Result2 == Fail)
|
|
{
|
|
DPRINT1("Failed to recover the hive data\n");
|
|
return STATUS_REGISTRY_CORRUPT;
|
|
}
|
|
|
|
/* Tag the boot as self heal if we haven't done it before */
|
|
if ((Result2 == SelfHeal) && (!HiveSelfHeal))
|
|
{
|
|
HiveSelfHeal = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Set the boot type */
|
|
BaseBlock->BootType = HiveSelfHeal ? HBOOT_TYPE_SELF_HEAL : HBOOT_TYPE_REGULAR;
|
|
|
|
/* Setup hive data */
|
|
Hive->BaseBlock = BaseBlock;
|
|
Hive->Version = BaseBlock->Minor;
|
|
|
|
/* Allocate a buffer large enough to hold the hive */
|
|
FileSize = HBLOCK_SIZE + BaseBlock->Length; // == sizeof(HBASE_BLOCK) + BaseBlock->Length;
|
|
HiveData = Hive->Allocate(FileSize, TRUE, TAG_CM);
|
|
if (!HiveData)
|
|
{
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
DPRINT1("There's no enough memory to allocate hive data\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* HACK (see explanation below): Now read the whole hive */
|
|
Success = Hive->FileRead(Hive,
|
|
HFILE_TYPE_PRIMARY,
|
|
&Offset,
|
|
HiveData,
|
|
FileSize);
|
|
if (!Success)
|
|
{
|
|
DPRINT1("Failed to read the whole hive\n");
|
|
Hive->Free(HiveData, FileSize);
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
return STATUS_NOT_REGISTRY_FILE;
|
|
}
|
|
|
|
/*
|
|
* HACK (FIXME): Free our base block... it's useless in
|
|
* this implementation.
|
|
*
|
|
* And it's useless because while the idea of reading the
|
|
* hive from physical file is correct, the implementation
|
|
* is hacky and incorrect. Instead of reading the whole hive,
|
|
* we should be instead reading the hive block by block,
|
|
* deconstruct the block buffer and enlist the bins and
|
|
* prepare the storage for the hive. What we currently do
|
|
* is we try to initialize the hive storage and bins enlistment
|
|
* by calling HvpInitializeMemoryHive below. This mixes
|
|
* HINIT_FILE and HINIT_MEMORY together which is disgusting
|
|
* because HINIT_FILE implementation shouldn't be calling
|
|
* HvpInitializeMemoryHive.
|
|
*/
|
|
Hive->Free(BaseBlock, Hive->BaseBlockAlloc);
|
|
Status = HvpInitializeMemoryHive(Hive, HiveData, FileName);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to initialize hive from memory\n");
|
|
Hive->Free(HiveData, FileSize);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* If we have done some sort of recovery against
|
|
* the hive we were going to load it from file,
|
|
* tell the caller we did recover it. The caller
|
|
* is responsible to flush the data later on.
|
|
*/
|
|
return (Result == RecoverHeader) ? STATUS_REGISTRY_RECOVERED : STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Initializes a registry hive. It allocates a hive
|
|
* descriptor and sets up the hive type depending
|
|
* on the type chosen by the caller.
|
|
*
|
|
* @param[in,out] RegistryHive
|
|
* A pointer to a hive descriptor to be initialized.
|
|
*
|
|
* @param[in] OperationType
|
|
* The operation type to choose for hive initialization.
|
|
* For further information about this, see Remarks.
|
|
*
|
|
* @param[in] HiveFlags
|
|
* A hive flag. Such flag is used to determine what kind
|
|
* of action must be taken into the hive or what aspects
|
|
* must be taken into account for such hive. For further
|
|
* information, see Remarks.
|
|
*
|
|
* @param[in] FileType
|
|
* Hive file type. For the newly initialized hive, you can
|
|
* choose from three different types for the hive:
|
|
*
|
|
* HFILE_TYPE_PRIMARY - Initializes a hive as primary hive
|
|
* of the system.
|
|
*
|
|
* HFILE_TYPE_LOG - The newly created hive is a hive log.
|
|
* Logs don't exist per se but they're accompanied with their
|
|
* associated primary hives. The Log field member of the hive
|
|
* descriptor is set to TRUE.
|
|
*
|
|
* HFILE_TYPE_EXTERNAL - The newly created hive is a portable
|
|
* hive, that can be used and copied for different machines,
|
|
* unlike primary hives.
|
|
*
|
|
* HFILE_TYPE_ALTERNATE - The newly created hive is an alternate hive.
|
|
* Technically speaking it is the same as a primary hive (the representation
|
|
* of on-disk image of the registry header is HFILE_TYPE_PRIMARY), with
|
|
* the purpose is to serve as a backup hive. The Alternate field of the
|
|
* hive descriptor is set to TRUE. Only the SYSTEM hive has a backup
|
|
* alternate hive.
|
|
*
|
|
* @param[in] HiveData
|
|
* An arbitrary pointer that points to the hive data. Usually this
|
|
* data is in form of a hive base block given by the caller of this
|
|
* function.
|
|
*
|
|
* @param[in] Allocate
|
|
* A pointer to a ALLOCATE_ROUTINE function that describes
|
|
* the main allocation routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] Free
|
|
* A pointer to a FREE_ROUTINE function that describes the
|
|
* the main memory freeing routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] FileSetSize
|
|
* A pointer to a FILE_SET_SIZE_ROUTINE function that describes
|
|
* the file set size routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] FileWrite
|
|
* A pointer to a FILE_WRITE_ROUTINE function that describes
|
|
* the file writing routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] FileRead
|
|
* A pointer to a FILE_READ_ROUTINE function that describes
|
|
* the file reading routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] FileFlush
|
|
* A pointer to a FILE_FLUSH_ROUTINE function that describes
|
|
* the file flushing routine for this hive. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] Cluster
|
|
* The registry hive cluster to be set. Usually this value
|
|
* is set to 1.
|
|
*
|
|
* @param[in] FileName
|
|
* A to a NULL-terminated Unicode string structure containing
|
|
* the hive file name. This parameter can be NULL.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has successfully
|
|
* initialized the hive. STATUS_REGISTRY_RECOVERED is returned
|
|
* if the hive has subdued previous damage and it's been recovered.
|
|
* This function will perform a hive writing and flushing with
|
|
* healthy and recovered data in that case. STATUS_REGISTRY_IO_FAILED
|
|
* is returned if registry hive writing/flushing of recovered data
|
|
* has failed. STATUS_INVALID_PARAMETER is returned if an invalid
|
|
* operation type pointed by OperationType parameter has been
|
|
* submitted. A failure NTSTATUS code is returned otherwise.
|
|
*
|
|
* @remarks
|
|
* OperationType parameter influences how should the hive be
|
|
* initialized. These are the following supported operation
|
|
* types:
|
|
*
|
|
* HINIT_CREATE -- Creates a new fresh hive.
|
|
*
|
|
* HINIT_MEMORY -- Initializes a registry hive that already exists
|
|
* from memory. The hive data is copied from the
|
|
* loaded hive in memory and used for read/write
|
|
* access.
|
|
*
|
|
* HINIT_FLAT -- Initializes a flat registry hive, with data that can
|
|
* only be read and not written into. Cells are always
|
|
* allocated on a flat hive.
|
|
*
|
|
* HINIT_FILE -- Initializes a hive from a hive file from the physical
|
|
* backing storage of the system. In this situation the
|
|
* function will perform self-healing and resuscitation
|
|
* procedures if data read from the physical hive file
|
|
* is corrupt.
|
|
*
|
|
* HINIT_MEMORY_INPLACE -- This operation type is similar to HINIT_FLAT,
|
|
* with the difference is that the hive is initialized
|
|
* with hive data from memory. The hive can only be read
|
|
* and not written into.
|
|
*
|
|
* HINIT_MAPFILE -- Initializes a hive from a hive file from the physical
|
|
* backing storage of the system. Unlike HINIT_FILE, the
|
|
* initialized hive is not backed to paged pool in memory
|
|
* but rather through mapping views.
|
|
*
|
|
* Alongside the operation type, the hive flags also influence the aspect
|
|
* of the newly initialized hive. These are the following supported hive
|
|
* flags:
|
|
*
|
|
* HIVE_VOLATILE -- Tells the function that this hive will be volatile, that
|
|
* is, the data stored inside the hive space resides only
|
|
* in volatile memory of the system, aka the RAM, and the
|
|
* data will be erased upon shutdown of the system.
|
|
*
|
|
* HIVE_NOLAZYFLUSH -- Tells the function that no lazy flushing must be
|
|
* done to this hive.
|
|
*/
|
|
NTSTATUS
|
|
CMAPI
|
|
HvInitialize(
|
|
_Inout_ PHHIVE RegistryHive,
|
|
_In_ ULONG OperationType,
|
|
_In_ ULONG HiveFlags,
|
|
_In_ ULONG FileType,
|
|
_In_opt_ PVOID HiveData,
|
|
_In_opt_ PALLOCATE_ROUTINE Allocate,
|
|
_In_opt_ PFREE_ROUTINE Free,
|
|
_In_opt_ PFILE_SET_SIZE_ROUTINE FileSetSize,
|
|
_In_opt_ PFILE_WRITE_ROUTINE FileWrite,
|
|
_In_opt_ PFILE_READ_ROUTINE FileRead,
|
|
_In_opt_ PFILE_FLUSH_ROUTINE FileFlush,
|
|
_In_ ULONG Cluster,
|
|
_In_opt_ PCUNICODE_STRING FileName)
|
|
{
|
|
NTSTATUS Status;
|
|
PHHIVE Hive = RegistryHive;
|
|
|
|
/*
|
|
* Create a new hive structure that will hold all the maintenance data.
|
|
*/
|
|
|
|
RtlZeroMemory(Hive, sizeof(HHIVE));
|
|
Hive->Signature = HV_HHIVE_SIGNATURE;
|
|
|
|
Hive->Allocate = Allocate;
|
|
Hive->Free = Free;
|
|
Hive->FileSetSize = FileSetSize;
|
|
Hive->FileWrite = FileWrite;
|
|
Hive->FileRead = FileRead;
|
|
Hive->FileFlush = FileFlush;
|
|
|
|
Hive->RefreshCount = 0;
|
|
Hive->StorageTypeCount = HTYPE_COUNT;
|
|
Hive->Cluster = Cluster;
|
|
Hive->BaseBlockAlloc = sizeof(HBASE_BLOCK); // == HBLOCK_SIZE
|
|
|
|
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;
|
|
|
|
// TODO: The CellRoutines point to different callbacks
|
|
// depending on the OperationType.
|
|
Hive->GetCellRoutine = HvpGetCellData;
|
|
Hive->ReleaseCellRoutine = NULL;
|
|
|
|
switch (OperationType)
|
|
{
|
|
case HINIT_CREATE:
|
|
{
|
|
/* Create a new fresh hive */
|
|
Status = HvpCreateHive(Hive, FileName);
|
|
break;
|
|
}
|
|
|
|
case HINIT_MEMORY:
|
|
{
|
|
/* Initialize a hive from memory */
|
|
Status = HvpInitializeMemoryHive(Hive, HiveData, FileName);
|
|
break;
|
|
}
|
|
|
|
case HINIT_FLAT:
|
|
{
|
|
/* Initialize a flat read-only hive */
|
|
Status = HvpInitializeFlatHive(Hive, HiveData);
|
|
break;
|
|
}
|
|
|
|
case HINIT_FILE:
|
|
{
|
|
/* Initialize a hive by loading it from physical file in backing storage */
|
|
Status = HvLoadHive(Hive, FileName);
|
|
if ((Status != STATUS_SUCCESS) &&
|
|
(Status != STATUS_REGISTRY_RECOVERED))
|
|
{
|
|
/* Unrecoverable failure */
|
|
DPRINT1("Registry hive couldn't be initialized, it's corrupt (hive 0x%p)\n", Hive);
|
|
return Status;
|
|
}
|
|
|
|
/* FIXME: See the comment above (near HvpQueryHiveSize) */
|
|
#if !defined(_M_AMD64)
|
|
/*
|
|
* Check if we have recovered this hive. We are responsible to
|
|
* flush the primary hive back to backing storage afterwards.
|
|
*/
|
|
if (Status == STATUS_REGISTRY_RECOVERED)
|
|
{
|
|
if (!HvSyncHiveFromRecover(Hive))
|
|
{
|
|
DPRINT1("Fail to write healthy data back to hive\n");
|
|
return STATUS_REGISTRY_IO_FAILED;
|
|
}
|
|
|
|
/*
|
|
* We are saved from hell, now clear out the
|
|
* dirty bits and dirty count.
|
|
*
|
|
* FIXME: We must as well clear out the log
|
|
* and reset its size to 0 but we are lacking
|
|
* in code that deals with log growing/shrinking
|
|
* management. When the time comes to implement
|
|
* this stuff we must set the LogSize and file size
|
|
* to 0 here.
|
|
*/
|
|
RtlClearAllBits(&Hive->DirtyVector);
|
|
Hive->DirtyCount = 0;
|
|
|
|
/*
|
|
* Masquerade the status code as success.
|
|
* STATUS_REGISTRY_RECOVERED is not a failure
|
|
* code but not STATUS_SUCCESS either so the caller
|
|
* thinks we failed at our job.
|
|
*/
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case HINIT_MEMORY_INPLACE:
|
|
{
|
|
// Status = HvpInitializeMemoryInplaceHive(Hive, HiveData);
|
|
// break;
|
|
DPRINT1("HINIT_MEMORY_INPLACE is UNIMPLEMENTED\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
case HINIT_MAPFILE:
|
|
{
|
|
DPRINT1("HINIT_MAPFILE is UNIMPLEMENTED\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
default:
|
|
{
|
|
DPRINT1("Invalid operation type (OperationType = %u)\n", OperationType);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Frees all the bins within the storage, the dirty vector
|
|
* and the base block associated with the given registry
|
|
* hive descriptor.
|
|
*
|
|
* @param[in] RegistryHive
|
|
* A pointer to a hive descriptor where all of its data
|
|
* is to be freed.
|
|
*/
|
|
VOID
|
|
CMAPI
|
|
HvFree(
|
|
_In_ PHHIVE RegistryHive)
|
|
{
|
|
if (!RegistryHive->ReadOnly)
|
|
{
|
|
/* Release hive bitmap */
|
|
if (RegistryHive->DirtyVector.Buffer)
|
|
{
|
|
RegistryHive->Free(RegistryHive->DirtyVector.Buffer, 0);
|
|
}
|
|
|
|
HvpFreeHiveBins(RegistryHive);
|
|
|
|
/* Free the BaseBlock */
|
|
if (RegistryHive->BaseBlock)
|
|
{
|
|
RegistryHive->Free(RegistryHive->BaseBlock, RegistryHive->BaseBlockAlloc);
|
|
RegistryHive->BaseBlock = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* EOF */
|