/*
 * 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 */