xref: /reactos/sdk/lib/cmlib/cmcheck.c (revision ee5338ff)
1 /*
2  * PROJECT:     ReactOS Kernel
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Configuration Manager Library - Registry Validation
5  * COPYRIGHT:   Copyright 2022 George Bișoc <george.bisoc@reactos.org>
6  */
7 
8 #include "cmlib.h"
9 #define NDEBUG
10 #include <debug.h>
11 
12 /* STRUCTURES ****************************************************************/
13 
14 typedef struct _CMP_REGISTRY_STACK_WORK_STATE
15 {
16     ULONG ChildCellIndex;
17     HCELL_INDEX Parent;
18     HCELL_INDEX Current;
19     HCELL_INDEX Sibling;
20 } CMP_REGISTRY_STACK_WORK_STATE, *PCMP_REGISTRY_STACK_WORK_STATE;
21 
22 /* DEFINES  ******************************************************************/
23 
24 #define GET_HHIVE(CmHive) (&((CmHive)->Hive))
25 #define GET_HHIVE_ROOT_CELL(Hive) ((Hive)->BaseBlock->RootCell)
26 #define GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex) ((PHBIN)Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress)
27 #define GET_CELL_BIN(Bin) ((PHCELL)((PUCHAR)Bin + sizeof(HBIN)))
28 
29 #define IS_CELL_VOLATILE(Cell) (HvGetCellType(Cell) == Volatile)
30 
31 #if !defined(CMLIB_HOST) && !defined(_BLDR_)
32 extern PCMHIVE CmiVolatileHive;
33 #endif
34 
35 #define CMP_PRIOR_STACK 1
36 #define CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH 512
37 
38 #define CMP_KEY_SIZE_THRESHOLD 0x45C
39 #define CMP_VOLATILE_LIST_UNINTIALIZED 0xBAADF00D
40 
41 /* PRIVATE FUNCTIONS **********************************************************/
42 
43 /**
44  * @brief
45  * Validates the lexicographical order between a child
46  * and prior sibling of the said child.
47  *
48  * @param[in] Hive
49  * A pointer to a hive descriptor of which lexicographical
50  * order of keys are to be checked.
51  *
52  * @param[in] Child
53  * A child subkey cell used for lexicographical order
54  * validation checks.
55  *
56  * @param[in] Sibling
57  * A subkey cell which is the prior sibling of the child.
58  * This is used in conjuction with the child to perfrom
59  * lexical order checks.
60  *
61  * @return
62  * Returns TRUE if the order is legal, FALSE otherwise.
63  */
64 static
65 BOOLEAN
CmpValidateLexicalOrder(_In_ PHHIVE Hive,_In_ HCELL_INDEX Child,_In_ HCELL_INDEX Sibling)66 CmpValidateLexicalOrder(
67     _In_ PHHIVE Hive,
68     _In_ HCELL_INDEX Child,
69     _In_ HCELL_INDEX Sibling)
70 {
71     LONG Result;
72     UNICODE_STRING ChildString, SiblingString;
73     PCM_KEY_NODE ChildNode, SiblingNode;
74 
75     PAGED_CODE();
76 
77     /* Obtain the child node */
78     ChildNode = (PCM_KEY_NODE)HvGetCell(Hive, Child);
79     if (!ChildNode)
80     {
81         /* Couldn't get the child node, bail out */
82         DPRINT1("Failed to get the child node\n");
83         return FALSE;
84     }
85 
86     /* Obtain the sibling node */
87     SiblingNode = (PCM_KEY_NODE)HvGetCell(Hive, Sibling);
88     if (!SiblingNode)
89     {
90         /* Couldn't get the sibling node, bail out */
91         DPRINT1("Failed to get the sibling node\n");
92         return FALSE;
93     }
94 
95     /* CASE 1: Two distinct non-compressed Unicode names */
96     if ((ChildNode->Flags & KEY_COMP_NAME) == 0 &&
97         (SiblingNode->Flags & KEY_COMP_NAME) == 0)
98     {
99         /* Build the sibling string */
100         SiblingString.Buffer = &(SiblingNode->Name[0]);
101         SiblingString.Length = SiblingNode->NameLength;
102         SiblingString.MaximumLength = SiblingNode->NameLength;
103 
104         /* Build the child string */
105         ChildString.Buffer = &(ChildNode->Name[0]);
106         ChildString.Length = ChildNode->NameLength;
107         ChildString.MaximumLength = ChildNode->NameLength;
108 
109         Result = RtlCompareUnicodeString(&SiblingString, &ChildString, TRUE);
110         if (Result >= 0)
111         {
112             DPRINT1("The sibling node name is greater or equal to that of the child\n");
113             return FALSE;
114         }
115     }
116 
117     /* CASE 2: Both compressed Unicode names */
118     if ((ChildNode->Flags & KEY_COMP_NAME) &&
119         (SiblingNode->Flags & KEY_COMP_NAME))
120     {
121         /* FIXME: Checks for two compressed names not implemented yet */
122         DPRINT("Lexicographical order checks for two compressed names is UNIMPLEMENTED, assume the key is healthy...\n");
123         return TRUE;
124     }
125 
126     /* CASE 3: The child name is compressed but the sibling is not */
127     if ((ChildNode->Flags & KEY_COMP_NAME) &&
128         (SiblingNode->Flags & KEY_COMP_NAME) == 0)
129     {
130         SiblingString.Buffer = &(SiblingNode->Name[0]);
131         SiblingString.Length = SiblingNode->NameLength;
132         SiblingString.MaximumLength = SiblingNode->NameLength;
133         Result = CmpCompareCompressedName(&SiblingString,
134                                           ChildNode->Name,
135                                           ChildNode->NameLength);
136         if (Result >= 0)
137         {
138             DPRINT1("The sibling node name is greater or equal to that of the compressed child\n");
139             return FALSE;
140         }
141     }
142 
143     /* CASE 4: The sibling name is compressed but the child is not */
144     if ((SiblingNode->Flags & KEY_COMP_NAME) &&
145         (ChildNode->Flags & KEY_COMP_NAME) == 0)
146     {
147         ChildString.Buffer = &(ChildNode->Name[0]);
148         ChildString.Length = ChildNode->NameLength;
149         ChildString.MaximumLength = ChildNode->NameLength;
150         Result = CmpCompareCompressedName(&ChildString,
151                                           SiblingNode->Name,
152                                           SiblingNode->NameLength);
153         if (Result <= 0)
154         {
155             DPRINT1("The compressed sibling node name is lesser or equal to that of the child\n");
156             return FALSE;
157         }
158     }
159 
160     /*
161      * One of the cases above has met the conditions
162      * successfully, the lexicographical order is legal.
163      */
164     return TRUE;
165 }
166 
167 /**
168  * @brief
169  * Validates the class of a given key cell.
170  *
171  * @param[in] Hive
172  * A pointer to a hive descriptor of which
173  * the registry call is to be validated.
174  *
175  * @param[in] CurrentCell
176  * The current key cell that the class points to.
177  *
178  * @param[in] CellData
179  * A pointer to cell data of the current key cell
180  * that contains the class to be validated.
181  *
182  * @return
183  * Returns CM_CHECK_REGISTRY_GOOD if the class is in good shape.
184  * The same CM status code is returned if the class doesn't
185  * but the class length says otherwise. CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED
186  * is returned if the class cell is not allocated.
187  */
188 static
189 CM_CHECK_REGISTRY_STATUS
CmpValidateClass(_In_ PHHIVE Hive,_In_ HCELL_INDEX CurrentCell,_Inout_ PCELL_DATA CellData)190 CmpValidateClass(
191     _In_ PHHIVE Hive,
192     _In_ HCELL_INDEX CurrentCell,
193     _Inout_ PCELL_DATA CellData)
194 {
195     ULONG ClassLength;
196     HCELL_INDEX ClassCell;
197 
198     PAGED_CODE();
199 
200     ASSERT(CurrentCell != HCELL_NIL);
201     ASSERT(CellData);
202 
203     /* Cache the class cell and validate it (if any) */
204     ClassCell = CellData->u.KeyNode.Class;
205     ClassLength = CellData->u.KeyNode.ClassLength;
206     if (ClassLength > 0)
207     {
208         if (ClassCell == HCELL_NIL)
209         {
210             /*
211              * Somebody has freed the class but left the
212              * length as is, reset it.
213              */
214             DPRINT1("The key node class is NIL but the class length is not 0, resetting it\n");
215             HvMarkCellDirty(Hive, CurrentCell, FALSE);
216             CellData->u.KeyNode.ClassLength = 0;
217             return CM_CHECK_REGISTRY_GOOD;
218         }
219 
220         if (!HvIsCellAllocated(Hive, ClassCell))
221         {
222             DPRINT1("The key class is not allocated\n");
223             return CM_CHECK_REGISTRY_KEY_CLASS_UNALLOCATED;
224         }
225     }
226 
227     return CM_CHECK_REGISTRY_GOOD;
228 }
229 
230 /**
231  * @brief
232  * Validates each value in the list by count. A
233  * value that is damaged gets removed from the list.
234  * This routine performs self-healing process in
235  * this case.
236  *
237  * @param[in] Hive
238  * A pointer to a hive descriptor of which a list
239  * of registry values is to be validated.
240  *
241  * @param[in] CurrentCell
242  * The current key cell that the value list points to.
243  *
244  * @param[in] ListCount
245  * The list count that describes the actual number of
246  * values in the list.
247  *
248  * @param[in] ValueListData
249  * A pointer to cell data of the current key cell
250  * that contains the value list to be validated.
251  *
252  * @param[out] ValuesRemoved
253  * When the function finishes doing its operations,
254  * this parameter contains the amount of removed values
255  * from the list. A value of 0 indicates no values have
256  * been removed (which that would imply a self-healing
257  * process of the value list has occurred).
258  *
259  * @param[in] FixHive
260  * If set to TRUE, the target hive will be fixed.
261  *
262  * @return
263  * Returns CM_CHECK_REGISTRY_GOOD if the value list is
264  * sane. CM_CHECK_REGISTRY_VALUE_CELL_NIL is returned
265  * if a certain value cell is HCELL_NIL at specific
266  * count index. CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED is
267  * returned if a certain value cell is unallocated at specific
268  * count index. CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND is
269  * returned if cell data could not be mapped from the value cell,
270  * the value list is totally torn apart in this case.
271  * CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE is returned if the
272  * value's size is bogus. CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA
273  * is returned if the data inside the value is HCELL_NIL.
274  * CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED is returned if the
275  * data cell inside the value is not allocated.
276  * CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE is returned if the
277  * value's signature is not valid.
278  */
279 static
280 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)281 CmpValidateValueListByCount(
282     _In_ PHHIVE Hive,
283     _In_ HCELL_INDEX CurrentCell,
284     _In_ ULONG ListCount,
285     _In_ PCELL_DATA ValueListData,
286     _Out_ PULONG ValuesRemoved,
287     _In_ BOOLEAN FixHive)
288 {
289     ULONG ValueDataSize;
290     ULONG ListCountIndex;
291     ULONG DataSize;
292     HCELL_INDEX DataCell;
293     HCELL_INDEX ValueCell;
294     PCELL_DATA ValueData;
295     ULONG ValueNameLength, TotalValueNameLength;
296 
297     PAGED_CODE();
298 
299     ASSERT(ValueListData);
300     ASSERT(ListCount != 0);
301 
302     /* Assume we haven't removed any value counts for now */
303     *ValuesRemoved = 0;
304 
305     /*
306      * Begin looping each value in the list and
307      * validate it accordingly.
308      */
309     ListCountIndex = 0;
310     while (ListCountIndex < ListCount)
311     {
312         ValueCell = ValueListData->u.KeyList[ListCountIndex];
313         if (ValueCell == HCELL_NIL)
314         {
315             if (!CmpRepairValueListCount(Hive,
316                                          CurrentCell,
317                                          ListCountIndex,
318                                          ValueListData,
319                                          FixHive))
320             {
321                 DPRINT1("The value cell is NIL (at index %u, list count %u)\n",
322                         ListCountIndex, ListCount);
323                 return CM_CHECK_REGISTRY_VALUE_CELL_NIL;
324             }
325 
326             /* Decrease the list count and go to the next value */
327             ListCount--;
328             *ValuesRemoved++;
329             DPRINT1("Damaged value removed, continuing with the next value...\n");
330             continue;
331         }
332 
333         if (!HvIsCellAllocated(Hive, ValueCell))
334         {
335             if (!CmpRepairValueListCount(Hive,
336                                          CurrentCell,
337                                          ListCountIndex,
338                                          ValueListData,
339                                          FixHive))
340             {
341                 DPRINT1("The value cell is not allocated (at index %u, list count %u)\n",
342                         ListCountIndex, ListCount);
343                 return CM_CHECK_REGISTRY_VALUE_CELL_UNALLOCATED;
344             }
345 
346             /* Decrease the list count and go to the next value */
347             ListCount--;
348             *ValuesRemoved++;
349             DPRINT1("Damaged value removed, continuing with the next value...\n");
350             continue;
351         }
352 
353         /* Obtain a cell data from this value */
354         ValueData = (PCELL_DATA)HvGetCell(Hive, ValueCell);
355         if (!ValueData)
356         {
357             DPRINT1("Cell data of the value cell not found (at index %u, value count %u)\n",
358                     ListCountIndex, ListCount);
359             return CM_CHECK_REGISTRY_VALUE_CELL_DATA_NOT_FOUND;
360         }
361 
362         /* Check that the value size is sane */
363         ValueDataSize = HvGetCellSize(Hive, ValueData);
364         ValueNameLength = ValueData->u.KeyValue.NameLength;
365         TotalValueNameLength = ValueNameLength + FIELD_OFFSET(CM_KEY_VALUE, Name);
366         if (TotalValueNameLength > ValueDataSize)
367         {
368             if (!CmpRepairValueListCount(Hive,
369                                          CurrentCell,
370                                          ListCountIndex,
371                                          ValueListData,
372                                          FixHive))
373             {
374                 DPRINT1("The total size is bigger than the actual cell size (total size %u, cell size %u, at index %u)\n",
375                         TotalValueNameLength, ValueDataSize, ListCountIndex);
376                 return CM_CHECK_REGISTRY_VALUE_CELL_SIZE_NOT_SANE;
377             }
378 
379             /* Decrease the list count and go to the next value */
380             ListCount--;
381             *ValuesRemoved++;
382             DPRINT1("Damaged value removed, continuing with the next value...\n");
383             continue;
384         }
385 
386         /*
387          * The value cell has a sane size. The last thing
388          * to validate is the actual data of the value cell.
389          * That is, we want that the data itself and length
390          * are consistent. Technically speaking, value keys
391          * that are small are directly located in the value
392          * cell and it's built-in, in other words, the data
393          * is immediately present in the cell so we don't have
394          * to bother validating them since they're alright on
395          * their own. This can't be said the same about normal
396          * values though.
397          */
398         DataCell = ValueData->u.KeyValue.Data;
399         if (!CmpIsKeyValueSmall(&DataSize, ValueData->u.KeyValue.DataLength))
400         {
401             /* Validate the actual data based on size */
402             if (DataSize == 0)
403             {
404                 if (DataCell != HCELL_NIL)
405                 {
406                     if (!CmpRepairValueListCount(Hive,
407                                                  CurrentCell,
408                                                  ListCountIndex,
409                                                  ValueListData,
410                                                  FixHive))
411                     {
412                         DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
413                         return CM_CHECK_REGISTRY_CORRUPT_VALUE_DATA;
414                     }
415 
416                     /* Decrease the list count and go to the next value */
417                     ListCount--;
418                     *ValuesRemoved++;
419                     DPRINT1("Damaged value removed, continuing with the next value...\n");
420                     continue;
421                 }
422             }
423             else
424             {
425                 if (!HvIsCellAllocated(Hive, DataCell))
426                 {
427                     if (!CmpRepairValueListCount(Hive,
428                                                  CurrentCell,
429                                                  ListCountIndex,
430                                                  ValueListData,
431                                                  FixHive))
432                     {
433                         DPRINT1("The data is not NIL on a 0 length, data is corrupt\n");
434                         return CM_CHECK_REGISTRY_DATA_CELL_NOT_ALLOCATED;
435                     }
436 
437                     /* Decrease the list count and go to the next value */
438                     ListCount--;
439                     *ValuesRemoved++;
440                     DPRINT1("Damaged value removed, continuing with the next value...\n");
441                     continue;
442                 }
443             }
444 
445             /* FIXME: Big values not supported yet */
446             ASSERT_VALUE_BIG(Hive, DataSize);
447         }
448 
449         /* Is the signature valid? */
450         if (ValueData->u.KeyValue.Signature != CM_KEY_VALUE_SIGNATURE)
451         {
452             if (!CmpRepairValueListCount(Hive,
453                                          CurrentCell,
454                                          ListCountIndex,
455                                          ValueListData,
456                                          FixHive))
457             {
458                 DPRINT1("The key value signature is invalid\n");
459                 return CM_CHECK_REGISTRY_BAD_KEY_VALUE_SIGNATURE;
460             }
461 
462             /* Decrease the list count and go to the next value */
463             ListCount--;
464             *ValuesRemoved++;
465             DPRINT1("Damaged value removed, continuing with the next value...\n");
466             continue;
467         }
468 
469         /* Advance to the next value */
470         ListCountIndex++;
471     }
472 
473     return CM_CHECK_REGISTRY_GOOD;
474 }
475 
476 /**
477  * @brief
478  * Validates the value list of a key. If the list
479  * is damaged due to corruption, the whole list
480  * is expunged. This function performs self-healing
481  * procedures in this case.
482  *
483  * @param[in] Hive
484  * A pointer to a hive descriptor of which a list of
485  * registry values is to be validated.
486  *
487  * @param[in] CurrentCell
488  * The current key cell that the value list points to.
489  *
490  * @param[in] CellData
491  * The cell data of the current cell of which the value
492  * list comes from.
493  *
494  * @param[in] FixHive
495  * If set to TRUE, the target hive will be fixed.
496  *
497  * @return
498  * Returns CM_CHECK_REGISTRY_GOOD if the value list is
499  * sane. CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED is returned
500  * if the value list cell is not allocated. CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND
501  * is returned if cell data could not be mapped from the value
502  * list cell. CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE is returned
503  * if the size of the value list is bogus. A failure CM status code
504  * is returned otherwise.
505  */
506 static
507 CM_CHECK_REGISTRY_STATUS
CmpValidateValueList(_In_ PHHIVE Hive,_In_ HCELL_INDEX CurrentCell,_Inout_ PCELL_DATA CellData,_In_ BOOLEAN FixHive)508 CmpValidateValueList(
509     _In_ PHHIVE Hive,
510     _In_ HCELL_INDEX CurrentCell,
511     _Inout_ PCELL_DATA CellData,
512     _In_ BOOLEAN FixHive)
513 {
514     CM_CHECK_REGISTRY_STATUS CmStatusCode;
515     ULONG TotalValueLength, ValueSize;
516     ULONG ValueListCount;
517     ULONG ValuesRemoved;
518     HCELL_INDEX ValueListCell;
519     PCELL_DATA ValueListData;
520 
521     PAGED_CODE();
522 
523     ASSERT(CurrentCell != HCELL_NIL);
524     ASSERT(CellData);
525 
526     /* Cache the value list and validate it */
527     ValueListCell = CellData->u.KeyNode.ValueList.List;
528     ValueListCount = CellData->u.KeyNode.ValueList.Count;
529     if (ValueListCount > 0)
530     {
531         if (!HvIsCellAllocated(Hive, ValueListCell))
532         {
533             DPRINT1("The value list is not allocated\n");
534             return CM_CHECK_REGISTRY_VALUE_LIST_UNALLOCATED;
535         }
536 
537         /* Obtain cell data from the value list cell */
538         ValueListData = (PCELL_DATA)HvGetCell(Hive, ValueListCell);
539         if (!ValueListData)
540         {
541             DPRINT1("Could not get cell data from the value list\n");
542             return CM_CHECK_REGISTRY_VALUE_LIST_DATA_NOT_FOUND;
543         }
544 
545         /*
546          * Cache the value size and total length and
547          * assert ourselves this is a sane value list
548          * to begin with.
549          */
550         ValueSize = HvGetCellSize(Hive, ValueListData);
551         TotalValueLength = ValueListCount * sizeof(HCELL_INDEX);
552         if (TotalValueLength > ValueSize)
553         {
554             DPRINT1("The value list is bigger than the cell (value list size %u, cell size %u)\n",
555                     TotalValueLength, ValueSize);
556             return CM_CHECK_REGISTRY_VALUE_LIST_SIZE_NOT_SANE;
557         }
558 
559         /*
560          * The value list is sane, now we would
561          * need to validate the actual list
562          * by its count.
563          */
564         CmStatusCode = CmpValidateValueListByCount(Hive,
565                                                    CurrentCell,
566                                                    ValueListCount,
567                                                    ValueListData,
568                                                    &ValuesRemoved,
569                                                    FixHive);
570         if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
571         {
572             DPRINT1("One of the values is corrupt and couldn't be repaired\n");
573             return CmStatusCode;
574         }
575 
576         /* Log how much values have been removed */
577         if (ValuesRemoved > 0)
578         {
579             DPRINT1("%u values removed in the list\n", ValuesRemoved);
580         }
581     }
582 
583     return CM_CHECK_REGISTRY_GOOD;
584 }
585 
586 /**
587  * @brief
588  * Validates the subkeys list of a key. If the list is
589  * damaged from corruption, the function can either
590  * salvage this list or purge the whole of it. The
591  * function performs different validation steps for
592  * different storage types.
593  *
594  * @param[in] Hive
595  * A pointer to a hive descriptor of which a list of
596  * subkeys is to be validated.
597  *
598  * @param[in] CurrentCell
599  * The current key cell that the subkeys list points to.
600  *
601  * @param[in] CellData
602  * The cell data of the current cell of which the subkeys
603  * list comes from.
604  *
605  * @param[in] FixHive
606  * If set to TRUE, the target hive will be fixed.
607  *
608  * @param[out] DoRepair
609  * A pointer to a boolean value set up by the function itself.
610  * The function automatically sets this to FALSE indicating
611  * that repairs can't be done onto the list itself. If the
612  * list can be salvaged, then the function sets this to TRUE.
613  * See Remarks for further information.
614  *
615  * @return
616  * Returns CM_CHECK_REGISTRY_GOOD if the subkeys list is in
617  * perfect shape. CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE is
618  * returned if the volatile storage has stable data which
619  * that should not happen (this is only for the case of volatile
620  * cells). CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED is returned
621  * if the subkeys list cell is not allocated. CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX
622  * is returned if a key index could not be mapped from the subkeys
623  * list cell. CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT is returned if
624  * the key index is a leaf and the subkeys count doesn't match up
625  * with that of the leaf. CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED is
626  * returned if the key index cell at the specific index in the list of
627  * the index is not allocated. CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT is
628  * returned if a leaf could not be mapped from an index.
629  * CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE is returned if the leaf has
630  * an invalid signature. CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE
631  * is returned if the key index has an invalid signature, that is, it's
632  * not a leaf nor a root.
633  *
634  * @remarks
635  * Deep subkeys list healing can be done in specific cases where only
636  * a subkey doesn't actually affect the key itself. The function will
637  * mark the subkeys list as repairable by setting DoRepair parameter
638  * to TRUE and the caller is responsible to heal the key by purging
639  * the whole subkeys list. If the damage is so bad that there's
640  * possibility the key itself is even damaged, no healing is done.
641  */
642 static
643 CM_CHECK_REGISTRY_STATUS
CmpValidateSubKeyList(_In_ PHHIVE Hive,_In_ HCELL_INDEX CurrentCell,_Inout_ PCELL_DATA CellData,_In_ BOOLEAN FixHive,_Out_ PBOOLEAN DoRepair)644 CmpValidateSubKeyList(
645     _In_ PHHIVE Hive,
646     _In_ HCELL_INDEX CurrentCell,
647     _Inout_ PCELL_DATA CellData,
648     _In_ BOOLEAN FixHive,
649     _Out_ PBOOLEAN DoRepair)
650 {
651     ULONG SubKeyCounts;
652     HCELL_INDEX KeyIndexCell, SubKeysListCell;
653     PCM_KEY_INDEX RootKeyIndex, LeafKeyIndex;
654     ULONG RootIndex;
655     ULONG TotalLeafCount;
656 
657     PAGED_CODE();
658 
659     ASSERT(CurrentCell != HCELL_NIL);
660     ASSERT(CellData);
661 
662     RootKeyIndex = NULL;
663     LeafKeyIndex = NULL;
664     TotalLeafCount = 0;
665 
666     /*
667      * Assume for now that the caller should not
668      * do any kind of repairs on the subkeys list,
669      * unless explicitly given the consent by us.
670      */
671     *DoRepair = FALSE;
672 
673     /*
674      * For volatile keys they have data that can
675      * fluctuate and change on the fly so there's
676      * pretty much nothing that we can validate those.
677      * But still, we would want that the volatile key
678      * is not damaged by external factors, like e.g.,
679      * having stable keys on a volatile space.
680      */
681     if (IS_CELL_VOLATILE(CurrentCell))
682     {
683         if (CellData->u.KeyNode.SubKeyCounts[Stable] != 0)
684         {
685             DPRINT1("The volatile key has stable subkeys\n");
686             return CM_CHECK_REGISTRY_STABLE_KEYS_ON_VOLATILE;
687         }
688 
689         return CM_CHECK_REGISTRY_GOOD;
690     }
691 
692     /*
693      * This is not a volatile key, cache the subkeys list
694      * and validate it.
695      */
696     SubKeysListCell = CellData->u.KeyNode.SubKeyLists[Stable];
697     SubKeyCounts = CellData->u.KeyNode.SubKeyCounts[Stable];
698     if (SubKeyCounts > 0)
699     {
700         if (!HvIsCellAllocated(Hive, SubKeysListCell))
701         {
702             DPRINT1("The subkeys list cell is not allocated\n");
703             *DoRepair = TRUE;
704             return CM_CHECK_REGISTRY_SUBKEYS_LIST_UNALLOCATED;
705         }
706 
707         /* Obtain a root index and validate it */
708         RootKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, SubKeysListCell);
709         if (!RootKeyIndex)
710         {
711             DPRINT1("Could not get the root key index of the subkeys list cell\n");
712             return CM_CHECK_REGISTRY_CORRUPT_SUBKEYS_INDEX;
713         }
714 
715         /*
716          * For simple, fast and hashed leaves we would want
717          * that the corresponding root index count matches with
718          * that of the subkey counts itself. If this is not the
719          * case we can isolate this problem and fix the count.
720          */
721         if (RootKeyIndex->Signature == CM_KEY_INDEX_LEAF ||
722             RootKeyIndex->Signature == CM_KEY_FAST_LEAF ||
723             RootKeyIndex->Signature == CM_KEY_HASH_LEAF)
724         {
725             if (SubKeyCounts != RootKeyIndex->Count)
726             {
727                 if (!CmpRepairSubKeyCounts(Hive,
728                                            CurrentCell,
729                                            RootKeyIndex->Count,
730                                            CellData,
731                                            FixHive))
732                 {
733                     DPRINT1("The subkeys list has invalid count (subkeys count %u, root key index count %u)\n",
734                             SubKeyCounts, RootKeyIndex->Count);
735                     return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
736                 }
737             }
738 
739             return CM_CHECK_REGISTRY_GOOD;
740         }
741 
742         /*
743          * The root index is not a leaf, check if the index
744          * is an actual root then.
745          */
746         if (RootKeyIndex->Signature == CM_KEY_INDEX_ROOT)
747         {
748             /*
749              * For the root we have to loop each leaf
750              * from it and increase the total leaf count
751              * in the root after we determined the validity
752              * of a leaf. This way we can see if the subcount
753              * matches with that of the subkeys list count.
754              */
755             for (RootIndex = 0; RootIndex < RootKeyIndex->Count; RootIndex++)
756             {
757                 KeyIndexCell = RootKeyIndex->List[RootIndex];
758                 if (!HvIsCellAllocated(Hive, KeyIndexCell))
759                 {
760                     DPRINT1("The key index cell is not allocated at index %u\n", RootIndex);
761                     *DoRepair = TRUE;
762                     return CM_CHECK_REGISTRY_KEY_INDEX_CELL_UNALLOCATED;
763                 }
764 
765                 /* Obtain a leaf from the root */
766                 LeafKeyIndex = (PCM_KEY_INDEX)HvGetCell(Hive, KeyIndexCell);
767                 if (!LeafKeyIndex)
768                 {
769                     DPRINT1("The root key index's signature is invalid!\n");
770                     return CM_CHECK_REGISTRY_CORRUPT_LEAF_ON_ROOT;
771                 }
772 
773                 /* Check that the leaf has valid signature */
774                 if (LeafKeyIndex->Signature != CM_KEY_INDEX_LEAF &&
775                     LeafKeyIndex->Signature != CM_KEY_FAST_LEAF &&
776                     LeafKeyIndex->Signature != CM_KEY_HASH_LEAF)
777                 {
778                     DPRINT1("The leaf's signature is invalid!\n");
779                     *DoRepair = TRUE;
780                     return CM_CHECK_REGISTRY_CORRUPT_LEAF_SIGNATURE;
781                 }
782 
783                 /* Add up the count of the leaf */
784                 TotalLeafCount += LeafKeyIndex->Count;
785             }
786 
787             /*
788              * We have built up the total leaf count,
789              * we have to determine this count is exactly
790              * the same as the subkeys list count. Otherwise
791              * just fix it.
792              */
793             if (SubKeyCounts != TotalLeafCount)
794             {
795                 if (!CmpRepairSubKeyCounts(Hive,
796                                            CurrentCell,
797                                            TotalLeafCount,
798                                            CellData,
799                                            FixHive))
800                 {
801                     DPRINT1("The subkeys list has invalid count (subkeys count %u, total leaf count %u)\n",
802                             SubKeyCounts, TotalLeafCount);
803                     return CM_CHECK_REGISTRY_BAD_SUBKEY_COUNT;
804                 }
805             }
806 
807             return CM_CHECK_REGISTRY_GOOD;
808         }
809 
810         /*
811          * None of the valid signatures match with that of
812          * the root key index. By definition, the whole subkey
813          * list is total toast.
814          */
815         DPRINT1("The root key index's signature is invalid\n");
816         *DoRepair = TRUE;
817         return CM_CHECK_REGISTRY_CORRUPT_KEY_INDEX_SIGNATURE;
818     }
819 
820     /* If we reach here then this key has no subkeys */
821     return CM_CHECK_REGISTRY_GOOD;
822 }
823 
824 /**
825  * @brief
826  * Purges the volatile storage of a registry
827  * hive. This operation is done mainly during
828  * the bootup of the system.
829  *
830  * @param[in] Hive
831  * A pointer to a hive descriptor of which volatiles
832  * are to be purged.
833  *
834  * @param[in] CurrentCell
835  * The current key cell that the volatile storage of
836  * the hive points to.
837  *
838  * @param[in] CellData
839  * The cell data of the current cell of which the volatile
840  * subkeys storage comes from.
841  *
842  * @param[in] Flags
843  * A bit mask flag that is used to influence how is the
844  * purging operation to be done. See CmCheckRegistry documentation
845  * below for more information.
846  */
847 static
848 VOID
CmpPurgeVolatiles(_In_ PHHIVE Hive,_In_ HCELL_INDEX CurrentCell,_Inout_ PCELL_DATA CellData,_In_ ULONG Flags)849 CmpPurgeVolatiles(
850     _In_ PHHIVE Hive,
851     _In_ HCELL_INDEX CurrentCell,
852     _Inout_ PCELL_DATA CellData,
853     _In_ ULONG Flags)
854 {
855     PAGED_CODE();
856 
857     ASSERT(CellData);
858 
859     /* Did the caller ask to purge volatiles? */
860     if (((Flags & CM_CHECK_REGISTRY_PURGE_VOLATILES) ||
861          (Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES)) &&
862         (CellData->u.KeyNode.SubKeyCounts[Volatile] != 0))
863     {
864         /*
865          * OK, the caller wants them cleaned from this
866          * hive. For XP Beta 1 or newer hives, we unintialize
867          * the whole volatile subkeys list. For older hives,
868          * we just do a cleanup.
869          */
870 #if !defined(_BLDR_)
871         HvMarkCellDirty(Hive, CurrentCell, FALSE);
872 #endif
873         if ((Flags & CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES) &&
874             (Hive->Version >= HSYS_WHISTLER_BETA1))
875         {
876             CellData->u.KeyNode.SubKeyLists[Volatile] = CMP_VOLATILE_LIST_UNINTIALIZED;
877         }
878         else
879         {
880             CellData->u.KeyNode.SubKeyLists[Volatile] = HCELL_NIL;
881         }
882 
883         /* Clear the count */
884         CellData->u.KeyNode.SubKeyCounts[Volatile] = 0;
885     }
886 }
887 
888 /**
889  * @brief
890  * Validates the key cell, ensuring that
891  * the key in the registry is valid and not corrupted.
892  *
893  * @param[in] Hive
894  * A pointer to a hive descriptor of the registry where
895  * the key is to be validated.
896  *
897  * @param[in] SecurityDefaulted
898  * If the caller sets this to TRUE, this indicates the
899  * hive has its security property defaulted due to
900  * heal recovery of the said security. If the caller
901  * sets this to FALSE, the hive comes with its own
902  * security details. This parameter is currently unused.
903  *
904  * @param[in] ParentCell
905  * The parent key cell that comes before the current cell.
906  * This parameter can be HCELL_NIL if the first cell is
907  * the root cell which is the parent of its own.
908  *
909  * @param[in] CurrentCell
910  * The current child key cell that is to be validated.
911  *
912  * @param[in] Flags
913  * A bit mask flag that is used to influence how is the
914  * purging operation of volatile keys in the volatile storage
915  * to be done. See CmCheckRegistry documentation below for more
916  * information.
917  *
918  * @param[in] FixHive
919  * If set to TRUE, the target hive will be fixed.
920  *
921  * @return
922  * Returns CM_CHECK_REGISTRY_GOOD if the key that has been validated
923  * is valid and not corrupted. CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED is
924  * returned if the key cell is not allocated. CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND
925  * is returned if cell data could not be mapped from the key cell.
926  * CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE is returned if the key cell
927  * has an abnormal size that is above the trehshold the validation checks
928  * can permit. CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO is returned if the
929  * name length of the key node is 0, meaning that the key has no name.
930  * CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL is returned if the key is too
931  * big than the cell itself. CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT is
932  * returned if the parent node of the key is incosistent and it couldn't
933  * be fixed. CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE is returned if
934  * the signature of the key node is corrupt and it couldn't be fixed.
935  * A failure CM status code is returned otherwise.
936  */
937 static
938 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)939 CmpValidateKey(
940     _In_ PHHIVE Hive,
941     _In_ BOOLEAN SecurityDefaulted,
942     _In_ HCELL_INDEX ParentCell,
943     _In_ HCELL_INDEX CurrentCell,
944     _In_ ULONG Flags,
945     _In_ BOOLEAN FixHive)
946 {
947     CM_CHECK_REGISTRY_STATUS CmStatusCode;
948     PCELL_DATA CellData;
949     ULONG CellSize;
950     BOOLEAN DoSubkeysRepair;
951     ULONG TotalKeyNameLength, NameLength;
952 
953     PAGED_CODE();
954 
955     /* The current key cell mustn't be NIL here! */
956     ASSERT(CurrentCell != HCELL_NIL);
957 
958     /* TODO: To be removed once we support security caching in Cm */
959     UNREFERENCED_PARAMETER(SecurityDefaulted);
960 
961     /*
962      * We must ensure that the key cell is
963      * allocated in the first place before
964      * we go further.
965      */
966     if (!HvIsCellAllocated(Hive, CurrentCell))
967     {
968         DPRINT1("The key cell is not allocated\n");
969         return CM_CHECK_REGISTRY_KEY_CELL_NOT_ALLOCATED;
970     }
971 
972     /* Obtain cell data from it */
973     CellData = (PCELL_DATA)HvGetCell(Hive, CurrentCell);
974     if (!CellData)
975     {
976         DPRINT1("Could not get cell data from the cell\n");
977         return CM_CHECK_REGISTRY_CELL_DATA_NOT_FOUND;
978     }
979 
980     /* Get the size of this cell and validate its size */
981     CellSize = HvGetCellSize(Hive, CellData);
982     if (CellSize > CMP_KEY_SIZE_THRESHOLD)
983     {
984         DPRINT1("The cell size is above the threshold size (size %u)\n", CellSize);
985         return CM_CHECK_REGISTRY_CELL_SIZE_NOT_SANE;
986     }
987 
988     /*
989      * The cell size is OK but we must ensure
990      * the key is not bigger than the container
991      * of the cell.
992      */
993     NameLength = CellData->u.KeyNode.NameLength;
994     if (NameLength == 0)
995     {
996         DPRINT1("The key node name length is 0!\n");
997         return CM_CHECK_REGISTRY_KEY_NAME_LENGTH_ZERO;
998     }
999 
1000     TotalKeyNameLength = NameLength + FIELD_OFFSET(CM_KEY_NODE, Name);
1001     if (TotalKeyNameLength > CellSize)
1002     {
1003         DPRINT1("The key is too big than the cell (key size %u, cell size %u)\n",
1004                 TotalKeyNameLength, CellSize);
1005         return CM_CHECK_REGISTRY_KEY_TOO_BIG_THAN_CELL;
1006     }
1007 
1008     /* Is the parent cell consistent? */
1009     if (ParentCell != HCELL_NIL &&
1010         ParentCell != CellData->u.KeyNode.Parent)
1011     {
1012         if (!CmpRepairParentNode(Hive,
1013                                  CurrentCell,
1014                                  ParentCell,
1015                                  CellData,
1016                                  FixHive))
1017         {
1018             DPRINT1("The parent key node doesn't point to the actual parent\n");
1019             return CM_CHECK_REGISTRY_BAD_KEY_NODE_PARENT;
1020         }
1021     }
1022 
1023     /* Is the key node signature valid? */
1024     if (CellData->u.KeyNode.Signature != CM_KEY_NODE_SIGNATURE)
1025     {
1026         if (!CmpRepairKeyNodeSignature(Hive,
1027                                        CurrentCell,
1028                                        CellData,
1029                                        FixHive))
1030         {
1031             DPRINT1("The parent key node signature is not valid\n");
1032             return CM_CHECK_REGISTRY_BAD_KEY_NODE_SIGNATURE;
1033         }
1034     }
1035 
1036     /*
1037      * FIXME: Security cell checks have to be implemented here
1038      * once we properly and reliably implement security caching
1039      * in the kernel.
1040      */
1041 
1042     /* Validate the class */
1043     CmStatusCode = CmpValidateClass(Hive, CurrentCell, CellData);
1044     if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1045     {
1046         if (!CmpRepairClassOfNodeKey(Hive,
1047                                      CurrentCell,
1048                                      CellData,
1049                                      FixHive))
1050         {
1051             DPRINT1("Failed to repair the hive, the cell class is not valid\n");
1052             return CmStatusCode;
1053         }
1054     }
1055 
1056     /* Validate the value list */
1057     CmStatusCode = CmpValidateValueList(Hive, CurrentCell, CellData, FixHive);
1058     if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1059     {
1060         /*
1061          * It happens that a certain value in the list
1062          * is so bad like we couldn't map a cell data from it
1063          * or the list itself is toast. In such cases what we
1064          * can do here is to do a "value list sacrifice", aka
1065          * purge the whole list.
1066          */
1067         if (!CmpRepairValueList(Hive, CurrentCell, FixHive))
1068         {
1069             DPRINT1("Failed to repair the hive, the value list is corrupt\n");
1070             return CmStatusCode;
1071         }
1072     }
1073 
1074     /* Validate the subkeys list */
1075     CmStatusCode = CmpValidateSubKeyList(Hive, CurrentCell, CellData, FixHive, &DoSubkeysRepair);
1076     if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1077     {
1078         /*
1079          * The subkeys list is in trouble. Worse when the actual
1080          * subkey list is so severed this key is also kaput on itself.
1081          */
1082         if (!DoSubkeysRepair)
1083         {
1084             DPRINT1("The subkeys list is totally corrupt, can't repair\n");
1085             return CmStatusCode;
1086         }
1087 
1088         /*
1089          * OK, there's still some salvation for this key.
1090          * Purge the whole subkeys list in order to fix it.
1091          */
1092         if (!CmpRepairSubKeyList(Hive,
1093                                  CurrentCell,
1094                                  CellData,
1095                                  FixHive))
1096         {
1097             DPRINT1("Failed to repair the hive, the subkeys list is corrupt!\n");
1098             return CmStatusCode;
1099         }
1100     }
1101 
1102     /* Purge volatile data if needed */
1103     CmpPurgeVolatiles(Hive, CurrentCell, CellData, Flags);
1104     return CM_CHECK_REGISTRY_GOOD;
1105 }
1106 
1107 /**
1108  * @brief
1109  * Performs deep checking of the registry by walking
1110  * down the registry tree using a stack based pool.
1111  * This function is the guts of CmCheckRegistry.
1112  *
1113  * @param[in] Hive
1114  * A pointer to a hive descriptor of the registry where
1115  * the validation is to be performed.
1116  *
1117  * @param[in] Flags
1118  * Bit mask flag used for volatiles purging. Such
1119  * flags influence on how volatile purging is actually
1120  * done. See CmCheckRegistry documentation for more
1121  * information.
1122  *
1123  * @param[in] SecurityDefaulted
1124  * If the caller sets this to FALSE, the registry hive
1125  * uses its own unique security details. Otherwise
1126  * registry hive has the security details defaulted.
1127  *
1128  * @param[in] FixHive
1129  * If set to TRUE, the target hive will be fixed.
1130  *
1131  * @return
1132  * Returns CM_CHECK_REGISTRY_GOOD is returned if the function
1133  * has successfully performed deep registry checking and
1134  * the registry contents are valid. CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL
1135  * is returned if the function has failed to allocate the
1136  * stack work state buffer in memory which is necessary for
1137  * deep checking of the registry. CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND
1138  * is returned if no root cell has been found of this hive.
1139  * CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER is returned if the lexical
1140  * order is not valid. CM_CHECK_REGISTRY_NODE_NOT_FOUND is returned if
1141  * the no key node could be mapped from the key. CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND
1142  * is returned if no subkey child cell could be found. CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS
1143  * is returned if we have reached the maximum stack limit which means the registry that
1144  * we have checked is too fat.
1145  */
1146 static
1147 CM_CHECK_REGISTRY_STATUS
CmpValidateRegistryInternal(_In_ PHHIVE Hive,_In_ ULONG Flags,_In_ BOOLEAN SecurityDefaulted,_In_ BOOLEAN FixHive)1148 CmpValidateRegistryInternal(
1149     _In_ PHHIVE Hive,
1150     _In_ ULONG Flags,
1151     _In_ BOOLEAN SecurityDefaulted,
1152     _In_ BOOLEAN FixHive)
1153 {
1154     CM_CHECK_REGISTRY_STATUS CmStatusCode;
1155     PCMP_REGISTRY_STACK_WORK_STATE WorkState;
1156     HCELL_INDEX RootCell, ParentCell, CurrentCell;
1157     HCELL_INDEX ChildSubKeyCell;
1158     PCM_KEY_NODE KeyNode;
1159     ULONG WorkStateLength;
1160     LONG StackDepth;
1161     BOOLEAN AllChildrenChecked;
1162 
1163     PAGED_CODE();
1164 
1165     ASSERT(Hive);
1166 
1167     /*
1168      * Allocate some memory blocks for the stack
1169      * state structure. We'll be using it to walk
1170      * down the registry hive tree in a recursive
1171      * way without worrying that we explode the
1172      * kernel stack in the most gruesome and gross
1173      * ways.
1174      */
1175     WorkStateLength = CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH * sizeof(CMP_REGISTRY_STACK_WORK_STATE);
1176     WorkState = CmpAllocate(WorkStateLength,
1177                             TRUE,
1178                             TAG_REGISTRY_STACK);
1179     if (!WorkState)
1180     {
1181         DPRINT1("Couldn't allocate memory for registry stack work state\n");
1182         return CM_CHECK_REGISTRY_ALLOCATE_MEM_STACK_FAIL;
1183     }
1184 
1185     /* Obtain the root cell of the hive */
1186     RootCell = GET_HHIVE_ROOT_CELL(Hive);
1187     if (RootCell == HCELL_NIL)
1188     {
1189         DPRINT1("Couldn't get the root cell of the hive\n");
1190         CmpFree(WorkState, WorkStateLength);
1191         return CM_CHECK_REGISTRY_ROOT_CELL_NOT_FOUND;
1192     }
1193 
1194 RestartValidation:
1195     /*
1196      * Prepare the stack state and start from
1197      * the root cell. Ensure that the root cell
1198      * itself is OK before we go forward.
1199      */
1200     StackDepth = 0;
1201     WorkState[StackDepth].ChildCellIndex = 0;
1202     WorkState[StackDepth].Current = RootCell;
1203     WorkState[StackDepth].Parent = HCELL_NIL;
1204     WorkState[StackDepth].Sibling = HCELL_NIL;
1205 
1206     /*
1207      * As we start checking the root cell which
1208      * is the top element of a registry hive,
1209      * we'll be going to look for child keys
1210      * in the course of walking down the tree.
1211      */
1212     AllChildrenChecked = FALSE;
1213 
1214     while (StackDepth >= 0)
1215     {
1216         /* Cache the current and parent cells */
1217         CurrentCell = WorkState[StackDepth].Current;
1218         ParentCell = WorkState[StackDepth].Parent;
1219 
1220         /* Do we have still have children to validate? */
1221         if (!AllChildrenChecked)
1222         {
1223             /* Check that the key is OK */
1224             CmStatusCode = CmpValidateKey(Hive,
1225                                           SecurityDefaulted,
1226                                           ParentCell,
1227                                           CurrentCell,
1228                                           Flags,
1229                                           FixHive);
1230             if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1231             {
1232                 /*
1233                  * The key cell is damaged. We have to pray and
1234                  * hope that this is not the root cell as any
1235                  * damage done to the root is catastrophically
1236                  * fatal.
1237                  */
1238                 if (CurrentCell == RootCell)
1239                 {
1240                     DPRINT1("THE ROOT CELL IS BROKEN\n");
1241                     CmpFree(WorkState, WorkStateLength);
1242                     return CmStatusCode;
1243                 }
1244 
1245                 /*
1246                  * It is not the root, remove the faulting
1247                  * damaged cell from the parent so that we
1248                  * can heal the hive.
1249                  */
1250                 if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
1251                 {
1252                     DPRINT1("The key is corrupt (current cell 0x%x, parent cell 0x%x)\n",
1253                             CurrentCell, ParentCell);
1254                     CmpFree(WorkState, WorkStateLength);
1255                     return CmStatusCode;
1256                 }
1257 
1258                 /* Damaged cell removed, restart the loop */
1259                 DPRINT1("Hive repaired, restarting the validation loop...\n");
1260                 goto RestartValidation;
1261             }
1262 
1263             /*
1264              * The key is in perfect shape. If we have advanced
1265              * the stack depth then check the lexicographical
1266              * order of the keys as well.
1267              */
1268             if (StackDepth > 0 &&
1269                 CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1270             {
1271                 if (WorkState[StackDepth - CMP_PRIOR_STACK].Sibling != HCELL_NIL)
1272                 {
1273                     if (!CmpValidateLexicalOrder(Hive,
1274                                                  CurrentCell,
1275                                                  WorkState[StackDepth - CMP_PRIOR_STACK].Sibling))
1276                     {
1277                         /*
1278                          * The lexicographical order is bad,
1279                          * attempt to heal the hive.
1280                          */
1281                         if (!CmpRepairParentKey(Hive, CurrentCell, ParentCell, FixHive))
1282                         {
1283                             DPRINT1("The lexicographical order is invalid (sibling 0x%x, current cell 0x%x)\n",
1284                                     CurrentCell, WorkState[StackDepth - CMP_PRIOR_STACK].Sibling);
1285                             CmpFree(WorkState, WorkStateLength);
1286                             return CM_CHECK_REGISTRY_BAD_LEXICOGRAPHICAL_ORDER;
1287                         }
1288 
1289                         /* Damaged cell removed, restart the loop */
1290                         DPRINT1("Hive repaired, restarting the validation loop...\n");
1291                         goto RestartValidation;
1292                     }
1293                 }
1294 
1295                 /* Assign the prior sibling for upcoming iteration */
1296                 WorkState[StackDepth - CMP_PRIOR_STACK].Sibling = CurrentCell;
1297             }
1298         }
1299 
1300         /* Obtain a node for this key */
1301         KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, CurrentCell);
1302         if (!KeyNode)
1303         {
1304             DPRINT1("Couldn't get the node of key (current cell 0x%x)\n", CurrentCell);
1305             CmpFree(WorkState, WorkStateLength);
1306             return CM_CHECK_REGISTRY_NODE_NOT_FOUND;
1307         }
1308 
1309         /*
1310          * If we have processed all the children from this
1311          * node then adjust the stack depth work state by
1312          * going back and restart the loop to lookup for
1313          * the rest of the tree. Acknowledge the code path
1314          * above that we checked all the children so that
1315          * we don't have to validate the same subkey again.
1316          */
1317         if (WorkState[StackDepth].ChildCellIndex < KeyNode->SubKeyCounts[Stable])
1318         {
1319             /*
1320              * We have children to process, obtain the
1321              * child subkey in question so that we can
1322              * cache it later for the next key validation.
1323              */
1324             ChildSubKeyCell = CmpFindSubKeyByNumber(Hive, KeyNode, WorkState[StackDepth].ChildCellIndex);
1325             if (ChildSubKeyCell == HCELL_NIL)
1326             {
1327                 DPRINT1("Couldn't get the child subkey cell (at stack index %d)\n", StackDepth);
1328                 CmpFree(WorkState, WorkStateLength);
1329                 return CM_CHECK_REGISTRY_SUBKEY_NOT_FOUND;
1330             }
1331 
1332             /*
1333              * As we got the subkey advance the child index as
1334              * well as the stack depth work state for the next
1335              * key validation. However we must ensure since
1336              * we're advancing the stack depth that we don't
1337              * go over the maximum tree level depth. A registry
1338              * tree can be at maximum 512 levels deep.
1339              *
1340              * For more information see https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits.
1341              */
1342             WorkState[StackDepth].ChildCellIndex++;
1343             StackDepth++;
1344             if (StackDepth >= CMP_REGISTRY_MAX_LEVELS_TREE_DEPTH - 1)
1345             {
1346                 /*
1347                  * This registry has so many levels it's
1348                  * so fat. We don't want to explode our
1349                  * kernel stack, so just simply bail out...
1350                  */
1351                 DPRINT1("The registry tree has so many levels!\n");
1352                 CmpFree(WorkState, WorkStateLength);
1353                 return CM_CHECK_REGISTRY_TREE_TOO_MANY_LEVELS;
1354             }
1355 
1356             /* Prepare the work state for the next key */
1357             WorkState[StackDepth].ChildCellIndex = 0;
1358             WorkState[StackDepth].Current = ChildSubKeyCell;
1359             WorkState[StackDepth].Parent = WorkState[StackDepth - CMP_PRIOR_STACK].Current;
1360             WorkState[StackDepth].Sibling = HCELL_NIL;
1361 
1362             /*
1363              * As we prepared the work state, acknowledge the
1364              * code path at the top of the loop that we need
1365              * to process and validate the next child subkey.
1366              */
1367             AllChildrenChecked = FALSE;
1368             continue;
1369         }
1370 
1371         /*
1372          * We have validated all the child subkeys
1373          * of the node. Decrease the stack depth
1374          * and tell the above code we looked for all
1375          * children so that we don't need to validate
1376          * the same children again but go for the next
1377          * node.
1378          */
1379         AllChildrenChecked = TRUE;
1380         StackDepth--;
1381         continue;
1382     }
1383 
1384     CmpFree(WorkState, WorkStateLength);
1385     return CM_CHECK_REGISTRY_GOOD;
1386 }
1387 
1388 /* PUBLIC FUNCTIONS ***********************************************************/
1389 
1390 /**
1391  * @brief
1392  * Validates a bin from a hive. It performs checks
1393  * against the cells from this bin, ensuring the
1394  * bin is not corrupt and that the cells are consistent
1395  * with each other.
1396  *
1397  * @param[in] Hive
1398  * A pointer to a hive descriptor of which a hive bin
1399  * is to be validated.
1400  *
1401  * @param[in] Bin
1402  * A pointer to a bin where its cells are to be
1403  * validated.
1404  *
1405  * @return
1406  * CM_CHECK_REGISTRY_GOOD is returned if the bin is
1407  * valid and not corrupt. CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT
1408  * is returned if this bin has a corrupt signature. CM_CHECK_REGISTRY_BAD_FREE_CELL
1409  * is returned if the free cell has a bogus size. CM_CHECK_REGISTRY_BAD_ALLOC_CELL
1410  * is returned for the allocated cell has a bogus size.
1411  */
1412 CM_CHECK_REGISTRY_STATUS
1413 NTAPI
HvValidateBin(_In_ PHHIVE Hive,_In_ PHBIN Bin)1414 HvValidateBin(
1415     _In_ PHHIVE Hive,
1416     _In_ PHBIN Bin)
1417 {
1418     PHCELL Cell, Basket;
1419 
1420     PAGED_CODE();
1421 
1422     ASSERT(Bin);
1423     ASSERT(Hive);
1424 
1425     /* Ensure that this bin we got has valid signature header */
1426     if (Bin->Signature != HV_HBIN_SIGNATURE)
1427     {
1428         DPRINT1("The bin's signature header is corrupt\n");
1429         return CM_CHECK_REGISTRY_BIN_SIGNATURE_HEADER_CORRUPT;
1430     }
1431 
1432     /*
1433      * Walk over all the cells from this bin and
1434      * validate that they're consistent with the bin.
1435      * Namely we want that each cell from this bin doesn't
1436      * have a bogus size.
1437      */
1438     Basket = (PHCELL)((PUCHAR)Bin + Bin->Size);
1439     for (Cell = GET_CELL_BIN(Bin);
1440          Cell < Basket;
1441          Cell = (PHCELL)((PUCHAR)Cell + abs(Cell->Size)))
1442     {
1443         if (IsFreeCell(Cell))
1444         {
1445             /*
1446              * This cell is free, check that
1447              * the size of this cell is not bogus.
1448              */
1449             if (Cell->Size > Bin->Size ||
1450                 Cell->Size == 0)
1451             {
1452                 /*
1453                  * This cell has too much free space that
1454                  * exceeds the boundary of the bin size.
1455                  * Otherwise the cell doesn't have actual
1456                  * free space (aka Size == 0) which is a
1457                  * no go for a bin.
1458                  */
1459                 DPRINT1("The free cell exceeds the bin size or cell size equal to 0 (cell 0x%p, cell size %d, bin size %u)\n",
1460                         Cell, Cell->Size, Bin->Size);
1461                 return CM_CHECK_REGISTRY_BAD_FREE_CELL;
1462             }
1463         }
1464         else
1465         {
1466             /*
1467              * This cell is allocated, make sure that
1468              * the size of this cell is not bogus.
1469              */
1470             if (abs(Cell->Size) > Bin->Size)
1471             {
1472                 /*
1473                  * This cell allocated too much space
1474                  * that exceeds the boundary of the
1475                  * bin size.
1476                  */
1477                 DPRINT1("The allocated cell exceeds the bin size (cell 0x%p, cell size %d, bin size %u)\n",
1478                         Cell, abs(Cell->Size), Bin->Size);
1479                 return CM_CHECK_REGISTRY_BAD_ALLOC_CELL;
1480             }
1481         }
1482     }
1483 
1484     return CM_CHECK_REGISTRY_GOOD;
1485 }
1486 
1487 /**
1488  * @brief
1489  * Validates a registry hive. This function ensures
1490  * that the storage of this hive has valid bins.
1491  *
1492  * @param[in] Hive
1493  * A pointer to a hive descriptor where validation on
1494  * its hive bins is to be performed.
1495  *
1496  * @return
1497  * CM_CHECK_REGISTRY_GOOD is returned if the hive
1498  * is valid. CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE is
1499  * returned if the hive has a corrupted signature.
1500  * CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT is returned
1501  * if the captured bin has a bad size. A failure CM status
1502  * code is returned otherwise.
1503  */
1504 CM_CHECK_REGISTRY_STATUS
1505 NTAPI
HvValidateHive(_In_ PHHIVE Hive)1506 HvValidateHive(
1507     _In_ PHHIVE Hive)
1508 {
1509     CM_CHECK_REGISTRY_STATUS CmStatusCode;
1510     ULONG StorageIndex;
1511     ULONG BlockIndex;
1512     ULONG StorageLength;
1513     PHBIN Bin;
1514 
1515     PAGED_CODE();
1516 
1517     ASSERT(Hive);
1518 
1519     /* Is the hive signature valid? */
1520     if (Hive->Signature != HV_HHIVE_SIGNATURE)
1521     {
1522         DPRINT1("Hive's signature corrupted (signature %u)\n", Hive->Signature);
1523         return CM_CHECK_REGISTRY_HIVE_CORRUPT_SIGNATURE;
1524     }
1525 
1526     /*
1527      * Now loop each bin in the storage of this
1528      * hive.
1529      */
1530     for (StorageIndex = 0; StorageIndex < Hive->StorageTypeCount; StorageIndex++)
1531     {
1532         /* Get the storage length at this index */
1533         StorageLength = Hive->Storage[StorageIndex].Length;
1534 
1535         for (BlockIndex = 0; BlockIndex < StorageLength;)
1536         {
1537             /* Go to the next if this bin does not exist */
1538             if (Hive->Storage[StorageIndex].BlockList[BlockIndex].BinAddress == (ULONG_PTR)NULL)
1539             {
1540                 continue;
1541             }
1542 
1543             /*
1544              * Capture this bin and ensure that such
1545              * bin is within the offset and the size
1546              * is not bogus.
1547              */
1548             Bin = GET_HHIVE_BIN(Hive, StorageIndex, BlockIndex);
1549             if (Bin->Size > (StorageLength * HBLOCK_SIZE) ||
1550                 (Bin->FileOffset / HBLOCK_SIZE) != BlockIndex)
1551             {
1552                 DPRINT1("Bin size or offset is corrupt (bin size %u, file offset %u, storage length %u)\n",
1553                         Bin->Size, Bin->FileOffset, StorageLength);
1554                 return CM_CHECK_REGISTRY_BIN_SIZE_OR_OFFSET_CORRUPT;
1555             }
1556 
1557             /* Validate the rest of the bin */
1558             CmStatusCode = HvValidateBin(Hive, Bin);
1559             if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1560             {
1561                 DPRINT1("This bin is not valid (bin 0x%p)\n", Bin);
1562                 return CmStatusCode;
1563             }
1564 
1565             /* Go to the next block */
1566             BlockIndex += Bin->Size / HBLOCK_SIZE;
1567         }
1568     }
1569 
1570     return CM_CHECK_REGISTRY_GOOD;
1571 }
1572 
1573 /**
1574  * @brief
1575  * Checks the registry that is consistent and its
1576  * contents valid and not corrupted. More specifically
1577  * this function performs a deep check of the registry
1578  * for the following properties:
1579  *
1580  * - That the security cache cell of the registry is OK
1581  * - That bins and cells are consistent with each other
1582  * - That the child subkey cell points to the parent
1583  * - That the key itself has sane sizes
1584  * - That the class, values and subkeys lists are valid
1585  * - Much more
1586  *
1587  * @param[in] Hive
1588  * A pointer to a CM hive of the registry to be checked
1589  * in question.
1590  *
1591  * @param[in] Flags
1592  * A bit mask flag used to influence the process of volatile
1593  * keys purging. See Remarks for further information.
1594  *
1595  * @return
1596  * This function returns a CM (Configuration Manager) check
1597  * registry status code. A code of CM_CHECK_REGISTRY_GOOD of
1598  * value 0 indicates the registry hive is valid and not corrupted.
1599  * A non zero unsigned integer value indicates a failure. Consult
1600  * other private routines in this file for other failure status
1601  * codes.
1602  *
1603  * @remarks
1604  * During a load operation CmCheckRegistry can purge the volatile
1605  * data of registry (or not) depending on the submitted flag bit mask
1606  * by the caller. The following supported flags are:
1607  *
1608  * CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES -- Tells the function that
1609  * volatile data purging must not be done.
1610  *
1611  * CM_CHECK_REGISTRY_PURGE_VOLATILES - Tells the function to purge out
1612  * volatile information data from a registry hive, on demand. Purging
1613  * doesn't come into action if no volatile data has been found.
1614  *
1615  * CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES - A special flag used
1616  * by FreeLdr and Environ. When this flag is set the function will not
1617  * clean up the volatile storage but it will unintialize the storage
1618  * instead (this is the case if the given registry hive for validation
1619  * is a XP Beta 1 hive or newer). Otherwise it will perform a normal
1620  * cleanup of the volatile storage.
1621  *
1622  * CM_CHECK_REGISTRY_VALIDATE_HIVE - Tells the function to perform a
1623  * thorough analysation of the underlying hive's bins and cells before
1624  * doing validation of the registry tree. HvValidateHive function is called
1625  * in this case.
1626  *
1627  * CM_CHECK_REGISTRY_FIX_HIVE - Tells the function to fix the target registry
1628  * hive if it is damaged. Usually this flag comes from a registry repair tool
1629  * where the user asked to for its damaged hive to be fixed. In this case
1630  * a self-heal procedure against the hive is performed.
1631  */
1632 CM_CHECK_REGISTRY_STATUS
1633 NTAPI
CmCheckRegistry(_In_ PCMHIVE RegistryHive,_In_ ULONG Flags)1634 CmCheckRegistry(
1635     _In_ PCMHIVE RegistryHive,
1636     _In_ ULONG Flags)
1637 {
1638     CM_CHECK_REGISTRY_STATUS CmStatusCode;
1639     PHHIVE Hive;
1640     BOOLEAN ShouldFixHive = FALSE;
1641 
1642     PAGED_CODE();
1643 
1644     /* Bail out if the caller did not give a hive */
1645     if (!RegistryHive)
1646     {
1647         DPRINT1("No registry hive given for check\n");
1648         return CM_CHECK_REGISTRY_INVALID_PARAMETER;
1649     }
1650 
1651 #if !defined(CMLIB_HOST) && !defined(_BLDR_)
1652     /*
1653      * The master hive is the root of the registry,
1654      * it holds all other hives together. So do not
1655      * do any validation checks.
1656      */
1657     if (RegistryHive == CmiVolatileHive)
1658     {
1659         DPRINT("This is master registry hive, don't do anything\n");
1660         return CM_CHECK_REGISTRY_GOOD;
1661     }
1662 #endif
1663 
1664     /* Bail out if no valid flag is given */
1665     if (Flags & ~(CM_CHECK_REGISTRY_DONT_PURGE_VOLATILES       |
1666                   CM_CHECK_REGISTRY_PURGE_VOLATILES            |
1667                   CM_CHECK_REGISTRY_BOOTLOADER_PURGE_VOLATILES |
1668                   CM_CHECK_REGISTRY_VALIDATE_HIVE              |
1669                   CM_CHECK_REGISTRY_FIX_HIVE))
1670     {
1671         DPRINT1("Invalid flag for registry check given (flag %u)\n", Flags);
1672         return CM_CHECK_REGISTRY_INVALID_PARAMETER;
1673     }
1674 
1675     /*
1676      * Obtain the hive and check if the caller wants
1677      * that the hive to be validated.
1678      */
1679     Hive = GET_HHIVE(RegistryHive);
1680     if (Flags & CM_CHECK_REGISTRY_VALIDATE_HIVE)
1681     {
1682         CmStatusCode = HvValidateHive(Hive);
1683         if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1684         {
1685             DPRINT1("The hive is not valid (hive 0x%p, check status code %u)\n",
1686                     Hive, CmStatusCode);
1687             return CmStatusCode;
1688         }
1689     }
1690 
1691     /*
1692      * A registry repair tool such as the ReactOS Check Registry
1693      * Utility wants the damaged hive to be fixed as we check the
1694      * target hive.
1695      */
1696     if (Flags & CM_CHECK_REGISTRY_FIX_HIVE)
1697     {
1698         ShouldFixHive = TRUE;
1699     }
1700 
1701     /*
1702      * FIXME: Currently ReactOS does not implement security
1703      * caching algorithms so it's pretty pointless to implement
1704      * security descriptors validation checks at this moment.
1705      * When the time comes to implement these, we would need
1706      * to implement security checks here as well.
1707      */
1708 
1709     /* Call the internal API to do the rest of the work bulk */
1710     CmStatusCode = CmpValidateRegistryInternal(Hive, Flags, FALSE, ShouldFixHive);
1711     if (!CM_CHECK_REGISTRY_SUCCESS(CmStatusCode))
1712     {
1713         DPRINT1("The hive is not valid (hive 0x%p, check status code %u)\n",
1714                 Hive, CmStatusCode);
1715         return CmStatusCode;
1716     }
1717 
1718     return CmStatusCode;
1719 }
1720 
1721 /* EOF */
1722