diff --git a/ntoskrnl/config/cmcheck.c b/ntoskrnl/config/cmcheck.c deleted file mode 100644 index 4172dddc332..00000000000 --- a/ntoskrnl/config/cmcheck.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * PROJECT: ReactOS Kernel - * LICENSE: GPL - See COPYING in the top level directory - * FILE: ntoskrnl/config/cmcheck.c - * PURPOSE: Configuration Manager - Hive and Key Validation - * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) - */ - -/* INCLUDES ******************************************************************/ - -#include "ntoskrnl.h" -#define NDEBUG -#include "debug.h" - -/* GLOBALS *******************************************************************/ - -/* FUNCTIONS *****************************************************************/ - -ULONG -NTAPI -CmCheckRegistry(IN PCMHIVE RegistryHive, - IN ULONG Flags) -{ - /* FIXME: HACK! */ - DPRINT1("CmCheckRegistry(0x%p, %lu) is UNIMPLEMENTED!\n", RegistryHive, Flags); - return 0; -} diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h index 5c2f352f753..04869a812bc 100644 --- a/ntoskrnl/include/internal/cm.h +++ b/ntoskrnl/include/internal/cm.h @@ -595,16 +595,6 @@ CmpCompareNewValueDataAgainstKCBCache( IN ULONG DataSize ); -// -// Registry Validation Functions -// -ULONG -NTAPI -CmCheckRegistry( - IN PCMHIVE Hive, - IN ULONG Flags -); - // // Hive List Routines // diff --git a/ntoskrnl/ntos.cmake b/ntoskrnl/ntos.cmake index 47e7c01eda3..d9328ae3d01 100644 --- a/ntoskrnl/ntos.cmake +++ b/ntoskrnl/ntos.cmake @@ -48,7 +48,6 @@ list(APPEND SOURCE ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmalloc.c ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmapi.c ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmboot.c - ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmcheck.c ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmconfig.c ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmcontrl.c ${REACTOS_SOURCE_DIR}/ntoskrnl/config/cmdata.c diff --git a/sdk/lib/cmlib/CMakeLists.txt b/sdk/lib/cmlib/CMakeLists.txt index 91ccbe6c3e5..ef8bff3ea66 100644 --- a/sdk/lib/cmlib/CMakeLists.txt +++ b/sdk/lib/cmlib/CMakeLists.txt @@ -4,6 +4,7 @@ add_definitions( -DNASSERT) list(APPEND SOURCE + cmcheck.c cminit.c cmheal.c cmindex.c diff --git a/sdk/lib/cmlib/cmcheck.c b/sdk/lib/cmlib/cmcheck.c new file mode 100644 index 00000000000..0c1d82b0555 --- /dev/null +++ b/sdk/lib/cmlib/cmcheck.c @@ -0,0 +1,1718 @@ +/* + * 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 + */ + +#include "cmlib.h" +#define NDEBUG +#include + +/* 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 */ diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h index 65f66f581b0..77911739ab6 100644 --- a/sdk/lib/cmlib/cmlib.h +++ b/sdk/lib/cmlib/cmlib.h @@ -205,13 +205,77 @@ #endif #endif -#define TAG_CM ' MC' -#define TAG_KCB 'bkMC' -#define TAG_CMHIVE 'vHMC' -#define TAG_CMSD 'DSMC' +#define TAG_CM ' MC' +#define TAG_KCB 'bkMC' +#define TAG_CMHIVE 'vHMC' +#define TAG_CMSD 'DSMC' +#define TAG_REGISTRY_STACK 'sRMC' #define CMAPI NTAPI +// +// Check Registry status type definition +// +typedef ULONG CM_CHECK_REGISTRY_STATUS; + +// +// Check Registry flags +// +#define CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES 0x0 +#define CM_CHECK_REGISTRY_PURGE_VOLATILES 0x2 +#define CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES 0x4 +#define CM_CHECK_REGISTRY_VALIDATE_HIVE 0x8 +#define CM_CHECK_REGISTRY_FIX_HIVE 0x10 + +// +// Check Registry status codes +// +#define CM_CHECK_REGISTRY_GOOD 0 +#define CM_CHECK_REGISTRY_INVALID_PARAMETER 1 +#define CM_CHECK_REGISTRY_SD_INVALID 2 +#define CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE 3 +#define CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT 4 +#define CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT 5 +#define CM_CHECK_REGISTRY_BAD_FREE_CELL 6 +#define CM_CHECK_REGISTRY_BAD_ALLOC_CELL 7 +#define CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL 8 +#define CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND 9 +#define CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER 10 +#define CM_CHECK_REGISTRY_NODE_NOT_FOUND 11 +#define CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND 12 +#define CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS 13 +#define CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED 14 +#define CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND 15 +#define CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE 16 +#define CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO 17 +#define CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL 18 +#define CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT 19 +#define CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE 20 +#define CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED 21 +#define CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED 22 +#define CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND 23 +#define CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE 24 +#define CM_CHECK_REGISTRY_VALUE_CELL_NIL 25 +#define CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED 26 +#define CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND 27 +#define CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE 28 +#define CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA 29 +#define CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED 30 +#define CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE 31 +#define CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE 32 +#define CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED 33 +#define CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX 34 +#define CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT 35 +#define CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED 36 +#define CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT 37 +#define CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE 38 +#define CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE 39 + +// +// Check Registry success macro +// +#define CM_CHECK_REGISTRY_SUCCESS(StatusCode) ((ULONG)(StatusCode) == CM_CHECK_REGISTRY_GOOD) + #include #include #include "hivedata.h" @@ -570,6 +634,26 @@ CmPrepareHive( /* NT-style Public Cm functions */ +// +// Check Registry Routines +// +CM_CHECK_REGISTRY_STATUS +NTAPI +HvValidateBin( + _In_ PHHIVE Hive, + _In_ PHBIN Bin); + +CM_CHECK_REGISTRY_STATUS +NTAPI +HvValidateHive( + _In_ PHHIVE Hive); + +CM_CHECK_REGISTRY_STATUS +NTAPI +CmCheckRegistry( + _In_ PCMHIVE RegistryHive, + _In_ ULONG Flags); + // // Cell Index Routines //