mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 18:15:11 +00:00
f33da480af
CmCheckRegistry is a function that provides the necessary validation checks for a registry hive. This function usually comes into action when logs have been replayed for example, or when a registry hive internals have changed such as when saving a key, loading a key, etc. This commit implements the whole Check Registry infrastructure (cmcheck.c) in CMLIB library for ease of usage and wide accessibility across parts of the OS. In addition, two more functions for registry checks are also implemented -- HvValidateHive and HvValidateBin. Instead of having the CmCheckRegistry implementation in the kernel, it's better to have it in the Configuration Manager library instead (aka CMLIB). The benefits of having it in the library are the following: - CmCheckRegistry can be used in FreeLdr to fix the SYSTEM hive - It can be used on-demand in the kernel - It can be used for offline registry repair tools - It makes the underlying CmCheckRegistry implementation code debug-able in user mode CORE-9195 CORE-6762
1718 lines
59 KiB
C
1718 lines
59 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 Validation
|
|
* COPYRIGHT: Copyright 2022 George Bișoc <george.bisoc@reactos.org>
|
|
*/
|
|
|
|
#include "cmlib.h"
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* STRUCTURES ****************************************************************/
|
|
|
|
typedef struct _CMP_REGISTRY_STACK_WORK_STATE
|
|
{
|
|
ULONG ChildCellIndex;
|
|
HCELL_INDEX Parent;
|
|
HCELL_INDEX Current;
|
|
HCELL_INDEX Sibling;
|
|
} CMP_REGISTRY_STACK_WORK_STATE, *PCMP_REGISTRY_STACK_WORK_STATE;
|
|
|
|
/* DEFINES ******************************************************************/
|
|
|
|
#define GET_HHIVE(CmHive) (&((CmHive)->Hive))
|
|
#define GET_HHIVE_ROOT_CELL(Hive) ((Hive)->BaseBlock->RootCell)
|
|
#define GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex) ((PHBIN)Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress)
|
|
#define GET_CELL_BIN(Bin) ((PHCELL)((PUCHAR)Bin + sizeof(HBIN)))
|
|
|
|
#define IS_CELL_VOLATILE(Cell) (HvGetCellType(Cell) == Volatile)
|
|
|
|
#if !defined(CMLIB_HOST) && !defined(_BLDR_)
|
|
extern PCMHIVE CmiVolatileHive;
|
|
#endif
|
|
|
|
#define CMP_PRIOR_STACK 1
|
|
#define CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH 512
|
|
|
|
#define CMP_KEY_SIZE_THRESHOLD 0x45C
|
|
#define CMP_VOLATILE_LIST_UNINTIALIZED 0xBAADF00D
|
|
|
|
/* PRIVATE FUNCTIONS **********************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the lexicographical order between a child
|
|
* and prior sibling of the said child.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which lexicographical
|
|
* order of keys are to be checked.
|
|
*
|
|
* @param[in] Child
|
|
* A child subkey cell used for lexicographical order
|
|
* validation checks.
|
|
*
|
|
* @param[in] Sibling
|
|
* A subkey cell which is the prior sibling of the child.
|
|
* This is used in conjuction with the child to perfrom
|
|
* lexical order checks.
|
|
*
|
|
* @return
|
|
* Returns TRUE if the order is legal, FALSE otherwise.
|
|
*/
|
|
static
|
|
BOOLEAN
|
|
CmpValidateLexicalOrder(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX Child,
|
|
_In_ HCELL_INDEX Sibling)
|
|
{
|
|
LONG Result;
|
|
UNICODE_STRING ChildString, SiblingString;
|
|
PCM_KEY_NODE ChildNode, SiblingNode;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Obtain the child node */
|
|
ChildNode = (PCM_KEY_NODE)HvGetCell(Hive, Child);
|
|
if (!ChildNode)
|
|
{
|
|
/* Couldn't get the child node, bail out */
|
|
DPRINT1("Failed to get the child node\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Obtain the sibling node */
|
|
SiblingNode = (PCM_KEY_NODE)HvGetCell(Hive, Sibling);
|
|
if (!SiblingNode)
|
|
{
|
|
/* Couldn't get the sibling node, bail out */
|
|
DPRINT1("Failed to get the sibling node\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* CASE 1: Two distinct non-compressed Unicode names */
|
|
if ((ChildNode->Flags & KEY_COMP_NAME) == 0 &&
|
|
(SiblingNode->Flags & KEY_COMP_NAME) == 0)
|
|
{
|
|
/* Build the sibling string */
|
|
SiblingString.Buffer = &(SiblingNode->Name[0]);
|
|
SiblingString.Length = SiblingNode->NameLength;
|
|
SiblingString.MaximumLength = SiblingNode->NameLength;
|
|
|
|
/* Build the child string */
|
|
ChildString.Buffer = &(ChildNode->Name[0]);
|
|
ChildString.Length = ChildNode->NameLength;
|
|
ChildString.MaximumLength = ChildNode->NameLength;
|
|
|
|
Result = RtlCompareUnicodeString(&SiblingString, &ChildString, TRUE);
|
|
if (Result >= 0)
|
|
{
|
|
DPRINT1("The sibling node name is greater or equal to that of the child\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* CASE 2: Both compressed Unicode names */
|
|
if ((ChildNode->Flags & KEY_COMP_NAME) &&
|
|
(SiblingNode->Flags & KEY_COMP_NAME))
|
|
{
|
|
/* FIXME: Checks for two compressed names not implemented yet */
|
|
DPRINT("Lexicographical order checks for two compressed names is UNIMPLEMENTED, assume the key is healthy...\n");
|
|
return TRUE;
|
|
}
|
|
|
|
/* CASE 3: The child name is compressed but the sibling is not */
|
|
if ((ChildNode->Flags & KEY_COMP_NAME) &&
|
|
(SiblingNode->Flags & KEY_COMP_NAME) == 0)
|
|
{
|
|
SiblingString.Buffer = &(SiblingNode->Name[0]);
|
|
SiblingString.Length = SiblingNode->NameLength;
|
|
SiblingString.MaximumLength = SiblingNode->NameLength;
|
|
Result = CmpCompareCompressedName(&SiblingString,
|
|
ChildNode->Name,
|
|
ChildNode->NameLength);
|
|
if (Result >= 0)
|
|
{
|
|
DPRINT1("The sibling node name is greater or equal to that of the compressed child\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* CASE 4: The sibling name is compressed but the child is not */
|
|
if ((SiblingNode->Flags & KEY_COMP_NAME) &&
|
|
(ChildNode->Flags & KEY_COMP_NAME) == 0)
|
|
{
|
|
ChildString.Buffer = &(ChildNode->Name[0]);
|
|
ChildString.Length = ChildNode->NameLength;
|
|
ChildString.MaximumLength = ChildNode->NameLength;
|
|
Result = CmpCompareCompressedName(&ChildString,
|
|
SiblingNode->Name,
|
|
SiblingNode->NameLength);
|
|
if (Result <= 0)
|
|
{
|
|
DPRINT1("The compressed sibling node name is lesser or equal to that of the child\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* One of the cases above has met the conditions
|
|
* successfully, the lexicographical order is legal.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the class of a given key cell.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which
|
|
* the registry call is to be validated.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current key cell that the class points to.
|
|
*
|
|
* @param[in] CellData
|
|
* A pointer to cell data of the current key cell
|
|
* that contains the class to be validated.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD if the class is in good shape.
|
|
* The same CM status code is returned if the class doesn't
|
|
* but the class length says otherwise. CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED
|
|
* is returned if the class cell is not allocated.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateClass(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_Inout_ PCELL_DATA CellData)
|
|
{
|
|
ULONG ClassLength;
|
|
HCELL_INDEX ClassCell;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(CurrentCell != HCELL_NIL);
|
|
ASSERT(CellData);
|
|
|
|
/* Cache the class cell and validate it (if any) */
|
|
ClassCell = CellData->u.KeyNode.Class;
|
|
ClassLength = CellData->u.KeyNode.ClassLength;
|
|
if (ClassLength > 0)
|
|
{
|
|
if (ClassCell == HCELL_NIL)
|
|
{
|
|
/*
|
|
* Somebody has freed the class but left the
|
|
* length as is, reset it.
|
|
*/
|
|
DPRINT1("The key node class is NIL but the class length is not 0, resetting it\n");
|
|
HvMarkCellDirty(Hive, CurrentCell, FALSE);
|
|
CellData->u.KeyNode.ClassLength = 0;
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
if (!HvIsCellAllocated(Hive, ClassCell))
|
|
{
|
|
DPRINT1("The key class is not allocated\n");
|
|
return CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED;
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates each value in the list by count. A
|
|
* value that is damaged gets removed from the list.
|
|
* This routine performs self-healing process in
|
|
* this case.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which a list
|
|
* of registry values is to be validated.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current key cell that the value list points to.
|
|
*
|
|
* @param[in] ListCount
|
|
* The list count that describes the actual number of
|
|
* values in the list.
|
|
*
|
|
* @param[in] ValueListData
|
|
* A pointer to cell data of the current key cell
|
|
* that contains the value list to be validated.
|
|
*
|
|
* @param[out] ValuesRemoved
|
|
* When the function finishes doing its operations,
|
|
* this parameter contains the amount of removed values
|
|
* from the list. A value of 0 indicates no values have
|
|
* been removed (which that would imply a self-healing
|
|
* process of the value list has occurred).
|
|
*
|
|
* @param[in] FixHive
|
|
* If set to TRUE, the target hive will be fixed.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD if the value list is
|
|
* sane. CM_CHECK_REGISTRY_VALUE_CELL_NIL is returned
|
|
* if a certain value cell is HCELL_NIL at specific
|
|
* count index. CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED is
|
|
* returned if a certain value cell is unallocated at specific
|
|
* count index. CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND is
|
|
* returned if cell data could not be mapped from the value cell,
|
|
* the value list is totally torn apart in this case.
|
|
* CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE is returned if the
|
|
* value's size is bogus. CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA
|
|
* is returned if the data inside the value is HCELL_NIL.
|
|
* CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED is returned if the
|
|
* data cell inside the value is not allocated.
|
|
* CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE is returned if the
|
|
* value's signature is not valid.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateValueListByCount(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_In_ ULONG ListCount,
|
|
_In_ PCELL_DATA ValueListData,
|
|
_Out_ PULONG ValuesRemoved,
|
|
_In_ BOOLEAN FixHive)
|
|
{
|
|
ULONG ValueDataSize;
|
|
ULONG ListCountIndex;
|
|
ULONG DataSize;
|
|
HCELL_INDEX DataCell;
|
|
HCELL_INDEX ValueCell;
|
|
PCELL_DATA ValueData;
|
|
ULONG ValueNameLength, TotalValueNameLength;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(ValueListData);
|
|
ASSERT(ListCount != 0);
|
|
|
|
/* Assume we haven't removed any value counts for now */
|
|
*ValuesRemoved = 0;
|
|
|
|
/*
|
|
* Begin looping each value in the list and
|
|
* validate it accordingly.
|
|
*/
|
|
ListCountIndex = 0;
|
|
while (ListCountIndex < ListCount)
|
|
{
|
|
ValueCell = ValueListData->u.KeyList[ListCountIndex];
|
|
if (ValueCell == HCELL_NIL)
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The value cell is NIL (at index %lu, list count %lu)\n",
|
|
ListCountIndex, ListCount);
|
|
return CM_CHECK_REGISTRY_VALUE_CELL_NIL;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
|
|
if (!HvIsCellAllocated(Hive, ValueCell))
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The value cell is not allocated (at index %lu, list count %lu)\n",
|
|
ListCountIndex, ListCount);
|
|
return CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
|
|
/* Obtain a cell data from this value */
|
|
ValueData = (PCELL_DATA)HvGetCell(Hive, ValueCell);
|
|
if (!ValueData)
|
|
{
|
|
DPRINT1("Cell data of the value cell not found (at index %lu, value count %lu)\n",
|
|
ListCountIndex, ListCount);
|
|
return CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND;
|
|
}
|
|
|
|
/* Check that the value size is sane */
|
|
ValueDataSize = HvGetCellSize(Hive, ValueData);
|
|
ValueNameLength = ValueData->u.KeyValue.NameLength;
|
|
TotalValueNameLength = ValueNameLength + FIELD_OFFSET(CM_KEY_VALUE, Name);
|
|
if (TotalValueNameLength > ValueDataSize)
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The total size is bigger than the actual cell size (total size %lu, cell size %lu, at index %lu)\n",
|
|
TotalValueNameLength, ValueDataSize, ListCountIndex);
|
|
return CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* The value cell has a sane size. The last thing
|
|
* to validate is the actual data of the value cell.
|
|
* That is, we want that the data itself and length
|
|
* are consistent. Technically speaking, value keys
|
|
* that are small are directly located in the value
|
|
* cell and it's built-in, in other words, the data
|
|
* is immediately present in the cell so we don't have
|
|
* to bother validating them since they're alright on
|
|
* their own. This can't be said the same about normal
|
|
* values though.
|
|
*/
|
|
DataCell = ValueData->u.KeyValue.Data;
|
|
if (!CmpIsKeyValueSmall(&DataSize, ValueData->u.KeyValue.DataLength))
|
|
{
|
|
/* Validate the actual data based on size */
|
|
if (DataSize == 0)
|
|
{
|
|
if (DataCell != HCELL_NIL)
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
|
|
return CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!HvIsCellAllocated(Hive, DataCell))
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
|
|
return CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* FIXME: Big values not supported yet */
|
|
ASSERT_VALUE_BIG(Hive, DataSize);
|
|
}
|
|
|
|
/* Is the signature valid? */
|
|
if (ValueData->u.KeyValue.Signature != CM_KEY_VALUE_SIGNATURE)
|
|
{
|
|
if (!CmpRepairValueListCount(Hive,
|
|
CurrentCell,
|
|
ListCountIndex,
|
|
ValueListData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The key value signature is invalid\n");
|
|
return CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE;
|
|
}
|
|
|
|
/* Decrease the list count and go to the next value */
|
|
ListCount--;
|
|
*ValuesRemoved++;
|
|
DPRINT1("Damaged value removed, continuing with the next value...\n");
|
|
continue;
|
|
}
|
|
|
|
/* Advance to the next value */
|
|
ListCountIndex++;
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the value list of a key. If the list
|
|
* is damaged due to corruption, the whole list
|
|
* is expunged. This function performs self-healing
|
|
* procedures in this case.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which a list of
|
|
* registry values is to be validated.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current key cell that the value list points to.
|
|
*
|
|
* @param[in] CellData
|
|
* The cell data of the current cell of which the value
|
|
* list comes from.
|
|
*
|
|
* @param[in] FixHive
|
|
* If set to TRUE, the target hive will be fixed.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD if the value list is
|
|
* sane. CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED is returned
|
|
* if the value list cell is not allocated. CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND
|
|
* is returned if cell data could not be mapped from the value
|
|
* list cell. CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE is returned
|
|
* if the size of the value list is bogus. A failure CM status code
|
|
* is returned otherwise.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateValueList(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_Inout_ PCELL_DATA CellData,
|
|
_In_ BOOLEAN FixHive)
|
|
{
|
|
CM_CHECK_REGISTRY_STATUS CmStatusCode;
|
|
ULONG TotalValueLength, ValueSize;
|
|
ULONG ValueListCount;
|
|
ULONG ValuesRemoved;
|
|
HCELL_INDEX ValueListCell;
|
|
PCELL_DATA ValueListData;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(CurrentCell != HCELL_NIL);
|
|
ASSERT(CellData);
|
|
|
|
/* Cache the value list and validate it */
|
|
ValueListCell = CellData->u.KeyNode.ValueList.List;
|
|
ValueListCount = CellData->u.KeyNode.ValueList.Count;
|
|
if (ValueListCount > 0)
|
|
{
|
|
if (!HvIsCellAllocated(Hive, ValueListCell))
|
|
{
|
|
DPRINT1("The value list is not allocated\n");
|
|
return CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED;
|
|
}
|
|
|
|
/* Obtain cell data from the value list cell */
|
|
ValueListData = (PCELL_DATA)HvGetCell(Hive, ValueListCell);
|
|
if (!ValueListData)
|
|
{
|
|
DPRINT1("Could not get cell data from the value list\n");
|
|
return CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND;
|
|
}
|
|
|
|
/*
|
|
* Cache the value size and total length and
|
|
* assert ourselves this is a sane value list
|
|
* to begin with.
|
|
*/
|
|
ValueSize = HvGetCellSize(Hive, ValueListData);
|
|
TotalValueLength = ValueListCount * sizeof(HCELL_INDEX);
|
|
if (TotalValueLength > ValueSize)
|
|
{
|
|
DPRINT1("The value list is bigger than the cell (value list size %lu, cell size %lu)\n",
|
|
TotalValueLength, ValueSize);
|
|
return CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE;
|
|
}
|
|
|
|
/*
|
|
* The value list is sane, now we would
|
|
* need to validate the actual list
|
|
* by its count.
|
|
*/
|
|
CmStatusCode = CmpValidateValueListByCount(Hive,
|
|
CurrentCell,
|
|
ValueListCount,
|
|
ValueListData,
|
|
&ValuesRemoved,
|
|
FixHive);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
DPRINT1("One of the values is corrupt and couldn't be repaired\n");
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/* Log how much values have been removed */
|
|
if (ValuesRemoved > 0)
|
|
{
|
|
DPRINT1("Values removed in the list -- %lu\n", ValuesRemoved);
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the subkeys list of a key. If the list is
|
|
* damaged from corruption, the function can either
|
|
* salvage this list or purge the whole of it. The
|
|
* function performs different validation steps for
|
|
* different storage types.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which a list of
|
|
* subkeys is to be validated.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current key cell that the subkeys list points to.
|
|
*
|
|
* @param[in] CellData
|
|
* The cell data of the current cell of which the subkeys
|
|
* list comes from.
|
|
*
|
|
* @param[in] FixHive
|
|
* If set to TRUE, the target hive will be fixed.
|
|
*
|
|
* @param[out] DoRepair
|
|
* A pointer to a boolean value set up by the function itself.
|
|
* The function automatically sets this to FALSE indicating
|
|
* that repairs can't be done onto the list itself. If the
|
|
* list can be salvaged, then the function sets this to TRUE.
|
|
* See Remarks for further information.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD if the subkeys list is in
|
|
* perfect shape. CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE is
|
|
* returned if the volatile storage has stable data which
|
|
* that should not happen (this is only for the case of volatile
|
|
* cells). CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED is returned
|
|
* if the subkeys list cell is not allocated. CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX
|
|
* is returned if a key index could not be mapped from the subkeys
|
|
* list cell. CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT is returned if
|
|
* the key index is a leaf and the subkeys count doesn't match up
|
|
* with that of the leaf. CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED is
|
|
* returned if the key index cell at the specific index in the list of
|
|
* the index is not allocated. CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT is
|
|
* returned if a leaf could not be mapped from an index.
|
|
* CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE is returned if the leaf has
|
|
* an invalid signature. CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE
|
|
* is returned if the key index has an invalid signature, that is, it's
|
|
* not a leaf nor a root.
|
|
*
|
|
* @remarks
|
|
* Deep subkeys list healing can be done in specific cases where only
|
|
* a subkey doesn't actually affect the key itself. The function will
|
|
* mark the subkeys list as repairable by setting DoRepair parameter
|
|
* to TRUE and the caller is responsible to heal the key by purging
|
|
* the whole subkeys list. If the damage is so bad that there's
|
|
* possibility the key itself is even damaged, no healing is done.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateSubKeyList(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_Inout_ PCELL_DATA CellData,
|
|
_In_ BOOLEAN FixHive,
|
|
_Out_ PBOOLEAN DoRepair)
|
|
{
|
|
ULONG SubKeyCounts;
|
|
HCELL_INDEX KeyIndexCell, SubKeysListCell;
|
|
PCM_KEY_INDEX RootKeyIndex, LeafKeyIndex;
|
|
ULONG RootIndex;
|
|
ULONG TotalLeafCount;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(CurrentCell != HCELL_NIL);
|
|
ASSERT(CellData);
|
|
|
|
RootKeyIndex = NULL;
|
|
LeafKeyIndex = NULL;
|
|
TotalLeafCount = 0;
|
|
|
|
/*
|
|
* Assume for now that the caller should not
|
|
* do any kind of repairs on the subkeys list,
|
|
* unless explicitly given the consent by us.
|
|
*/
|
|
*DoRepair = FALSE;
|
|
|
|
/*
|
|
* For volatile keys they have data that can
|
|
* fluctuate and change on the fly so there's
|
|
* pretty much nothing that we can validate those.
|
|
* But still, we would want that the volatile key
|
|
* is not damaged by external factors, like e.g.,
|
|
* having stable keys on a volatile space.
|
|
*/
|
|
if (IS_CELL_VOLATILE(CurrentCell))
|
|
{
|
|
if (CellData->u.KeyNode.SubKeyCounts[Stable] != 0)
|
|
{
|
|
DPRINT1("The volatile key has stable subkeys\n");
|
|
return CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE;
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/*
|
|
* This is not a volatile key, cache the subkeys list
|
|
* and validate it.
|
|
*/
|
|
SubKeysListCell = CellData->u.KeyNode.SubKeyLists[Stable];
|
|
SubKeyCounts = CellData->u.KeyNode.SubKeyCounts[Stable];
|
|
if (SubKeyCounts > 0)
|
|
{
|
|
if (!HvIsCellAllocated(Hive, SubKeysListCell))
|
|
{
|
|
DPRINT1("The subkeys list cell is not allocated\n");
|
|
*DoRepair = TRUE;
|
|
return CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED;
|
|
}
|
|
|
|
/* Obtain a root index and validate it */
|
|
RootKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, SubKeysListCell);
|
|
if (!RootKeyIndex)
|
|
{
|
|
DPRINT1("Could not get the root key index of the subkeys list cell\n");
|
|
return CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX;
|
|
}
|
|
|
|
/*
|
|
* For simple, fast and hashed leaves we would want
|
|
* that the corresponding root index count matches with
|
|
* that of the subkey counts itself. If this is not the
|
|
* case we can isolate this problem and fix the count.
|
|
*/
|
|
if (RootKeyIndex->Signature == CM_KEY_INDEX_LEAF ||
|
|
RootKeyIndex->Signature == CM_KEY_FAST_LEAF ||
|
|
RootKeyIndex->Signature == CM_KEY_HASH_LEAF)
|
|
{
|
|
if (SubKeyCounts != RootKeyIndex->Count)
|
|
{
|
|
if (!CmpRepairSubKeyCounts(Hive,
|
|
CurrentCell,
|
|
RootKeyIndex->Count,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The subkeys list has invalid count (subkeys count %lu, root key index count %lu)\n",
|
|
SubKeyCounts, RootKeyIndex->Count);
|
|
return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/*
|
|
* The root index is not a leaf, check if the index
|
|
* is an actual root then.
|
|
*/
|
|
if (RootKeyIndex->Signature == CM_KEY_INDEX_ROOT)
|
|
{
|
|
/*
|
|
* For the root we have to loop each leaf
|
|
* from it and increase the total leaf count
|
|
* in the root after we determined the validity
|
|
* of a leaf. This way we can see if the subcount
|
|
* matches with that of the subkeys list count.
|
|
*/
|
|
for (RootIndex = 0; RootIndex < RootKeyIndex->Count; RootIndex++)
|
|
{
|
|
KeyIndexCell = RootKeyIndex->List[RootIndex];
|
|
if (!HvIsCellAllocated(Hive, KeyIndexCell))
|
|
{
|
|
DPRINT1("The key index cell is not allocated at index %lu\n", RootIndex);
|
|
*DoRepair = TRUE;
|
|
return CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED;
|
|
}
|
|
|
|
/* Obtain a leaf from the root */
|
|
LeafKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, KeyIndexCell);
|
|
if (!LeafKeyIndex)
|
|
{
|
|
DPRINT1("The root key index's signature is invalid!\n");
|
|
return CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT;
|
|
}
|
|
|
|
/* Check that the leaf has valid signature */
|
|
if (LeafKeyIndex->Signature != CM_KEY_INDEX_LEAF &&
|
|
LeafKeyIndex->Signature != CM_KEY_FAST_LEAF &&
|
|
LeafKeyIndex->Signature != CM_KEY_HASH_LEAF)
|
|
{
|
|
DPRINT1("The leaf's signature is invalid!\n");
|
|
*DoRepair = TRUE;
|
|
return CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE;
|
|
}
|
|
|
|
/* Add up the count of the leaf */
|
|
TotalLeafCount += LeafKeyIndex->Count;
|
|
}
|
|
|
|
/*
|
|
* We have built up the total leaf count,
|
|
* we have to determine this count is exactly
|
|
* the same as the subkeys list count. Otherwise
|
|
* just fix it.
|
|
*/
|
|
if (SubKeyCounts != TotalLeafCount)
|
|
{
|
|
if (!CmpRepairSubKeyCounts(Hive,
|
|
CurrentCell,
|
|
TotalLeafCount,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The subkeys list has invalid count (subkeys count %lu, total leaf count %lu)\n",
|
|
SubKeyCounts, TotalLeafCount);
|
|
return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/*
|
|
* None of the valid signatures match with that of
|
|
* the root key index. By definition, the whole subkey
|
|
* list is total toast.
|
|
*/
|
|
DPRINT1("The root key index's signature is invalid\n");
|
|
*DoRepair = TRUE;
|
|
return CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE;
|
|
}
|
|
|
|
/* If we reach here then this key has no subkeys */
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Purges the volatile storage of a registry
|
|
* hive. This operation is done mainly during
|
|
* the bootup of the system.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which volatiles
|
|
* are to be purged.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current key cell that the volatile storage of
|
|
* the hive points to.
|
|
*
|
|
* @param[in] CellData
|
|
* The cell data of the current cell of which the volatile
|
|
* subkeys storage comes from.
|
|
*
|
|
* @param[in] Flags
|
|
* A bit mask flag that is used to influence how is the
|
|
* purging operation to be done. See CmCheckRegistry documentation
|
|
* below for more information.
|
|
*/
|
|
static
|
|
VOID
|
|
CmpPurgeVolatiles(
|
|
_In_ PHHIVE Hive,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_Inout_ PCELL_DATA CellData,
|
|
_In_ ULONG Flags)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT(CellData);
|
|
|
|
/* Did the caller ask to purge volatiles? */
|
|
if (((Flags & CM_CHECK_REGISTRY_PURGE_VOLATILES) ||
|
|
(Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES)) &&
|
|
(CellData->u.KeyNode.SubKeyCounts[Volatile] != 0))
|
|
{
|
|
/*
|
|
* OK, the caller wants them cleaned from this
|
|
* hive. For XP Beta 1 or newer hives, we unintialize
|
|
* the whole volatile subkeys list. For older hives,
|
|
* we just do a cleanup.
|
|
*/
|
|
#if !defined(_BLDR_)
|
|
HvMarkCellDirty(Hive, CurrentCell, FALSE);
|
|
#endif
|
|
if ((Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES) &&
|
|
(Hive->Version >= HSYS_WHISTLER_BETA1))
|
|
{
|
|
CellData->u.KeyNode.SubKeyLists[Volatile] = CMP_VOLATILE_LIST_UNINTIALIZED;
|
|
}
|
|
else
|
|
{
|
|
CellData->u.KeyNode.SubKeyLists[Volatile] = HCELL_NIL;
|
|
}
|
|
|
|
/* Clear the count */
|
|
CellData->u.KeyNode.SubKeyCounts[Volatile] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates the key cell, ensuring that
|
|
* the key in the registry is valid and not corrupted.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of the registry where
|
|
* the key is to be validated.
|
|
*
|
|
* @param[in] SecurityDefaulted
|
|
* If the caller sets this to TRUE, this indicates the
|
|
* hive has its security property defaulted due to
|
|
* heal recovery of the said security. If the caller
|
|
* sets this to FALSE, the hive comes with its own
|
|
* security details. This parameter is currently unused.
|
|
*
|
|
* @param[in] ParentCell
|
|
* The parent key cell that comes before the current cell.
|
|
* This parameter can be HCELL_NIL if the first cell is
|
|
* the root cell which is the parent of its own.
|
|
*
|
|
* @param[in] CurrentCell
|
|
* The current child key cell that is to be validated.
|
|
*
|
|
* @param[in] Flags
|
|
* A bit mask flag that is used to influence how is the
|
|
* purging operation of volatile keys in the volatile storage
|
|
* to be done. See CmCheckRegistry documentation below for more
|
|
* information.
|
|
*
|
|
* @param[in] FixHive
|
|
* If set to TRUE, the target hive will be fixed.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD if the key that has been validated
|
|
* is valid and not corrupted. CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED is
|
|
* returned if the key cell is not allocated. CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND
|
|
* is returned if cell data could not be mapped from the key cell.
|
|
* CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE is returned if the key cell
|
|
* has an abnormal size that is above the trehshold the validation checks
|
|
* can permit. CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO is returned if the
|
|
* name length of the key node is 0, meaning that the key has no name.
|
|
* CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL is returned if the key is too
|
|
* big than the cell itself. CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT is
|
|
* returned if the parent node of the key is incosistent and it couldn't
|
|
* be fixed. CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE is returned if
|
|
* the signature of the key node is corrupt and it couldn't be fixed.
|
|
* A failure CM status code is returned otherwise.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateKey(
|
|
_In_ PHHIVE Hive,
|
|
_In_ BOOLEAN SecurityDefaulted,
|
|
_In_ HCELL_INDEX ParentCell,
|
|
_In_ HCELL_INDEX CurrentCell,
|
|
_In_ ULONG Flags,
|
|
_In_ BOOLEAN FixHive)
|
|
{
|
|
CM_CHECK_REGISTRY_STATUS CmStatusCode;
|
|
PCELL_DATA CellData;
|
|
ULONG CellSize;
|
|
BOOLEAN DoSubkeysRepair;
|
|
ULONG TotalKeyNameLength, NameLength;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* The current key cell mustn't be NIL here! */
|
|
ASSERT(CurrentCell != HCELL_NIL);
|
|
|
|
/* TODO: To be removed once we support security caching in Cm */
|
|
UNREFERENCED_PARAMETER(SecurityDefaulted);
|
|
|
|
/*
|
|
* We must ensure that the key cell is
|
|
* allocated in the first place before
|
|
* we go further.
|
|
*/
|
|
if (!HvIsCellAllocated(Hive, CurrentCell))
|
|
{
|
|
DPRINT1("The key cell is not allocated\n");
|
|
return CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED;
|
|
}
|
|
|
|
/* Obtain cell data from it */
|
|
CellData = (PCELL_DATA)HvGetCell(Hive, CurrentCell);
|
|
if (!CellData)
|
|
{
|
|
DPRINT1("Could not get cell data from the cell\n");
|
|
return CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND;
|
|
}
|
|
|
|
/* Get the size of this cell and validate its size */
|
|
CellSize = HvGetCellSize(Hive, CellData);
|
|
if (CellSize > CMP_KEY_SIZE_THRESHOLD)
|
|
{
|
|
DPRINT1("The cell size is above the threshold size (size %lu)\n", CellSize);
|
|
return CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE;
|
|
}
|
|
|
|
/*
|
|
* The cell size is OK but we must ensure
|
|
* the key is not bigger than the container
|
|
* of the cell.
|
|
*/
|
|
NameLength = CellData->u.KeyNode.NameLength;
|
|
if (NameLength == 0)
|
|
{
|
|
DPRINT1("The key node name length is 0!\n");
|
|
return CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO;
|
|
}
|
|
|
|
TotalKeyNameLength = NameLength + FIELD_OFFSET(CM_KEY_NODE, Name);
|
|
if (TotalKeyNameLength > CellSize)
|
|
{
|
|
DPRINT1("The key is too big than the cell (key size %lu, cell size %lu)\n", TotalKeyNameLength, CellSize);
|
|
return CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL;
|
|
}
|
|
|
|
/* Is the parent cell consistent? */
|
|
if (ParentCell != HCELL_NIL &&
|
|
ParentCell != CellData->u.KeyNode.Parent)
|
|
{
|
|
if (!CmpRepairParentNode(Hive,
|
|
CurrentCell,
|
|
ParentCell,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The parent key node doesn't point to the actual parent\n");
|
|
return CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT;
|
|
}
|
|
}
|
|
|
|
/* Is the key node signature valid? */
|
|
if (CellData->u.KeyNode.Signature != CM_KEY_NODE_SIGNATURE)
|
|
{
|
|
if (!CmpRepairKeyNodeSignature(Hive,
|
|
CurrentCell,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("The parent key node signature is not valid\n");
|
|
return CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FIXME: Security cell checks have to be implemented here
|
|
* once we properly and reliably implement security caching
|
|
* in the kernel.
|
|
*/
|
|
|
|
/* Validate the class */
|
|
CmStatusCode = CmpValidateClass(Hive, CurrentCell, CellData);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
if (!CmpRepairClassOfNodeKey(Hive,
|
|
CurrentCell,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("Failed to repair the hive, the cell class is not valid\n");
|
|
return CmStatusCode;
|
|
}
|
|
}
|
|
|
|
/* Validate the value list */
|
|
CmStatusCode = CmpValidateValueList(Hive, CurrentCell, CellData, FixHive);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
/*
|
|
* It happens that a certain value in the list
|
|
* is so bad like we couldn't map a cell data from it
|
|
* or the list itself is toast. In such cases what we
|
|
* can do here is to do a "value list sacrifice", aka
|
|
* purge the whole list.
|
|
*/
|
|
if (!CmpRepairValueList(Hive, CurrentCell, FixHive))
|
|
{
|
|
DPRINT1("Failed to repair the hive, the value list is corrupt\n");
|
|
return CmStatusCode;
|
|
}
|
|
}
|
|
|
|
/* Validate the subkeys list */
|
|
CmStatusCode = CmpValidateSubKeyList(Hive, CurrentCell, CellData, FixHive, &DoSubkeysRepair);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
/*
|
|
* The subkeys list is in trouble. Worse when the actual
|
|
* subkey list is so severed this key is also kaput on itself.
|
|
*/
|
|
if (!DoSubkeysRepair)
|
|
{
|
|
DPRINT1("The subkeys list is totally corrupt, can't repair\n");
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/*
|
|
* OK, there's still some salvation for this key.
|
|
* Purge the whole subkeys list in order to fix it.
|
|
*/
|
|
if (!CmpRepairSubKeyList(Hive,
|
|
CurrentCell,
|
|
CellData,
|
|
FixHive))
|
|
{
|
|
DPRINT1("Failed to repair the hive, the subkeys list is corrupt!\n");
|
|
return CmStatusCode;
|
|
}
|
|
}
|
|
|
|
/* Purge volatile data if needed */
|
|
CmpPurgeVolatiles(Hive, CurrentCell, CellData, Flags);
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Performs deep checking of the registry by walking
|
|
* down the registry tree using a stack based pool.
|
|
* This function is the guts of CmCheckRegistry.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of the registry where
|
|
* the validation is to be performed.
|
|
*
|
|
* @param[in] Flags
|
|
* Bit mask flag used for volatiles purging. Such
|
|
* flags influence on how volatile purging is actually
|
|
* done. See CmCheckRegistry documentation for more
|
|
* information.
|
|
*
|
|
* @param[in] SecurityDefaulted
|
|
* If the caller sets this to FALSE, the registry hive
|
|
* uses its own unique security details. Otherwise
|
|
* registry hive has the security details defaulted.
|
|
*
|
|
* @param[in] FixHive
|
|
* If set to TRUE, the target hive will be fixed.
|
|
*
|
|
* @return
|
|
* Returns CM_CHECK_REGISTRY_GOOD is returned if the function
|
|
* has successfully performed deep registry checking and
|
|
* the registry contents are valid. CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL
|
|
* is returned if the function has failed to allocate the
|
|
* stack work state buffer in memory which is necessary for
|
|
* deep checking of the registry. CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND
|
|
* is returned if no root cell has been found of this hive.
|
|
* CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER is returned if the lexical
|
|
* order is not valid. CM_CHECK_REGISTRY_NODE_NOT_FOUND is returned if
|
|
* the no key node could be mapped from the key. CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND
|
|
* is returned if no subkey child cell could be found. CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS
|
|
* is returned if we have reached the maximum stack limit which means the registry that
|
|
* we have checked is too fat.
|
|
*/
|
|
static
|
|
CM_CHECK_REGISTRY_STATUS
|
|
CmpValidateRegistryInternal(
|
|
_In_ PHHIVE Hive,
|
|
_In_ ULONG Flags,
|
|
_In_ BOOLEAN SecurityDefaulted,
|
|
_In_ BOOLEAN FixHive)
|
|
{
|
|
CM_CHECK_REGISTRY_STATUS CmStatusCode;
|
|
PCMP_REGISTRY_STACK_WORK_STATE WorkState;
|
|
HCELL_INDEX RootCell, ParentCell, CurrentCell;
|
|
HCELL_INDEX ChildSubKeyCell;
|
|
PCM_KEY_NODE KeyNode;
|
|
ULONG WorkStateLength;
|
|
LONG StackDepth;
|
|
BOOLEAN AllChildrenChecked;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(Hive);
|
|
|
|
/*
|
|
* Allocate some memory blocks for the stack
|
|
* state structure. We'll be using it to walk
|
|
* down the registry hive tree in a recursive
|
|
* way without worrying that we explode the
|
|
* kernel stack in the most gruesome and gross
|
|
* ways.
|
|
*/
|
|
WorkStateLength = CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH * sizeof(CMP_REGISTRY_STACK_WORK_STATE);
|
|
WorkState = CmpAllocate(WorkStateLength,
|
|
TRUE,
|
|
TAG_REGISTRY_STACK);
|
|
if (!WorkState)
|
|
{
|
|
DPRINT1("Couldn't allocate memory for registry stack work state\n");
|
|
return CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL;
|
|
}
|
|
|
|
/* Obtain the root cell of the hive */
|
|
RootCell = GET_HHIVE_ROOT_CELL(Hive);
|
|
if (RootCell == HCELL_NIL)
|
|
{
|
|
DPRINT1("Couldn't get the root cell of the hive\n");
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND;
|
|
}
|
|
|
|
RestartValidation:
|
|
/*
|
|
* Prepare the stack state and start from
|
|
* the root cell. Ensure that the root cell
|
|
* itself is OK before we go forward.
|
|
*/
|
|
StackDepth = 0;
|
|
WorkState[StackDepth].ChildCellIndex = 0;
|
|
WorkState[StackDepth].Current = RootCell;
|
|
WorkState[StackDepth].Parent = HCELL_NIL;
|
|
WorkState[StackDepth].Sibling = HCELL_NIL;
|
|
|
|
/*
|
|
* As we start checking the root cell which
|
|
* is the top element of a registry hive,
|
|
* we'll be going to look for child keys
|
|
* in the course of walking down the tree.
|
|
*/
|
|
AllChildrenChecked = FALSE;
|
|
|
|
while (StackDepth >= 0)
|
|
{
|
|
/* Cache the current and parent cells */
|
|
CurrentCell = WorkState[StackDepth].Current;
|
|
ParentCell = WorkState[StackDepth].Parent;
|
|
|
|
/* Do we have still have children to validate? */
|
|
if (!AllChildrenChecked)
|
|
{
|
|
/* Check that the key is OK */
|
|
CmStatusCode = CmpValidateKey(Hive,
|
|
SecurityDefaulted,
|
|
ParentCell,
|
|
CurrentCell,
|
|
Flags,
|
|
FixHive);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
/*
|
|
* The key cell is damaged. We have to pray and
|
|
* hope that this is not the root cell as any
|
|
* damage done to the root is catastrophically
|
|
* fatal.
|
|
*/
|
|
if (CurrentCell == RootCell)
|
|
{
|
|
DPRINT1("THE ROOT CELL IS BROKEN\n");
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/*
|
|
* It is not the root, remove the faulting
|
|
* damaged cell from the parent so that we
|
|
* can heal the hive.
|
|
*/
|
|
if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
|
|
{
|
|
DPRINT1("The key is corrupt (current cell %lu, parent cell %lu)\n",
|
|
CurrentCell, ParentCell);
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/* Damaged cell removed, restart the loop */
|
|
DPRINT1("Hive repaired, restarting the validation loop...\n");
|
|
goto RestartValidation;
|
|
}
|
|
|
|
/*
|
|
* The key is in perfect shape. If we have advanced
|
|
* the stack depth then check the lexicographical
|
|
* order of the keys as well.
|
|
*/
|
|
if (StackDepth > 0 &&
|
|
CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
if (WorkState[StackDepth - CMP_PRIOR_STACK].Sibling != HCELL_NIL)
|
|
{
|
|
if (!CmpValidateLexicalOrder(Hive,
|
|
CurrentCell,
|
|
WorkState[StackDepth - CMP_PRIOR_STACK].Sibling))
|
|
{
|
|
/*
|
|
* The lexicographical order is bad,
|
|
* attempt to heal the hive.
|
|
*/
|
|
if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
|
|
{
|
|
DPRINT1("The lexicographical order is invalid (sibling %lu, current cell %lu)\n",
|
|
CurrentCell, WorkState[StackDepth - CMP_PRIOR_STACK].Sibling);
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER;
|
|
}
|
|
|
|
/* Damaged cell removed, restart the loop */
|
|
DPRINT1("Hive repaired, restarting the validation loop...\n");
|
|
goto RestartValidation;
|
|
}
|
|
}
|
|
|
|
/* Assign the prior sibling for upcoming iteration */
|
|
WorkState[StackDepth - CMP_PRIOR_STACK].Sibling = CurrentCell;
|
|
}
|
|
}
|
|
|
|
/* Obtain a node for this key */
|
|
KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, CurrentCell);
|
|
if (!KeyNode)
|
|
{
|
|
DPRINT1("Couldn't get the node of key (current cell %lu)\n", CurrentCell);
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_NODE_NOT_FOUND;
|
|
}
|
|
|
|
/*
|
|
* If we have processed all the children from this
|
|
* node then adjust the stack depth work state by
|
|
* going back and restart the loop to lookup for
|
|
* the rest of the tree. Acknowledge the code path
|
|
* above that we checked all the children so that
|
|
* we don't have to validate the same subkey again.
|
|
*/
|
|
if (WorkState[StackDepth].ChildCellIndex < KeyNode->SubKeyCounts[Stable])
|
|
{
|
|
/*
|
|
* We have children to process, obtain the
|
|
* child subkey in question so that we can
|
|
* cache it later for the next key validation.
|
|
*/
|
|
ChildSubKeyCell = CmpFindSubKeyByNumber(Hive, KeyNode, WorkState[StackDepth].ChildCellIndex);
|
|
if (ChildSubKeyCell == HCELL_NIL)
|
|
{
|
|
DPRINT1("Couldn't get the child subkey cell (at stack index %lu)\n", StackDepth);
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND;
|
|
}
|
|
|
|
/*
|
|
* As we got the subkey advance the child index as
|
|
* well as the stack depth work state for the next
|
|
* key validation. However we must ensure since
|
|
* we're advancing the stack depth that we don't
|
|
* go over the maximum tree level depth. A registry
|
|
* tree can be at maximum 512 levels deep.
|
|
*
|
|
* For more information see https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits.
|
|
*/
|
|
WorkState[StackDepth].ChildCellIndex++;
|
|
StackDepth++;
|
|
if (StackDepth >= CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH - 1)
|
|
{
|
|
/*
|
|
* This registry has so many levels it's
|
|
* so fat. We don't want to explode our
|
|
* kernel stack, so just simply bail out...
|
|
*/
|
|
DPRINT1("The registry tree has so many levels!\n");
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS;
|
|
}
|
|
|
|
/* Prepare the work state for the next key */
|
|
WorkState[StackDepth].ChildCellIndex = 0;
|
|
WorkState[StackDepth].Current = ChildSubKeyCell;
|
|
WorkState[StackDepth].Parent = WorkState[StackDepth - CMP_PRIOR_STACK].Current;
|
|
WorkState[StackDepth].Sibling = HCELL_NIL;
|
|
|
|
/*
|
|
* As we prepared the work state, acknowledge the
|
|
* code path at the top of the loop that we need
|
|
* to process and validate the next child subkey.
|
|
*/
|
|
AllChildrenChecked = FALSE;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We have validated all the child subkeys
|
|
* of the node. Decrease the stack depth
|
|
* and tell the above code we looked for all
|
|
* children so that we don't need to validate
|
|
* the same children again but go for the next
|
|
* node.
|
|
*/
|
|
AllChildrenChecked = TRUE;
|
|
StackDepth--;
|
|
continue;
|
|
}
|
|
|
|
CmpFree(WorkState, WorkStateLength);
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Validates a bin from a hive. It performs checks
|
|
* against the cells from this bin, ensuring the
|
|
* bin is not corrupt and that the cells are consistent
|
|
* with each other.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor of which a hive bin
|
|
* is to be validated.
|
|
*
|
|
* @param[in] Bin
|
|
* A pointer to a bin where its cells are to be
|
|
* validated.
|
|
*
|
|
* @return
|
|
* CM_CHECK_REGISTRY_GOOD is returned if the bin is
|
|
* valid and not corrupt. CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT
|
|
* is returned if this bin has a corrupt signature. CM_CHECK_REGISTRY_BAD_FREE_CELL
|
|
* is returned if the free cell has a bogus size. CM_CHECK_REGISTRY_BAD_ALLOC_CELL
|
|
* is returned for the allocated cell has a bogus size.
|
|
*/
|
|
CM_CHECK_REGISTRY_STATUS
|
|
NTAPI
|
|
HvValidateBin(
|
|
_In_ PHHIVE Hive,
|
|
_In_ PHBIN Bin)
|
|
{
|
|
PHCELL Cell, Basket;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(Bin);
|
|
ASSERT(Hive);
|
|
|
|
/* Ensure that this bin we got has valid signature header */
|
|
if (Bin->Signature != HV_HBIN_SIGNATURE)
|
|
{
|
|
DPRINT1("The bin's signature header is corrupt\n");
|
|
return CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT;
|
|
}
|
|
|
|
/*
|
|
* Walk over all the cells from this bin and
|
|
* validate that they're consistent with the bin.
|
|
* Namely we want that each cell from this bin doesn't
|
|
* have a bogus size.
|
|
*/
|
|
Basket = (PHCELL)((PUCHAR)Bin + Bin->Size);
|
|
for (Cell = GET_CELL_BIN(Bin);
|
|
Cell < Basket;
|
|
Cell = (PHCELL)((PUCHAR)Cell + abs(Cell->Size)))
|
|
{
|
|
if (IsFreeCell(Cell))
|
|
{
|
|
/*
|
|
* This cell is free, check that
|
|
* the size of this cell is not bogus.
|
|
*/
|
|
if (Cell->Size > Bin->Size ||
|
|
Cell->Size == 0)
|
|
{
|
|
/*
|
|
* This cell has too much free space that
|
|
* exceeds the boundary of the bin size.
|
|
* Otherwise the cell doesn't have actual
|
|
* free space (aka Size == 0) which is a
|
|
* no go for a bin.
|
|
*/
|
|
DPRINT1("The free cell exceeds the bin size or cell size equal to 0 (cell 0x%p, cell size %d, bin size %lu)\n",
|
|
Cell, Cell->Size, Bin->Size);
|
|
return CM_CHECK_REGISTRY_BAD_FREE_CELL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* This cell is allocated, make sure that
|
|
* the size of this cell is not bogus.
|
|
*/
|
|
if (abs(Cell->Size) > Bin->Size)
|
|
{
|
|
/*
|
|
* This cell allocated too much space
|
|
* that exceeds the boundary of the
|
|
* bin size.
|
|
*/
|
|
DPRINT1("The allocated cell exceeds the bin size (cell 0x%p, cell size %d, bin size %lu)\n",
|
|
Cell, abs(Cell->Size), Bin->Size);
|
|
return CM_CHECK_REGISTRY_BAD_ALLOC_CELL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Validates a registry hive. This function ensures
|
|
* that the storage of this hive has valid bins.
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a hive descriptor where validation on
|
|
* its hive bins is to be performed.
|
|
*
|
|
* @return
|
|
* CM_CHECK_REGISTRY_GOOD is returned if the hive
|
|
* is valid. CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE is
|
|
* returned if the hive has a corrupted signature.
|
|
* CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT is returned
|
|
* if the captured bin has a bad size. A failure CM status
|
|
* code is returned otherwise.
|
|
*/
|
|
CM_CHECK_REGISTRY_STATUS
|
|
NTAPI
|
|
HvValidateHive(
|
|
_In_ PHHIVE Hive)
|
|
{
|
|
CM_CHECK_REGISTRY_STATUS CmStatusCode;
|
|
ULONG StorageIndex;
|
|
ULONG BlockIndex;
|
|
ULONG StorageLength;
|
|
PHBIN Bin;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(Hive);
|
|
|
|
/* Is the hive signature valid? */
|
|
if (Hive->Signature != HV_HHIVE_SIGNATURE)
|
|
{
|
|
DPRINT1("Hive's signature corrupted (signature %lu)\n", Hive->Signature);
|
|
return CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE;
|
|
}
|
|
|
|
/*
|
|
* Now loop each bin in the storage of this
|
|
* hive.
|
|
*/
|
|
for (StorageIndex = 0; StorageIndex < Hive->StorageTypeCount; StorageIndex++)
|
|
{
|
|
/* Get the storage length at this index */
|
|
StorageLength = Hive->Storage[StorageIndex].Length;
|
|
|
|
for (BlockIndex = 0; BlockIndex < StorageLength;)
|
|
{
|
|
/* Go to the next if this bin does not exist */
|
|
if (Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress == (ULONG_PTR)NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Capture this bin and ensure that such
|
|
* bin is within the offset and the size
|
|
* is not bogus.
|
|
*/
|
|
Bin = GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex);
|
|
if (Bin->Size > (StorageLength * HBLOCK_SIZE) ||
|
|
(Bin->FileOffset / HBLOCK_SIZE) != BlockIndex)
|
|
{
|
|
DPRINT1("Bin size or offset is corrupt (bin size %lu, file offset %lu, storage length %lu)\n",
|
|
Bin->Size, Bin->FileOffset, StorageLength);
|
|
return CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT;
|
|
}
|
|
|
|
/* Validate the rest of the bin */
|
|
CmStatusCode = HvValidateBin(Hive, Bin);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
DPRINT1("This bin is not valid (bin 0x%p)\n", Bin);
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/* Go to the next block */
|
|
BlockIndex += Bin->Size / HBLOCK_SIZE;
|
|
}
|
|
}
|
|
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Checks the registry that is consistent and its
|
|
* contents valid and not corrupted. More specifically
|
|
* this function performs a deep check of the registry
|
|
* for the following properties:
|
|
*
|
|
* - That the security cache cell of the registry is OK
|
|
* - That bins and cells are consistent with each other
|
|
* - That the child subkey cell points to the parent
|
|
* - That the key itself has sane sizes
|
|
* - That the class, values and subkeys lists are valid
|
|
* - Much more
|
|
*
|
|
* @param[in] Hive
|
|
* A pointer to a CM hive of the registry to be checked
|
|
* in question.
|
|
*
|
|
* @param[in] Flags
|
|
* A bit mask flag used to influence the process of volatile
|
|
* keys purging. See Remarks for further information.
|
|
*
|
|
* @return
|
|
* This function returns a CM (Configuration Manager) check
|
|
* registry status code. A code of CM_CHECK_REGISTRY_GOOD of
|
|
* value 0 indicates the registry hive is valid and not corrupted.
|
|
* A non zero unsigned integer value indicates a failure. Consult
|
|
* other private routines in this file for other failure status
|
|
* codes.
|
|
*
|
|
* @remarks
|
|
* During a load operation CmCheckRegistry can purge the volatile
|
|
* data of registry (or not) depending on the submitted flag bit mask
|
|
* by the caller. The following supported flags are:
|
|
*
|
|
* CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES -- Tells the function that
|
|
* volatile data purging must not be done.
|
|
*
|
|
* CM_CHECK_REGISTRY_PURGE_VOLATILES - Tells the function to purge out
|
|
* volatile information data from a registry hive, on demand. Purging
|
|
* doesn't come into action if no volatile data has been found.
|
|
*
|
|
* CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES - A special flag used
|
|
* by FreeLdr and Environ. When this flag is set the function will not
|
|
* clean up the volatile storage but it will unintialize the storage
|
|
* instead (this is the case if the given registry hive for validation
|
|
* is a XP Beta 1 hive or newer). Otherwise it will perform a normal
|
|
* cleanup of the volatile storage.
|
|
*
|
|
* CM_CHECK_REGISTRY_VALIDATE_HIVE - Tells the function to perform a
|
|
* thorough analysation of the underlying hive's bins and cells before
|
|
* doing validation of the registry tree. HvValidateHive function is called
|
|
* in this case.
|
|
*
|
|
* CM_CHECK_REGISTRY_FIX_HIVE - Tells the function to fix the target registry
|
|
* hive if it is damaged. Usually this flag comes from a registry repair tool
|
|
* where the user asked to for its damaged hive to be fixed. In this case
|
|
* a self-heal procedure against the hive is performed.
|
|
*/
|
|
CM_CHECK_REGISTRY_STATUS
|
|
NTAPI
|
|
CmCheckRegistry(
|
|
_In_ PCMHIVE RegistryHive,
|
|
_In_ ULONG Flags)
|
|
{
|
|
CM_CHECK_REGISTRY_STATUS CmStatusCode;
|
|
PHHIVE Hive;
|
|
BOOLEAN ShouldFixHive = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Bail out if the caller did not give a hive */
|
|
if (!RegistryHive)
|
|
{
|
|
DPRINT1("No registry hive given for check\n");
|
|
return CM_CHECK_REGISTRY_INVALID_PARAMETER;
|
|
}
|
|
|
|
#if !defined(CMLIB_HOST) && !defined(_BLDR_)
|
|
/*
|
|
* The master hive is the root of the registry,
|
|
* it holds all other hives together. So do not
|
|
* do any validation checks.
|
|
*/
|
|
if (RegistryHive == CmiVolatileHive)
|
|
{
|
|
DPRINT("This is master registry hive, don't do anything\n");
|
|
return CM_CHECK_REGISTRY_GOOD;
|
|
}
|
|
#endif
|
|
|
|
/* Bail out if no valid flag is given */
|
|
if (Flags & ~(CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES |
|
|
CM_CHECK_REGISTRY_PURGE_VOLATILES |
|
|
CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES |
|
|
CM_CHECK_REGISTRY_VALIDATE_HIVE |
|
|
CM_CHECK_REGISTRY_FIX_HIVE))
|
|
{
|
|
DPRINT1("Invalid flag for registry check given (flag %lu)\n", Flags);
|
|
return CM_CHECK_REGISTRY_INVALID_PARAMETER;
|
|
}
|
|
|
|
/*
|
|
* Obtain the hive and check if the caller wants
|
|
* that the hive to be validated.
|
|
*/
|
|
Hive = GET_HHIVE(RegistryHive);
|
|
if (Flags & CM_CHECK_REGISTRY_VALIDATE_HIVE)
|
|
{
|
|
CmStatusCode = HvValidateHive(Hive);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
DPRINT1("The hive is not valid (hive 0x%p, check status code %lu)\n", Hive, CmStatusCode);
|
|
return CmStatusCode;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A registry repair tool such as the ReactOS Check Registry
|
|
* Utility wants the damaged hive to be fixed as we check the
|
|
* target hive.
|
|
*/
|
|
if (Flags & CM_CHECK_REGISTRY_FIX_HIVE)
|
|
{
|
|
ShouldFixHive = TRUE;
|
|
}
|
|
|
|
/*
|
|
* FIXME: Currently ReactOS does not implement security
|
|
* caching algorithms so it's pretty pointless to implement
|
|
* security descriptors validation checks at this moment.
|
|
* When the time comes to implement these, we would need
|
|
* to implement security checks here as well.
|
|
*/
|
|
|
|
/* Call the internal API to do the rest of the work bulk */
|
|
CmStatusCode = CmpValidateRegistryInternal(Hive, Flags, FALSE, ShouldFixHive);
|
|
if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
|
|
{
|
|
DPRINT1("The hive is not valid (hive 0x%p, check status code %lu)\n", Hive, CmStatusCode);
|
|
return CmStatusCode;
|
|
}
|
|
|
|
return CmStatusCode;
|
|
}
|
|
|
|
/* EOF */
|