xref: /reactos/ntoskrnl/config/cmboot.c (revision 09dde2cf)
1 /*
2  * PROJECT:     ReactOS Kernel
3  * LICENSE:     BSD - See COPYING.ARM in the top level directory
4  * PURPOSE:     Configuration Manager - Boot Initialization
5  * COPYRIGHT:   Copyright 2007 Alex Ionescu (alex.ionescu@reactos.org)
6  *              Copyright 2010 ReactOS Portable Systems Group
7  *              Copyright 2022 Hermès Bélusca-Maïto
8  *
9  * NOTE: This module is shared by both the kernel and the bootloader.
10  */
11 
12 /* INCLUDES *******************************************************************/
13 
14 #include <ntoskrnl.h>
15 
16 #define NDEBUG
17 #include <debug.h>
18 
19 #ifdef _BLDR_
20 
21 #undef CODE_SEG
22 #define CODE_SEG(...)
23 
24 #include <ntstrsafe.h>
25 #include <cmlib.h>
26 #include "internal/cmboot.h"
27 
28 // HACK: This is part of non-NT-compatible SafeBoot support in kernel.
29 ULONG InitSafeBootMode = 0;
30 
31 DBG_DEFAULT_CHANNEL(REGISTRY);
32 #define CMTRACE(x, fmt, ...) TRACE(fmt, ##__VA_ARGS__) // DPRINT
33 
34 #endif /* _BLDR_ */
35 
36 
37 /* DEFINES ********************************************************************/
38 
39 #define CM_BOOT_DEBUG   0x20
40 
41 #define IS_NULL_TERMINATED(Buffer, Size) \
42     (((Size) >= sizeof(WCHAR)) && ((Buffer)[(Size) / sizeof(WCHAR) - 1] == UNICODE_NULL))
43 
44 
45 /* FUNCTIONS ******************************************************************/
46 
47 // HACK: This is part of non-NT-compatible SafeBoot support in kernel.
48 extern ULONG InitSafeBootMode;
49 
50 CODE_SEG("INIT")
51 static
52 BOOLEAN
53 CmpIsSafe(
54     _In_ PHHIVE Hive,
55     _In_ HCELL_INDEX SafeBootCell,
56     _In_ HCELL_INDEX DriverCell);
57 
58 /**
59  * @brief
60  * Finds the corresponding "HKLM\SYSTEM\ControlSetXXX" system control set
61  * registry key, according to the "Current", "Default", or "LastKnownGood"
62  * values in the "HKLM\SYSTEM\Select" registry key.
63  *
64  * @param[in]   SystemHive
65  * The SYSTEM hive.
66  *
67  * @param[in]   RootCell
68  * The root cell of the SYSTEM hive.
69  *
70  * @param[in]   SelectKeyName
71  * The control set to check for: either "Current", "Default", or
72  * "LastKnownGood", the value of which selects the corresponding
73  * "HKLM\SYSTEM\ControlSetXXX" control set registry key.
74  *
75  * @param[out]  AutoSelect
76  * Value of the "AutoSelect" registry value (unused).
77  *
78  * @return
79  * The control set registry key's hive cell (if found), or HCELL_NIL.
80  **/
81 CODE_SEG("INIT")
82 HCELL_INDEX
83 NTAPI
84 CmpFindControlSet(
85     _In_ PHHIVE SystemHive,
86     _In_ HCELL_INDEX RootCell,
87     _In_ PCUNICODE_STRING SelectKeyName,
88     _Out_ PBOOLEAN AutoSelect)
89 {
90     UNICODE_STRING Name;
91     PCM_KEY_NODE Node;
92     HCELL_INDEX SelectCell, AutoSelectCell, SelectValueCell, ControlSetCell;
93     HCELL_INDEX CurrentValueCell;
94     PCM_KEY_VALUE Value;
95     ULONG Length;
96     NTSTATUS Status;
97     PULONG CurrentData;
98     PULONG ControlSetId;
99     WCHAR Buffer[128];
100 
101     /* Sanity check: We shouldn't need to release any acquired cells */
102     ASSERT(SystemHive->ReleaseCellRoutine == NULL);
103 
104     /* Get the Select key */
105     RtlInitUnicodeString(&Name, L"select");
106     Node = (PCM_KEY_NODE)HvGetCell(SystemHive, RootCell);
107     if (!Node) return HCELL_NIL;
108     SelectCell = CmpFindSubKeyByName(SystemHive, Node, &Name);
109     if (SelectCell == HCELL_NIL) return HCELL_NIL;
110 
111     /* Get AutoSelect value */
112     RtlInitUnicodeString(&Name, L"AutoSelect");
113     Node = (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
114     if (!Node) return HCELL_NIL;
115     AutoSelectCell = CmpFindValueByName(SystemHive, Node, &Name);
116     if (AutoSelectCell == HCELL_NIL)
117     {
118         /* Assume TRUE if the value is missing */
119         *AutoSelect = TRUE;
120     }
121     else
122     {
123         /* Read the value */
124         Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, AutoSelectCell);
125         if (!Value) return HCELL_NIL;
126         // if (Value->Type != REG_DWORD) return HCELL_NIL;
127 
128         /* Convert it to a boolean */
129         CurrentData = (PULONG)CmpValueToData(SystemHive, Value, &Length);
130         if (!CurrentData) return HCELL_NIL;
131         // if (Length < sizeof(ULONG)) return HCELL_NIL;
132 
133         *AutoSelect = *(PBOOLEAN)CurrentData;
134     }
135 
136     /* Now find the control set being looked up */
137     Node = (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
138     if (!Node) return HCELL_NIL;
139     SelectValueCell = CmpFindValueByName(SystemHive, Node, SelectKeyName);
140     if (SelectValueCell == HCELL_NIL) return HCELL_NIL;
141 
142     /* Read the value (corresponding to the CCS ID) */
143     Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, SelectValueCell);
144     if (!Value) return HCELL_NIL;
145     if (Value->Type != REG_DWORD) return HCELL_NIL;
146     ControlSetId = (PULONG)CmpValueToData(SystemHive, Value, &Length);
147     if (!ControlSetId) return HCELL_NIL;
148     if (Length < sizeof(ULONG)) return HCELL_NIL;
149 
150     /* Now build the CCS's Name */
151     Status = RtlStringCbPrintfW(Buffer, sizeof(Buffer),
152                                 L"ControlSet%03lu", *ControlSetId);
153     if (!NT_SUCCESS(Status)) return HCELL_NIL;
154     /* RtlStringCbPrintfW ensures the buffer to be NULL-terminated */
155     RtlInitUnicodeString(&Name, Buffer);
156 
157     /* Now open it */
158     Node = (PCM_KEY_NODE)HvGetCell(SystemHive, RootCell);
159     if (!Node) return HCELL_NIL;
160     ControlSetCell = CmpFindSubKeyByName(SystemHive, Node, &Name);
161     if (ControlSetCell == HCELL_NIL) return HCELL_NIL;
162 
163     /* Get the value of the "Current" CCS */
164     RtlInitUnicodeString(&Name, L"Current");
165     Node =  (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
166     if (!Node) return HCELL_NIL;
167     CurrentValueCell = CmpFindValueByName(SystemHive, Node, &Name);
168 
169     /* Make sure it exists */
170     if (CurrentValueCell != HCELL_NIL)
171     {
172         /* Get the current value and make sure it's a ULONG */
173         Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, CurrentValueCell);
174         if (!Value) return HCELL_NIL;
175         if (Value->Type == REG_DWORD)
176         {
177             /* Get the data and update it */
178             CurrentData = (PULONG)CmpValueToData(SystemHive, Value, &Length);
179             if (!CurrentData) return HCELL_NIL;
180             if (Length < sizeof(ULONG)) return HCELL_NIL;
181 
182             *CurrentData = *ControlSetId;
183         }
184     }
185 
186     /* Return the CCS cell */
187     return ControlSetCell;
188 }
189 
190 /**
191  * @brief
192  * Finds the index of the driver's "Tag" value
193  * in its corresponding group ordering list.
194  *
195  * @param[in]   Hive
196  * The SYSTEM hive.
197  *
198  * @param[in]   TagCell
199  * The driver's "Tag" registry value's hive cell.
200  *
201  * @param[in]   GroupOrderCell
202  * The hive cell of the "Control\GroupOrderList" registry key
203  * inside the currently selected control set.
204  *
205  * @param[in]   GroupName
206  * The driver's group name.
207  *
208  * @return
209  * The corresponding tag index, or -1 (last position),
210  * or -2 (next-to-last position).
211  **/
212 CODE_SEG("INIT")
213 static
214 ULONG
215 CmpFindTagIndex(
216     _In_ PHHIVE Hive,
217     _In_ HCELL_INDEX TagCell,
218     _In_ HCELL_INDEX GroupOrderCell,
219     _In_ PCUNICODE_STRING GroupName)
220 {
221     PCM_KEY_VALUE TagValue, Value;
222     PCM_KEY_NODE Node;
223     HCELL_INDEX OrderCell;
224     PULONG DriverTag, TagOrder;
225     ULONG CurrentTag, Length;
226     BOOLEAN BufferAllocated;
227 
228     /* Sanity check: We shouldn't need to release any acquired cells */
229     ASSERT(Hive->ReleaseCellRoutine == NULL);
230 
231     /* Get the tag */
232     Value = (PCM_KEY_VALUE)HvGetCell(Hive, TagCell);
233     if (!Value) return -2;
234     if (Value->Type != REG_DWORD) return -2;
235     DriverTag = (PULONG)CmpValueToData(Hive, Value, &Length);
236     if (!DriverTag) return -2;
237     if (Length < sizeof(ULONG)) return -2;
238 
239     /* Get the order array */
240     Node = (PCM_KEY_NODE)HvGetCell(Hive, GroupOrderCell);
241     if (!Node) return -2;
242     OrderCell = CmpFindValueByName(Hive, Node, GroupName);
243     if (OrderCell == HCELL_NIL) return -2;
244 
245     /* And read it */
246     TagValue = (PCM_KEY_VALUE)HvGetCell(Hive, OrderCell);
247     if (!TagValue) return -2;
248     if (!CmpGetValueData(Hive,
249                          TagValue,
250                          &Length,
251                          (PVOID*)&TagOrder,
252                          &BufferAllocated,
253                          &OrderCell)
254         || !TagOrder)
255     {
256         return -2;
257     }
258 
259     /* Parse each tag */
260     for (CurrentTag = 1; CurrentTag <= TagOrder[0]; CurrentTag++)
261     {
262         /* Find a match */
263         if (TagOrder[CurrentTag] == *DriverTag)
264         {
265             /* Found it -- return the tag */
266             if (BufferAllocated) Hive->Free(TagOrder, Length);
267             return CurrentTag;
268         }
269     }
270 
271     /* No matches, so assume next to last ordering */
272     if (BufferAllocated) Hive->Free(TagOrder, Length);
273     return -2;
274 }
275 
276 #ifdef _BLDR_
277 
278 /**
279  * @brief
280  * Checks whether the specified named driver is already in the driver list.
281  * Optionally returns its corresponding driver node.
282  *
283  * @remarks Used in bootloader only.
284  *
285  * @param[in]   DriverListHead
286  * The driver list.
287  *
288  * @param[in]   DriverName
289  * The name of the driver to search for.
290  *
291  * @param[out]  FoundDriver
292  * Optional pointer that receives in output the address of the
293  * matching driver node, if any, or NULL if none has been found.
294  *
295  * @return
296  * TRUE if the driver has been found, FALSE if not.
297  **/
298 CODE_SEG("INIT")
299 BOOLEAN
300 NTAPI
301 CmpIsDriverInList(
302     _In_ PLIST_ENTRY DriverListHead,
303     _In_ PCUNICODE_STRING DriverName,
304     _Out_opt_ PBOOT_DRIVER_NODE* FoundDriver)
305 {
306     PLIST_ENTRY Entry;
307     PBOOT_DRIVER_NODE DriverNode;
308 
309     for (Entry = DriverListHead->Flink;
310          Entry != DriverListHead;
311          Entry = Entry->Flink)
312     {
313         DriverNode = CONTAINING_RECORD(Entry,
314                                        BOOT_DRIVER_NODE,
315                                        ListEntry.Link);
316 
317         if (RtlEqualUnicodeString(&DriverNode->Name,
318                                   DriverName,
319                                   TRUE))
320         {
321             /* The driver node has been found */
322             if (FoundDriver)
323                 *FoundDriver = DriverNode;
324             return TRUE;
325         }
326     }
327 
328     /* None has been found */
329     if (FoundDriver)
330         *FoundDriver = NULL;
331     return FALSE;
332 }
333 
334 #endif /* _BLDR_ */
335 
336 /**
337  * @brief
338  * Inserts the specified driver entry into the driver list.
339  *
340  * @param[in]   Hive
341  * The SYSTEM hive.
342  *
343  * @param[in]   DriverCell
344  * The registry key's hive cell of the driver to be added, inside
345  * the "Services" sub-key of the currently selected control set.
346  *
347  * @param[in]   GroupOrderCell
348  * The hive cell of the "Control\GroupOrderList" registry key
349  * inside the currently selected control set.
350  *
351  * @param[in]   RegistryPath
352  * Constant UNICODE_STRING pointing to
353  * "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\".
354  *
355  * @param[in,out]   DriverListHead
356  * The driver list where to insert the driver entry.
357  *
358  * @return
359  * TRUE if the driver has been inserted into the list, FALSE if not.
360  **/
361 CODE_SEG("INIT")
362 BOOLEAN
363 NTAPI
364 CmpAddDriverToList(
365     _In_ PHHIVE Hive,
366     _In_ HCELL_INDEX DriverCell,
367     _In_ HCELL_INDEX GroupOrderCell,
368     _In_ PCUNICODE_STRING RegistryPath,
369     _Inout_ PLIST_ENTRY DriverListHead)
370 {
371     PBOOT_DRIVER_NODE DriverNode;
372     PBOOT_DRIVER_LIST_ENTRY DriverEntry;
373     PCM_KEY_NODE Node;
374     PCM_KEY_VALUE Value;
375     ULONG Length;
376     USHORT NameLength;
377     HCELL_INDEX ValueCell, TagCell;
378     PUNICODE_STRING FilePath, RegistryString;
379     UNICODE_STRING Name;
380     PULONG ErrorControl;
381     PWCHAR Buffer;
382 
383     /* Sanity check: We shouldn't need to release any acquired cells */
384     ASSERT(Hive->ReleaseCellRoutine == NULL);
385 
386     /* Allocate a driver node and initialize it */
387     DriverNode = Hive->Allocate(sizeof(BOOT_DRIVER_NODE), FALSE, TAG_CM);
388     if (!DriverNode)
389         return FALSE;
390 
391     RtlZeroMemory(DriverNode, sizeof(BOOT_DRIVER_NODE));
392     DriverEntry = &DriverNode->ListEntry;
393 
394     /* Get the driver cell */
395     Node = (PCM_KEY_NODE)HvGetCell(Hive, DriverCell);
396     if (!Node)
397         goto Failure;
398 
399     /* Get the name from the cell */
400     NameLength = (Node->Flags & KEY_COMP_NAME) ?
401                  CmpCompressedNameSize(Node->Name, Node->NameLength) :
402                  Node->NameLength;
403     if (NameLength < sizeof(WCHAR))
404         goto Failure;
405 
406     /* Now allocate the buffer for it and copy the name */
407     RtlInitEmptyUnicodeString(&DriverNode->Name,
408                               Hive->Allocate(NameLength, FALSE, TAG_CM),
409                               NameLength);
410     if (!DriverNode->Name.Buffer)
411         goto Failure;
412 
413     DriverNode->Name.Length = NameLength;
414     if (Node->Flags & KEY_COMP_NAME)
415     {
416         /* Compressed name */
417         CmpCopyCompressedName(DriverNode->Name.Buffer,
418                               DriverNode->Name.Length,
419                               Node->Name,
420                               Node->NameLength);
421     }
422     else
423     {
424         /* Normal name */
425         RtlCopyMemory(DriverNode->Name.Buffer, Node->Name, Node->NameLength);
426     }
427 
428     /* Now find the image path */
429     RtlInitUnicodeString(&Name, L"ImagePath");
430     ValueCell = CmpFindValueByName(Hive, Node, &Name);
431     if (ValueCell == HCELL_NIL)
432     {
433         /* Could not find it, so assume the drivers path */
434         Length = sizeof(L"System32\\Drivers\\") + NameLength + sizeof(L".sys");
435 
436         /* Allocate the path name */
437         FilePath = &DriverEntry->FilePath;
438         RtlInitEmptyUnicodeString(FilePath,
439                                   Hive->Allocate(Length, FALSE, TAG_CM),
440                                   (USHORT)Length);
441         if (!FilePath->Buffer)
442             goto Failure;
443 
444         /* Write the path name */
445         if (!NT_SUCCESS(RtlAppendUnicodeToString(FilePath, L"System32\\Drivers\\"))  ||
446             !NT_SUCCESS(RtlAppendUnicodeStringToString(FilePath, &DriverNode->Name)) ||
447             !NT_SUCCESS(RtlAppendUnicodeToString(FilePath, L".sys")))
448         {
449             goto Failure;
450         }
451     }
452     else
453     {
454         /* Path name exists, so grab it */
455         Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
456         if (!Value)
457             goto Failure;
458         if ((Value->Type != REG_SZ) && (Value->Type != REG_EXPAND_SZ))
459             goto Failure;
460         Buffer = (PWCHAR)CmpValueToData(Hive, Value, &Length);
461         if (!Buffer)
462             goto Failure;
463         if (IS_NULL_TERMINATED(Buffer, Length))
464             Length -= sizeof(UNICODE_NULL);
465         if (Length < sizeof(WCHAR))
466             goto Failure;
467 
468         /* Allocate and setup the path name */
469         FilePath = &DriverEntry->FilePath;
470         RtlInitEmptyUnicodeString(FilePath,
471                                   Hive->Allocate(Length, FALSE, TAG_CM),
472                                   (USHORT)Length);
473         if (!FilePath->Buffer)
474             goto Failure;
475 
476         /* Transfer the data */
477         RtlCopyMemory(FilePath->Buffer, Buffer, Length);
478         FilePath->Length = (USHORT)Length;
479     }
480 
481     /* Now build the registry path */
482     RegistryString = &DriverEntry->RegistryPath;
483     Length = RegistryPath->Length + NameLength;
484     RtlInitEmptyUnicodeString(RegistryString,
485                               Hive->Allocate(Length, FALSE, TAG_CM),
486                               (USHORT)Length);
487     if (!RegistryString->Buffer)
488         goto Failure;
489 
490     /* Add the driver name to it */
491     if (!NT_SUCCESS(RtlAppendUnicodeStringToString(RegistryString, RegistryPath)) ||
492         !NT_SUCCESS(RtlAppendUnicodeStringToString(RegistryString, &DriverNode->Name)))
493     {
494         goto Failure;
495     }
496 
497     /* The entry is done, add it */
498     InsertHeadList(DriverListHead, &DriverEntry->Link);
499 
500     /* Now find error control settings */
501     RtlInitUnicodeString(&Name, L"ErrorControl");
502     ValueCell = CmpFindValueByName(Hive, Node, &Name);
503     if (ValueCell == HCELL_NIL)
504     {
505         /* Could not find it, so assume default */
506         DriverNode->ErrorControl = NormalError;
507     }
508     else
509     {
510         /* Otherwise, read whatever the data says */
511         Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
512         if (!Value)
513             goto Failure;
514         if (Value->Type != REG_DWORD)
515             goto Failure;
516         ErrorControl = (PULONG)CmpValueToData(Hive, Value, &Length);
517         if (!ErrorControl)
518             goto Failure;
519         if (Length < sizeof(ULONG))
520             goto Failure;
521 
522         DriverNode->ErrorControl = *ErrorControl;
523     }
524 
525     /* Next, get the group cell */
526     RtlInitUnicodeString(&Name, L"group");
527     ValueCell = CmpFindValueByName(Hive, Node, &Name);
528     if (ValueCell == HCELL_NIL)
529     {
530         /* Could not find it, so set an empty string */
531         RtlInitEmptyUnicodeString(&DriverNode->Group, NULL, 0);
532     }
533     else
534     {
535         /* Found it, read the group value */
536         Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
537         if (!Value)
538             goto Failure;
539         if (Value->Type != REG_SZ) // REG_EXPAND_SZ not really allowed there.
540             goto Failure;
541 
542         /* Copy it into the node */
543         Buffer = (PWCHAR)CmpValueToData(Hive, Value, &Length);
544         if (!Buffer)
545             goto Failure;
546         if (IS_NULL_TERMINATED(Buffer, Length))
547             Length -= sizeof(UNICODE_NULL);
548 
549         DriverNode->Group.Buffer = Buffer;
550         DriverNode->Group.Length = (USHORT)Length;
551         DriverNode->Group.MaximumLength = DriverNode->Group.Length;
552     }
553 
554     /* Finally, find the tag */
555     RtlInitUnicodeString(&Name, L"Tag");
556     TagCell = CmpFindValueByName(Hive, Node, &Name);
557     if (TagCell == HCELL_NIL)
558     {
559         /* No tag, so load last */
560         DriverNode->Tag = -1;
561     }
562     else
563     {
564         /* Otherwise, decode it based on tag order */
565         DriverNode->Tag = CmpFindTagIndex(Hive,
566                                           TagCell,
567                                           GroupOrderCell,
568                                           &DriverNode->Group);
569     }
570 
571     CMTRACE(CM_BOOT_DEBUG, "Adding boot driver: '%wZ', '%wZ'\n",
572             &DriverNode->Name, &DriverEntry->FilePath);
573 
574     /* All done! */
575     return TRUE;
576 
577 Failure:
578     if (DriverEntry->RegistryPath.Buffer)
579     {
580         Hive->Free(DriverEntry->RegistryPath.Buffer,
581                    DriverEntry->RegistryPath.MaximumLength);
582     }
583     if (DriverEntry->FilePath.Buffer)
584     {
585         Hive->Free(DriverEntry->FilePath.Buffer,
586                    DriverEntry->FilePath.MaximumLength);
587     }
588     if (DriverNode->Name.Buffer)
589     {
590         Hive->Free(DriverNode->Name.Buffer,
591                    DriverNode->Name.MaximumLength);
592     }
593     Hive->Free(DriverNode, sizeof(BOOT_DRIVER_NODE));
594 
595     return FALSE;
596 }
597 
598 /**
599  * @brief
600  * Checks whether the specified driver has the expected load type.
601  *
602  * @param[in]   Hive
603  * The SYSTEM hive.
604  *
605  * @param[in]   DriverCell
606  * The registry key's hive cell of the driver, inside the
607  * "Services" sub-key of the currently selected control set.
608  *
609  * @param[in]   LoadType
610  * The load type the driver should match.
611  *
612  * @return
613  * TRUE if the driver's load type matches, FALSE if not.
614  **/
615 CODE_SEG("INIT")
616 static
617 BOOLEAN
618 CmpIsLoadType(
619     _In_ PHHIVE Hive,
620     _In_ HCELL_INDEX Cell,
621     _In_ SERVICE_LOAD_TYPE LoadType)
622 {
623     PCM_KEY_NODE Node;
624     PCM_KEY_VALUE Value;
625     UNICODE_STRING Name = RTL_CONSTANT_STRING(L"Start");
626     HCELL_INDEX ValueCell;
627     ULONG Length;
628     PULONG Data;
629 
630     /* Sanity check: We shouldn't need to release any acquired cells */
631     ASSERT(Hive->ReleaseCellRoutine == NULL);
632 
633     /* Open the start cell */
634     Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
635     if (!Node) return FALSE;
636     ValueCell = CmpFindValueByName(Hive, Node, &Name);
637     if (ValueCell == HCELL_NIL) return FALSE;
638 
639     /* Read the start value */
640     Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
641     if (!Value) return FALSE;
642     if (Value->Type != REG_DWORD) return FALSE;
643     Data = (PULONG)CmpValueToData(Hive, Value, &Length);
644     if (!Data) return FALSE;
645     if (Length < sizeof(ULONG)) return FALSE;
646 
647     /* Return if the type matches */
648     return (*Data == LoadType);
649 }
650 
651 /**
652  * @brief
653  * Enumerates all drivers within the given control set and load type,
654  * present in the "Services" sub-key, and inserts them into the driver list.
655  *
656  * @param[in]   Hive
657  * The SYSTEM hive.
658  *
659  * @param[in]   ControlSet
660  * The control set registry key's hive cell.
661  *
662  * @param[in]   LoadType
663  * The load type the driver should match.
664  *
665  * @param[in]   BootFileSystem
666  * Optional name of the boot file system, for which to insert
667  * its corresponding driver.
668  *
669  * @param[in,out]   DriverListHead
670  * The driver list where to insert the enumerated drivers.
671  *
672  * @return
673  * TRUE if the drivers have been successfully enumerated and inserted,
674  * FALSE if not.
675  **/
676 CODE_SEG("INIT")
677 BOOLEAN
678 NTAPI
679 CmpFindDrivers(
680     _In_ PHHIVE Hive,
681     _In_ HCELL_INDEX ControlSet,
682     _In_ SERVICE_LOAD_TYPE LoadType,
683     _In_opt_ PCWSTR BootFileSystem,
684     _Inout_ PLIST_ENTRY DriverListHead)
685 {
686     HCELL_INDEX ServicesCell, ControlCell, GroupOrderCell, DriverCell;
687     HCELL_INDEX SafeBootCell = HCELL_NIL;
688     ULONG i;
689     UNICODE_STRING Name;
690     UNICODE_STRING KeyPath;
691     PCM_KEY_NODE ControlNode, ServicesNode, Node;
692     PBOOT_DRIVER_NODE FsNode;
693 
694     /* Sanity check: We shouldn't need to release any acquired cells */
695     ASSERT(Hive->ReleaseCellRoutine == NULL);
696 
697     /* Open the control set key */
698     ControlNode = (PCM_KEY_NODE)HvGetCell(Hive, ControlSet);
699     if (!ControlNode) return FALSE;
700 
701     /* Get services cell */
702     RtlInitUnicodeString(&Name, L"Services");
703     ServicesCell = CmpFindSubKeyByName(Hive, ControlNode, &Name);
704     if (ServicesCell == HCELL_NIL) return FALSE;
705 
706     /* Open services key */
707     ServicesNode = (PCM_KEY_NODE)HvGetCell(Hive, ServicesCell);
708     if (!ServicesNode) return FALSE;
709 
710     /* Get control cell */
711     RtlInitUnicodeString(&Name, L"Control");
712     ControlCell = CmpFindSubKeyByName(Hive, ControlNode, &Name);
713     if (ControlCell == HCELL_NIL) return FALSE;
714 
715     /* Get the group order cell and read it */
716     Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
717     if (!Node) return FALSE;
718     RtlInitUnicodeString(&Name, L"GroupOrderList");
719     GroupOrderCell = CmpFindSubKeyByName(Hive, Node, &Name);
720     if (GroupOrderCell == HCELL_NIL) return FALSE;
721 
722     /* Get Safe Boot cell */
723     if (InitSafeBootMode)
724     {
725         /* Open the Safe Boot key */
726         RtlInitUnicodeString(&Name, L"SafeBoot");
727         Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
728         if (!Node) return FALSE;
729         SafeBootCell = CmpFindSubKeyByName(Hive, Node, &Name);
730         if (SafeBootCell == HCELL_NIL) return FALSE;
731 
732         /* Open the correct start key (depending on the mode) */
733         Node = (PCM_KEY_NODE)HvGetCell(Hive, SafeBootCell);
734         if (!Node) return FALSE;
735         switch (InitSafeBootMode)
736         {
737             /* NOTE: Assumes MINIMAL (1) and DSREPAIR (3) load same items */
738             case 1:
739             case 3: RtlInitUnicodeString(&Name, L"Minimal"); break;
740             case 2: RtlInitUnicodeString(&Name, L"Network"); break;
741             default: return FALSE;
742         }
743         SafeBootCell = CmpFindSubKeyByName(Hive, Node, &Name);
744         if (SafeBootCell == HCELL_NIL) return FALSE;
745     }
746 
747     /* Build the root registry path */
748     RtlInitUnicodeString(&KeyPath, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
749 
750     /* Enumerate each sub-key */
751     i = 0;
752     DriverCell = CmpFindSubKeyByNumber(Hive, ServicesNode, i);
753     while (DriverCell != HCELL_NIL)
754     {
755         /* Make sure it's a driver of this start type AND is "safe" to load */
756         if (CmpIsLoadType(Hive, DriverCell, LoadType) &&
757             CmpIsSafe(Hive, SafeBootCell, DriverCell))
758         {
759             /* Add it to the list */
760             if (!CmpAddDriverToList(Hive,
761                                     DriverCell,
762                                     GroupOrderCell,
763                                     &KeyPath,
764                                     DriverListHead))
765             {
766                 CMTRACE(CM_BOOT_DEBUG, "  Failed to add boot driver\n");
767             }
768         }
769 
770         /* Go to the next sub-key */
771         DriverCell = CmpFindSubKeyByNumber(Hive, ServicesNode, ++i);
772     }
773 
774     /* Check if we have a boot file system */
775     if (BootFileSystem)
776     {
777         /* Find it */
778         RtlInitUnicodeString(&Name, BootFileSystem);
779         DriverCell = CmpFindSubKeyByName(Hive, ServicesNode, &Name);
780         if (DriverCell != HCELL_NIL)
781         {
782             CMTRACE(CM_BOOT_DEBUG, "Adding Boot FileSystem '%S'\n",
783                     BootFileSystem);
784 
785             /* Always add it to the list */
786             if (!CmpAddDriverToList(Hive,
787                                     DriverCell,
788                                     GroupOrderCell,
789                                     &KeyPath,
790                                     DriverListHead))
791             {
792                 CMTRACE(CM_BOOT_DEBUG, "  Failed to add boot driver\n");
793             }
794             else
795             {
796                 /* Mark it as critical so it always loads */
797                 FsNode = CONTAINING_RECORD(DriverListHead->Flink,
798                                            BOOT_DRIVER_NODE,
799                                            ListEntry.Link);
800                 FsNode->ErrorControl = SERVICE_ERROR_CRITICAL;
801             }
802         }
803     }
804 
805     /* We're done! */
806     return TRUE;
807 }
808 
809 /**
810  * @brief
811  * Performs the driver list sorting, according to the ordering list.
812  *
813  * @param[in]   Hive
814  * The SYSTEM hive.
815  *
816  * @param[in]   ControlSet
817  * The control set registry key's hive cell.
818  *
819  * @param[in,out]   DriverListHead
820  * The driver list to sort.
821  *
822  * @return
823  * TRUE if sorting has been successfully done, FALSE if not.
824  **/
825 CODE_SEG("INIT")
826 static
827 BOOLEAN
828 CmpDoSort(
829     _Inout_ PLIST_ENTRY DriverListHead,
830     _In_ PCUNICODE_STRING OrderList)
831 {
832     PWCHAR Current, End = NULL;
833     UNICODE_STRING GroupName;
834     PLIST_ENTRY NextEntry;
835     PBOOT_DRIVER_NODE CurrentNode;
836 
837     /* We're going from end to start, so get to the last group and keep going */
838     Current = &OrderList->Buffer[OrderList->Length / sizeof(WCHAR)];
839     while (Current > OrderList->Buffer)
840     {
841         /* Scan the current string */
842         do
843         {
844             if (*Current == UNICODE_NULL) End = Current;
845         } while ((*(--Current - 1) != UNICODE_NULL) && (Current != OrderList->Buffer));
846 
847         /* This is our cleaned up string for this specific group */
848         ASSERT(End != NULL);
849         GroupName.Length = (USHORT)(End - Current) * sizeof(WCHAR);
850         GroupName.MaximumLength = GroupName.Length;
851         GroupName.Buffer = Current;
852 
853         /* Now loop the driver list */
854         NextEntry = DriverListHead->Flink;
855         while (NextEntry != DriverListHead)
856         {
857             /* Get this node */
858             CurrentNode = CONTAINING_RECORD(NextEntry,
859                                             BOOT_DRIVER_NODE,
860                                             ListEntry.Link);
861 
862             /* Get the next entry now since we'll do a relink */
863             NextEntry = NextEntry->Flink;
864 
865             /* Is there a group name and does it match the current group? */
866             if (CurrentNode->Group.Buffer &&
867                 RtlEqualUnicodeString(&GroupName, &CurrentNode->Group, TRUE))
868             {
869                 /* Remove from this location and re-link in the new one */
870                 RemoveEntryList(&CurrentNode->ListEntry.Link);
871                 InsertHeadList(DriverListHead, &CurrentNode->ListEntry.Link);
872             }
873         }
874 
875         /* Move on */
876         --Current;
877     }
878 
879     /* All done */
880     return TRUE;
881 }
882 
883 /**
884  * @brief
885  * Sorts the driver list, according to the drivers' group load ordering.
886  *
887  * @param[in]   Hive
888  * The SYSTEM hive.
889  *
890  * @param[in]   ControlSet
891  * The control set registry key's hive cell.
892  *
893  * @param[in,out]   DriverListHead
894  * The driver list to sort.
895  *
896  * @return
897  * TRUE if sorting has been successfully done, FALSE if not.
898  **/
899 CODE_SEG("INIT")
900 BOOLEAN
901 NTAPI
902 CmpSortDriverList(
903     _In_ PHHIVE Hive,
904     _In_ HCELL_INDEX ControlSet,
905     _Inout_ PLIST_ENTRY DriverListHead)
906 {
907     PCM_KEY_NODE Node;
908     PCM_KEY_VALUE ListValue;
909     HCELL_INDEX ControlCell, GroupOrder, ListCell;
910     UNICODE_STRING Name, OrderList;
911     ULONG Length;
912 
913     /* Sanity check: We shouldn't need to release any acquired cells */
914     ASSERT(Hive->ReleaseCellRoutine == NULL);
915 
916     /* Open the control key */
917     Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlSet);
918     if (!Node) return FALSE;
919     RtlInitUnicodeString(&Name, L"Control");
920     ControlCell = CmpFindSubKeyByName(Hive, Node, &Name);
921     if (ControlCell == HCELL_NIL) return FALSE;
922 
923     /* Open the service group order */
924     Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
925     if (!Node) return FALSE;
926     RtlInitUnicodeString(&Name, L"ServiceGroupOrder");
927     GroupOrder = CmpFindSubKeyByName(Hive, Node, &Name);
928     if (GroupOrder == HCELL_NIL) return FALSE;
929 
930     /* Open the list key */
931     Node = (PCM_KEY_NODE)HvGetCell(Hive, GroupOrder);
932     if (!Node) return FALSE;
933     RtlInitUnicodeString(&Name, L"list");
934     ListCell = CmpFindValueByName(Hive, Node, &Name);
935     if (ListCell == HCELL_NIL) return FALSE;
936 
937     /* Read the actual list */
938     ListValue = (PCM_KEY_VALUE)HvGetCell(Hive, ListCell);
939     if (!ListValue) return FALSE;
940     if (ListValue->Type != REG_MULTI_SZ) return FALSE;
941 
942     /* Copy it into a buffer */
943     OrderList.Buffer = (PWCHAR)CmpValueToData(Hive, ListValue, &Length);
944     if (!OrderList.Buffer) return FALSE;
945     if (!IS_NULL_TERMINATED(OrderList.Buffer, Length)) return FALSE;
946     OrderList.Length = (USHORT)Length - sizeof(UNICODE_NULL);
947     OrderList.MaximumLength = OrderList.Length;
948 
949     /* And start the sort algorithm */
950     return CmpDoSort(DriverListHead, &OrderList);
951 }
952 
953 CODE_SEG("INIT")
954 static
955 BOOLEAN
956 CmpOrderGroup(
957     _In_ PBOOT_DRIVER_NODE StartNode,
958     _In_ PBOOT_DRIVER_NODE EndNode)
959 {
960     PBOOT_DRIVER_NODE CurrentNode, PreviousNode;
961     PLIST_ENTRY ListEntry;
962 
963     /* Base case, nothing to do */
964     if (StartNode == EndNode) return TRUE;
965 
966     /* Loop the nodes */
967     CurrentNode = StartNode;
968     do
969     {
970         /* Save this as the previous node */
971         PreviousNode = CurrentNode;
972 
973         /* And move to the next one */
974         ListEntry = CurrentNode->ListEntry.Link.Flink;
975         CurrentNode = CONTAINING_RECORD(ListEntry,
976                                         BOOT_DRIVER_NODE,
977                                         ListEntry.Link);
978 
979         /* Check if the previous driver had a bigger tag */
980         if (PreviousNode->Tag > CurrentNode->Tag)
981         {
982             /* Check if we need to update the tail */
983             if (CurrentNode == EndNode)
984             {
985                 /* Update the tail */
986                 ListEntry = CurrentNode->ListEntry.Link.Blink;
987                 EndNode = CONTAINING_RECORD(ListEntry,
988                                             BOOT_DRIVER_NODE,
989                                             ListEntry.Link);
990             }
991 
992             /* Remove this driver since we need to move it */
993             RemoveEntryList(&CurrentNode->ListEntry.Link);
994 
995             /* Keep looping until we find a driver with a lower tag than ours */
996             while ((PreviousNode->Tag > CurrentNode->Tag) && (PreviousNode != StartNode))
997             {
998                 /* We'll be re-inserted at this spot */
999                 ListEntry = PreviousNode->ListEntry.Link.Blink;
1000                 PreviousNode = CONTAINING_RECORD(ListEntry,
1001                                                  BOOT_DRIVER_NODE,
1002                                                  ListEntry.Link);
1003             }
1004 
1005             /* Do the insert in the new location */
1006             InsertTailList(&PreviousNode->ListEntry.Link, &CurrentNode->ListEntry.Link);
1007 
1008             /* Update the head, if needed */
1009             if (PreviousNode == StartNode) StartNode = CurrentNode;
1010         }
1011     } while (CurrentNode != EndNode);
1012 
1013     /* All done */
1014     return TRUE;
1015 }
1016 
1017 /**
1018  * @brief
1019  * Removes potential circular dependencies (cycles) and sorts the driver list.
1020  *
1021  * @param[in,out]   DriverListHead
1022  * The driver list to sort.
1023  *
1024  * @return
1025  * Always TRUE.
1026  **/
1027 CODE_SEG("INIT")
1028 BOOLEAN
1029 NTAPI
1030 CmpResolveDriverDependencies(
1031     _Inout_ PLIST_ENTRY DriverListHead)
1032 {
1033     PLIST_ENTRY NextEntry;
1034     PBOOT_DRIVER_NODE StartNode, EndNode, CurrentNode;
1035 
1036     /* Loop the list */
1037     NextEntry = DriverListHead->Flink;
1038     while (NextEntry != DriverListHead)
1039     {
1040         /* Find the first entry */
1041         StartNode = CONTAINING_RECORD(NextEntry,
1042                                       BOOT_DRIVER_NODE,
1043                                       ListEntry.Link);
1044         do
1045         {
1046             /* Find the last entry */
1047             EndNode = CONTAINING_RECORD(NextEntry,
1048                                         BOOT_DRIVER_NODE,
1049                                         ListEntry.Link);
1050 
1051             /* Get the next entry */
1052             NextEntry = NextEntry->Flink;
1053             CurrentNode = CONTAINING_RECORD(NextEntry,
1054                                             BOOT_DRIVER_NODE,
1055                                             ListEntry.Link);
1056 
1057             /* If the next entry is back to the top, break out */
1058             if (NextEntry == DriverListHead) break;
1059 
1060             /* Otherwise, check if this entry is equal */
1061             if (!RtlEqualUnicodeString(&StartNode->Group,
1062                                        &CurrentNode->Group,
1063                                        TRUE))
1064             {
1065                 /* It is, so we've detected a cycle, break out */
1066                 break;
1067             }
1068         } while (NextEntry != DriverListHead);
1069 
1070         /* Now we have the correct start and end pointers, so do the sort */
1071         CmpOrderGroup(StartNode, EndNode);
1072     }
1073 
1074     /* We're done */
1075     return TRUE;
1076 }
1077 
1078 CODE_SEG("INIT")
1079 static
1080 BOOLEAN
1081 CmpIsSafe(
1082     _In_ PHHIVE Hive,
1083     _In_ HCELL_INDEX SafeBootCell,
1084     _In_ HCELL_INDEX DriverCell)
1085 {
1086     PCM_KEY_NODE SafeBootNode;
1087     PCM_KEY_NODE DriverNode;
1088     PCM_KEY_VALUE KeyValue;
1089     HCELL_INDEX CellIndex;
1090     ULONG Length;
1091     UNICODE_STRING Name;
1092     PWCHAR Buffer;
1093 
1094     /* Sanity check: We shouldn't need to release any acquired cells */
1095     ASSERT(Hive->ReleaseCellRoutine == NULL);
1096 
1097     /* Driver key node (mandatory) */
1098     ASSERT(DriverCell != HCELL_NIL);
1099     DriverNode = (PCM_KEY_NODE)HvGetCell(Hive, DriverCell);
1100     if (!DriverNode) return FALSE;
1101 
1102     /* Safe boot key node (optional but return TRUE if not present) */
1103     if (SafeBootCell == HCELL_NIL) return TRUE;
1104     SafeBootNode = (PCM_KEY_NODE)HvGetCell(Hive, SafeBootCell);
1105     if (!SafeBootNode) return FALSE;
1106 
1107     /* Search by the name from the group */
1108     RtlInitUnicodeString(&Name, L"Group");
1109     CellIndex = CmpFindValueByName(Hive, DriverNode, &Name);
1110     if (CellIndex != HCELL_NIL)
1111     {
1112         KeyValue = (PCM_KEY_VALUE)HvGetCell(Hive, CellIndex);
1113         if (!KeyValue) return FALSE;
1114 
1115         if (KeyValue->Type == REG_SZ) // REG_EXPAND_SZ not really allowed there.
1116         {
1117             /* Compose the search 'key' */
1118             Buffer = (PWCHAR)CmpValueToData(Hive, KeyValue, &Length);
1119             if (!Buffer)
1120                 return FALSE;
1121             if (IS_NULL_TERMINATED(Buffer, Length))
1122                 Length -= sizeof(UNICODE_NULL);
1123 
1124             Name.Buffer = Buffer;
1125             Name.Length = (USHORT)Length;
1126             Name.MaximumLength = Name.Length;
1127 
1128             /* Search for corresponding key in the Safe Boot key */
1129             CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
1130             if (CellIndex != HCELL_NIL) return TRUE;
1131         }
1132     }
1133 
1134     /* Group has not been found - find driver name */
1135     Length = (DriverNode->Flags & KEY_COMP_NAME) ?
1136              CmpCompressedNameSize(DriverNode->Name, DriverNode->NameLength) :
1137              DriverNode->NameLength;
1138     if (Length < sizeof(WCHAR))
1139         return FALSE;
1140 
1141     /* Now allocate the buffer for it and copy the name */
1142     RtlInitEmptyUnicodeString(&Name,
1143                               Hive->Allocate(Length, FALSE, TAG_CM),
1144                               (USHORT)Length);
1145     if (!Name.Buffer)
1146         return FALSE;
1147 
1148     Name.Length = (USHORT)Length;
1149     if (DriverNode->Flags & KEY_COMP_NAME)
1150     {
1151         /* Compressed name */
1152         CmpCopyCompressedName(Name.Buffer,
1153                               Name.Length,
1154                               DriverNode->Name,
1155                               DriverNode->NameLength);
1156     }
1157     else
1158     {
1159         /* Normal name */
1160         RtlCopyMemory(Name.Buffer, DriverNode->Name, DriverNode->NameLength);
1161     }
1162 
1163     CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
1164     Hive->Free(Name.Buffer, Name.MaximumLength);
1165     if (CellIndex != HCELL_NIL) return TRUE;
1166 
1167     /* Not group or driver name - search by image name */
1168     RtlInitUnicodeString(&Name, L"ImagePath");
1169     CellIndex = CmpFindValueByName(Hive, DriverNode, &Name);
1170     if (CellIndex != HCELL_NIL)
1171     {
1172         KeyValue = (PCM_KEY_VALUE)HvGetCell(Hive, CellIndex);
1173         if (!KeyValue) return FALSE;
1174 
1175         if ((KeyValue->Type == REG_SZ) || (KeyValue->Type == REG_EXPAND_SZ))
1176         {
1177             /* Compose the search 'key' */
1178             Buffer = (PWCHAR)CmpValueToData(Hive, KeyValue, &Length);
1179             if (!Buffer) return FALSE;
1180             if (Length < sizeof(WCHAR)) return FALSE;
1181 
1182             /* Get the base image file name */
1183             // FIXME: wcsrchr() may fail if Buffer is *not* NULL-terminated!
1184             Name.Buffer = wcsrchr(Buffer, OBJ_NAME_PATH_SEPARATOR);
1185             if (!Name.Buffer) return FALSE;
1186             ++Name.Buffer;
1187 
1188             /* Length of the base name must be >=1 WCHAR */
1189             if (((ULONG_PTR)Name.Buffer - (ULONG_PTR)Buffer) >= Length)
1190                 return FALSE;
1191             Length -= ((ULONG_PTR)Name.Buffer - (ULONG_PTR)Buffer);
1192             if (IS_NULL_TERMINATED(Name.Buffer, Length))
1193                 Length -= sizeof(UNICODE_NULL);
1194             if (Length < sizeof(WCHAR)) return FALSE;
1195 
1196             Name.Length = (USHORT)Length;
1197             Name.MaximumLength = Name.Length;
1198 
1199             /* Search for corresponding key in the Safe Boot key */
1200             CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
1201             if (CellIndex != HCELL_NIL) return TRUE;
1202         }
1203     }
1204 
1205     /* Nothing found - nothing else to search */
1206     return FALSE;
1207 }
1208 
1209 /**
1210  * @brief
1211  * Empties the driver list and frees all allocated driver nodes in it.
1212  *
1213  * @param[in]   Hive
1214  * The SYSTEM hive (used only for the Hive->Free() memory deallocator).
1215  *
1216  * @param[in,out]   DriverListHead
1217  * The driver list to free.
1218  *
1219  * @return  None
1220  **/
1221 CODE_SEG("INIT")
1222 VOID
1223 NTAPI
1224 CmpFreeDriverList(
1225     _In_ PHHIVE Hive,
1226     _Inout_ PLIST_ENTRY DriverListHead)
1227 {
1228     PLIST_ENTRY Entry;
1229     PBOOT_DRIVER_NODE DriverNode;
1230 
1231     /* Loop through the list and remove each driver node */
1232     while (!IsListEmpty(DriverListHead))
1233     {
1234         /* Get the driver node */
1235         Entry = RemoveHeadList(DriverListHead);
1236         DriverNode = CONTAINING_RECORD(Entry,
1237                                        BOOT_DRIVER_NODE,
1238                                        ListEntry.Link);
1239 
1240         /* Free any allocated string buffers, then the node */
1241         if (DriverNode->ListEntry.RegistryPath.Buffer)
1242         {
1243             Hive->Free(DriverNode->ListEntry.RegistryPath.Buffer,
1244                        DriverNode->ListEntry.RegistryPath.MaximumLength);
1245         }
1246         if (DriverNode->ListEntry.FilePath.Buffer)
1247         {
1248             Hive->Free(DriverNode->ListEntry.FilePath.Buffer,
1249                        DriverNode->ListEntry.FilePath.MaximumLength);
1250         }
1251         if (DriverNode->Name.Buffer)
1252         {
1253             Hive->Free(DriverNode->Name.Buffer,
1254                        DriverNode->Name.MaximumLength);
1255         }
1256         Hive->Free(DriverNode, sizeof(BOOT_DRIVER_NODE));
1257     }
1258 }
1259 
1260 /* EOF */
1261