reactos/ntoskrnl/config/cmboot.c
Hermès Bélusca-Maïto 6ff0232368
[NTOS:CM] Adapt cmboot.c for usage in NT/ReactOS bootloader.
- Add a new cmboot.h header to isolate the boot-support definitions
  shared with the NT/ReactOS bootloader.

- Move CmpFreeDriverList() to cmboot.c so that we can use it for
  cleanup paths in the NT/ReactOS bootloader.

- CmpFindControlSet(): Directly build the control set name in UNICODE,
  instead of doing an ANSI->UNICODE conversion.

- Directly assign the CurrentControlSet\Services constant string,
  instead of going the route of init-empty-string + append-string.
  This is possible since that string is not modified later.

- Remove ASSERT(FALSE), replacing them with correct failure handling.

- Add cleanup paths in CmpAddDriverToList().

- Simplify and fix CmpFreeDriverList(): it's the full DriverNode
  that needs to be freed; not the LIST_ENTRY pointer.

- Add other validity checks:
  * Registry value types and data sizes;
  * For multi-strings, verify that they are NULL-terminated.
  * For (multi-)strings, check whether they are NULL-terminated before
    optionally removing their trailing NULL character from the count.
    Check also whether they are of zero-length and take appropriate
    action where necessary.

- Add CmpIsDriverInList() for future usage in CMBOOT compiled in
  bootloader mode.

- Add SAL annotations and Doxygen documentation.

- Add debug traces.

- Formatting / code style fixes.

** TODO: Fix SafeBoot support **
2022-04-16 18:37:45 +02:00

1260 lines
39 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: BSD - See COPYING.ARM in the top level directory
* PURPOSE: Configuration Manager - Boot Initialization
* COPYRIGHT: Copyright 2007 Alex Ionescu (alex.ionescu@reactos.org)
* Copyright 2010 ReactOS Portable Systems Group
* Copyright 2022 Hermès Bélusca-Maïto
*
* NOTE: This module is shared by both the kernel and the bootloader.
*/
/* INCLUDES *******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
#ifdef _BLDR_
#undef CODE_SEG
#define CODE_SEG(...)
#include <ntstrsafe.h>
#include <cmlib.h>
#include "internal/cmboot.h"
// HACK: This is part of non-NT-compatible SafeBoot support in kernel.
ULONG InitSafeBootMode = 0;
DBG_DEFAULT_CHANNEL(REGISTRY);
#define CMTRACE(x, fmt, ...) TRACE(fmt, ##__VA_ARGS__) // DPRINT
#endif /* _BLDR_ */
/* DEFINES ********************************************************************/
#define CM_BOOT_DEBUG 0x20
#define IS_NULL_TERMINATED(Buffer, Size) \
(((Size) >= sizeof(WCHAR)) && ((Buffer)[(Size) / sizeof(WCHAR) - 1] == UNICODE_NULL))
/* FUNCTIONS ******************************************************************/
// HACK: This is part of non-NT-compatible SafeBoot support in kernel.
extern ULONG InitSafeBootMode;
CODE_SEG("INIT")
static
BOOLEAN
CmpIsSafe(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX SafeBootCell,
_In_ HCELL_INDEX DriverCell);
/**
* @brief
* Finds the corresponding "HKLM\SYSTEM\ControlSetXXX" system control set
* registry key, according to the "Current", "Default", or "LastKnownGood"
* values in the "HKLM\SYSTEM\Select" registry key.
*
* @param[in] SystemHive
* The SYSTEM hive.
*
* @param[in] RootCell
* The root cell of the SYSTEM hive.
*
* @param[in] SelectKeyName
* The control set to check for: either "Current", "Default", or
* "LastKnownGood", the value of which selects the corresponding
* "HKLM\SYSTEM\ControlSetXXX" control set registry key.
*
* @param[out] AutoSelect
* Value of the "AutoSelect" registry value (unused).
*
* @return
* The control set registry key's hive cell (if found), or HCELL_NIL.
**/
CODE_SEG("INIT")
HCELL_INDEX
NTAPI
CmpFindControlSet(
_In_ PHHIVE SystemHive,
_In_ HCELL_INDEX RootCell,
_In_ PCUNICODE_STRING SelectKeyName,
_Out_ PBOOLEAN AutoSelect)
{
UNICODE_STRING Name;
PCM_KEY_NODE Node;
HCELL_INDEX SelectCell, AutoSelectCell, SelectValueCell, ControlSetCell;
HCELL_INDEX CurrentValueCell;
PCM_KEY_VALUE Value;
ULONG Length;
NTSTATUS Status;
PULONG CurrentData;
PULONG ControlSetId;
WCHAR Buffer[128];
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(SystemHive->ReleaseCellRoutine == NULL);
/* Get the Select key */
RtlInitUnicodeString(&Name, L"select");
Node = (PCM_KEY_NODE)HvGetCell(SystemHive, RootCell);
if (!Node) return HCELL_NIL;
SelectCell = CmpFindSubKeyByName(SystemHive, Node, &Name);
if (SelectCell == HCELL_NIL) return HCELL_NIL;
/* Get AutoSelect value */
RtlInitUnicodeString(&Name, L"AutoSelect");
Node = (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
if (!Node) return HCELL_NIL;
AutoSelectCell = CmpFindValueByName(SystemHive, Node, &Name);
if (AutoSelectCell == HCELL_NIL)
{
/* Assume TRUE if the value is missing */
*AutoSelect = TRUE;
}
else
{
/* Read the value */
Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, AutoSelectCell);
if (!Value) return HCELL_NIL;
// if (Value->Type != REG_DWORD) return HCELL_NIL;
/* Convert it to a boolean */
CurrentData = (PULONG)CmpValueToData(SystemHive, Value, &Length);
if (!CurrentData) return HCELL_NIL;
// if (Length < sizeof(ULONG)) return HCELL_NIL;
*AutoSelect = *(PBOOLEAN)CurrentData;
}
/* Now find the control set being looked up */
Node = (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
if (!Node) return HCELL_NIL;
SelectValueCell = CmpFindValueByName(SystemHive, Node, SelectKeyName);
if (SelectValueCell == HCELL_NIL) return HCELL_NIL;
/* Read the value (corresponding to the CCS ID) */
Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, SelectValueCell);
if (!Value) return HCELL_NIL;
if (Value->Type != REG_DWORD) return HCELL_NIL;
ControlSetId = (PULONG)CmpValueToData(SystemHive, Value, &Length);
if (!ControlSetId) return HCELL_NIL;
if (Length < sizeof(ULONG)) return HCELL_NIL;
/* Now build the CCS's Name */
Status = RtlStringCbPrintfW(Buffer, sizeof(Buffer),
L"ControlSet%03lu", *ControlSetId);
if (!NT_SUCCESS(Status)) return HCELL_NIL;
/* RtlStringCbPrintfW ensures the buffer to be NULL-terminated */
RtlInitUnicodeString(&Name, Buffer);
/* Now open it */
Node = (PCM_KEY_NODE)HvGetCell(SystemHive, RootCell);
if (!Node) return HCELL_NIL;
ControlSetCell = CmpFindSubKeyByName(SystemHive, Node, &Name);
if (ControlSetCell == HCELL_NIL) return HCELL_NIL;
/* Get the value of the "Current" CCS */
RtlInitUnicodeString(&Name, L"Current");
Node = (PCM_KEY_NODE)HvGetCell(SystemHive, SelectCell);
if (!Node) return HCELL_NIL;
CurrentValueCell = CmpFindValueByName(SystemHive, Node, &Name);
/* Make sure it exists */
if (CurrentValueCell != HCELL_NIL)
{
/* Get the current value and make sure it's a ULONG */
Value = (PCM_KEY_VALUE)HvGetCell(SystemHive, CurrentValueCell);
if (!Value) return HCELL_NIL;
if (Value->Type == REG_DWORD)
{
/* Get the data and update it */
CurrentData = (PULONG)CmpValueToData(SystemHive, Value, &Length);
if (!CurrentData) return HCELL_NIL;
if (Length < sizeof(ULONG)) return HCELL_NIL;
*CurrentData = *ControlSetId;
}
}
/* Return the CCS cell */
return ControlSetCell;
}
/**
* @brief
* Finds the index of the driver's "Tag" value
* in its corresponding group ordering list.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] TagCell
* The driver's "Tag" registry value's hive cell.
*
* @param[in] GroupOrderCell
* The hive cell of the "Control\GroupOrderList" registry key
* inside the currently selected control set.
*
* @param[in] GroupName
* The driver's group name.
*
* @return
* The corresponding tag index, or -1 (last position),
* or -2 (next-to-last position).
**/
CODE_SEG("INIT")
static
ULONG
CmpFindTagIndex(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX TagCell,
_In_ HCELL_INDEX GroupOrderCell,
_In_ PCUNICODE_STRING GroupName)
{
PCM_KEY_VALUE TagValue, Value;
PCM_KEY_NODE Node;
HCELL_INDEX OrderCell;
PULONG DriverTag, TagOrder;
ULONG CurrentTag, Length;
BOOLEAN BufferAllocated;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Get the tag */
Value = (PCM_KEY_VALUE)HvGetCell(Hive, TagCell);
if (!Value) return -2;
if (Value->Type != REG_DWORD) return -2;
DriverTag = (PULONG)CmpValueToData(Hive, Value, &Length);
if (!DriverTag) return -2;
if (Length < sizeof(ULONG)) return -2;
/* Get the order array */
Node = (PCM_KEY_NODE)HvGetCell(Hive, GroupOrderCell);
if (!Node) return -2;
OrderCell = CmpFindValueByName(Hive, Node, GroupName);
if (OrderCell == HCELL_NIL) return -2;
/* And read it */
TagValue = (PCM_KEY_VALUE)HvGetCell(Hive, OrderCell);
if (!TagValue) return -2;
if (!CmpGetValueData(Hive,
TagValue,
&Length,
(PVOID*)&TagOrder,
&BufferAllocated,
&OrderCell)
|| !TagOrder)
{
return -2;
}
/* Parse each tag */
for (CurrentTag = 1; CurrentTag <= TagOrder[0]; CurrentTag++)
{
/* Find a match */
if (TagOrder[CurrentTag] == *DriverTag)
{
/* Found it -- return the tag */
if (BufferAllocated) Hive->Free(TagOrder, Length);
return CurrentTag;
}
}
/* No matches, so assume next to last ordering */
if (BufferAllocated) Hive->Free(TagOrder, Length);
return -2;
}
#ifdef _BLDR_
/**
* @brief
* Checks whether the specified named driver is already in the driver list.
* Optionally returns its corresponding driver node.
*
* @remarks Used in bootloader only.
*
* @param[in] DriverListHead
* The driver list.
*
* @param[in] DriverName
* The name of the driver to search for.
*
* @param[out] FoundDriver
* Optional pointer that receives in output the address of the
* matching driver node, if any, or NULL if none has been found.
*
* @return
* TRUE if the driver has been found, FALSE if not.
**/
CODE_SEG("INIT")
BOOLEAN
NTAPI
CmpIsDriverInList(
_In_ PLIST_ENTRY DriverListHead,
_In_ PCUNICODE_STRING DriverName,
_Out_opt_ PBOOT_DRIVER_NODE* FoundDriver)
{
PLIST_ENTRY Entry;
PBOOT_DRIVER_NODE DriverNode;
for (Entry = DriverListHead->Flink;
Entry != DriverListHead;
Entry = Entry->Flink)
{
DriverNode = CONTAINING_RECORD(Entry,
BOOT_DRIVER_NODE,
ListEntry.Link);
if (RtlEqualUnicodeString(&DriverNode->Name,
DriverName,
TRUE))
{
/* The driver node has been found */
if (FoundDriver)
*FoundDriver = DriverNode;
return TRUE;
}
}
/* None has been found */
if (FoundDriver)
*FoundDriver = NULL;
return FALSE;
}
#endif /* _BLDR_ */
/**
* @brief
* Inserts the specified driver entry into the driver list.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] DriverCell
* The registry key's hive cell of the driver to be added, inside
* the "Services" sub-key of the currently selected control set.
*
* @param[in] GroupOrderCell
* The hive cell of the "Control\GroupOrderList" registry key
* inside the currently selected control set.
*
* @param[in] RegistryPath
* Constant UNICODE_STRING pointing to
* "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\".
*
* @param[in,out] DriverListHead
* The driver list where to insert the driver entry.
*
* @return
* TRUE if the driver has been inserted into the list, FALSE if not.
**/
CODE_SEG("INIT")
BOOLEAN
NTAPI
CmpAddDriverToList(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX DriverCell,
_In_ HCELL_INDEX GroupOrderCell,
_In_ PCUNICODE_STRING RegistryPath,
_Inout_ PLIST_ENTRY DriverListHead)
{
PBOOT_DRIVER_NODE DriverNode;
PBOOT_DRIVER_LIST_ENTRY DriverEntry;
PCM_KEY_NODE Node;
PCM_KEY_VALUE Value;
ULONG Length;
USHORT NameLength;
HCELL_INDEX ValueCell, TagCell;
PUNICODE_STRING FilePath, RegistryString;
UNICODE_STRING Name;
PULONG ErrorControl;
PWCHAR Buffer;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Allocate a driver node and initialize it */
DriverNode = Hive->Allocate(sizeof(BOOT_DRIVER_NODE), FALSE, TAG_CM);
if (!DriverNode)
return FALSE;
RtlZeroMemory(DriverNode, sizeof(BOOT_DRIVER_NODE));
DriverEntry = &DriverNode->ListEntry;
/* Get the driver cell */
Node = (PCM_KEY_NODE)HvGetCell(Hive, DriverCell);
if (!Node)
goto Failure;
/* Get the name from the cell */
NameLength = (Node->Flags & KEY_COMP_NAME) ?
CmpCompressedNameSize(Node->Name, Node->NameLength) :
Node->NameLength;
if (NameLength < sizeof(WCHAR))
goto Failure;
/* Now allocate the buffer for it and copy the name */
RtlInitEmptyUnicodeString(&DriverNode->Name,
Hive->Allocate(NameLength, FALSE, TAG_CM),
NameLength);
if (!DriverNode->Name.Buffer)
goto Failure;
DriverNode->Name.Length = NameLength;
if (Node->Flags & KEY_COMP_NAME)
{
/* Compressed name */
CmpCopyCompressedName(DriverNode->Name.Buffer,
DriverNode->Name.Length,
Node->Name,
Node->NameLength);
}
else
{
/* Normal name */
RtlCopyMemory(DriverNode->Name.Buffer, Node->Name, Node->NameLength);
}
/* Now find the image path */
RtlInitUnicodeString(&Name, L"ImagePath");
ValueCell = CmpFindValueByName(Hive, Node, &Name);
if (ValueCell == HCELL_NIL)
{
/* Could not find it, so assume the drivers path */
Length = sizeof(L"System32\\Drivers\\") + NameLength + sizeof(L".sys");
/* Allocate the path name */
FilePath = &DriverEntry->FilePath;
RtlInitEmptyUnicodeString(FilePath,
Hive->Allocate(Length, FALSE, TAG_CM),
(USHORT)Length);
if (!FilePath->Buffer)
goto Failure;
/* Write the path name */
if (!NT_SUCCESS(RtlAppendUnicodeToString(FilePath, L"System32\\Drivers\\")) ||
!NT_SUCCESS(RtlAppendUnicodeStringToString(FilePath, &DriverNode->Name)) ||
!NT_SUCCESS(RtlAppendUnicodeToString(FilePath, L".sys")))
{
goto Failure;
}
}
else
{
/* Path name exists, so grab it */
Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
if (!Value)
goto Failure;
if ((Value->Type != REG_SZ) && (Value->Type != REG_EXPAND_SZ))
goto Failure;
Buffer = (PWCHAR)CmpValueToData(Hive, Value, &Length);
if (!Buffer)
goto Failure;
if (IS_NULL_TERMINATED(Buffer, Length))
Length -= sizeof(UNICODE_NULL);
if (Length < sizeof(WCHAR))
goto Failure;
/* Allocate and setup the path name */
FilePath = &DriverEntry->FilePath;
RtlInitEmptyUnicodeString(FilePath,
Hive->Allocate(Length, FALSE, TAG_CM),
(USHORT)Length);
if (!FilePath->Buffer)
goto Failure;
/* Transfer the data */
RtlCopyMemory(FilePath->Buffer, Buffer, Length);
FilePath->Length = (USHORT)Length;
}
/* Now build the registry path */
RegistryString = &DriverEntry->RegistryPath;
Length = RegistryPath->Length + NameLength;
RtlInitEmptyUnicodeString(RegistryString,
Hive->Allocate(Length, FALSE, TAG_CM),
(USHORT)Length);
if (!RegistryString->Buffer)
goto Failure;
/* Add the driver name to it */
if (!NT_SUCCESS(RtlAppendUnicodeStringToString(RegistryString, RegistryPath)) ||
!NT_SUCCESS(RtlAppendUnicodeStringToString(RegistryString, &DriverNode->Name)))
{
goto Failure;
}
/* The entry is done, add it */
InsertHeadList(DriverListHead, &DriverEntry->Link);
/* Now find error control settings */
RtlInitUnicodeString(&Name, L"ErrorControl");
ValueCell = CmpFindValueByName(Hive, Node, &Name);
if (ValueCell == HCELL_NIL)
{
/* Could not find it, so assume default */
DriverNode->ErrorControl = NormalError;
}
else
{
/* Otherwise, read whatever the data says */
Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
if (!Value)
goto Failure;
if (Value->Type != REG_DWORD)
goto Failure;
ErrorControl = (PULONG)CmpValueToData(Hive, Value, &Length);
if (!ErrorControl)
goto Failure;
if (Length < sizeof(ULONG))
goto Failure;
DriverNode->ErrorControl = *ErrorControl;
}
/* Next, get the group cell */
RtlInitUnicodeString(&Name, L"group");
ValueCell = CmpFindValueByName(Hive, Node, &Name);
if (ValueCell == HCELL_NIL)
{
/* Could not find it, so set an empty string */
RtlInitEmptyUnicodeString(&DriverNode->Group, NULL, 0);
}
else
{
/* Found it, read the group value */
Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
if (!Value)
goto Failure;
if (Value->Type != REG_SZ) // REG_EXPAND_SZ not really allowed there.
goto Failure;
/* Copy it into the node */
Buffer = (PWCHAR)CmpValueToData(Hive, Value, &Length);
if (!Buffer)
goto Failure;
if (IS_NULL_TERMINATED(Buffer, Length))
Length -= sizeof(UNICODE_NULL);
DriverNode->Group.Buffer = Buffer;
DriverNode->Group.Length = (USHORT)Length;
DriverNode->Group.MaximumLength = DriverNode->Group.Length;
}
/* Finally, find the tag */
RtlInitUnicodeString(&Name, L"Tag");
TagCell = CmpFindValueByName(Hive, Node, &Name);
if (TagCell == HCELL_NIL)
{
/* No tag, so load last */
DriverNode->Tag = -1;
}
else
{
/* Otherwise, decode it based on tag order */
DriverNode->Tag = CmpFindTagIndex(Hive,
TagCell,
GroupOrderCell,
&DriverNode->Group);
}
CMTRACE(CM_BOOT_DEBUG, "Adding boot driver: '%wZ', '%wZ'\n",
&DriverNode->Name, &DriverEntry->FilePath);
/* All done! */
return TRUE;
Failure:
if (DriverEntry->RegistryPath.Buffer)
{
Hive->Free(DriverEntry->RegistryPath.Buffer,
DriverEntry->RegistryPath.MaximumLength);
}
if (DriverEntry->FilePath.Buffer)
{
Hive->Free(DriverEntry->FilePath.Buffer,
DriverEntry->FilePath.MaximumLength);
}
if (DriverNode->Name.Buffer)
{
Hive->Free(DriverNode->Name.Buffer,
DriverNode->Name.MaximumLength);
}
Hive->Free(DriverNode, sizeof(BOOT_DRIVER_NODE));
return FALSE;
}
/**
* @brief
* Checks whether the specified driver has the expected load type.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] DriverCell
* The registry key's hive cell of the driver, inside the
* "Services" sub-key of the currently selected control set.
*
* @param[in] LoadType
* The load type the driver should match.
*
* @return
* TRUE if the driver's load type matches, FALSE if not.
**/
CODE_SEG("INIT")
static
BOOLEAN
CmpIsLoadType(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX Cell,
_In_ SERVICE_LOAD_TYPE LoadType)
{
PCM_KEY_NODE Node;
PCM_KEY_VALUE Value;
UNICODE_STRING Name = RTL_CONSTANT_STRING(L"Start");
HCELL_INDEX ValueCell;
ULONG Length;
PULONG Data;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Open the start cell */
Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
if (!Node) return FALSE;
ValueCell = CmpFindValueByName(Hive, Node, &Name);
if (ValueCell == HCELL_NIL) return FALSE;
/* Read the start value */
Value = (PCM_KEY_VALUE)HvGetCell(Hive, ValueCell);
if (!Value) return FALSE;
if (Value->Type != REG_DWORD) return FALSE;
Data = (PULONG)CmpValueToData(Hive, Value, &Length);
if (!Data) return FALSE;
if (Length < sizeof(ULONG)) return FALSE;
/* Return if the type matches */
return (*Data == LoadType);
}
/**
* @brief
* Enumerates all drivers within the given control set and load type,
* present in the "Services" sub-key, and inserts them into the driver list.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] ControlSet
* The control set registry key's hive cell.
*
* @param[in] LoadType
* The load type the driver should match.
*
* @param[in] BootFileSystem
* Optional name of the boot file system, for which to insert
* its corresponding driver.
*
* @param[in,out] DriverListHead
* The driver list where to insert the enumerated drivers.
*
* @return
* TRUE if the drivers have been successfully enumerated and inserted,
* FALSE if not.
**/
CODE_SEG("INIT")
BOOLEAN
NTAPI
CmpFindDrivers(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX ControlSet,
_In_ SERVICE_LOAD_TYPE LoadType,
_In_opt_ PCWSTR BootFileSystem,
_Inout_ PLIST_ENTRY DriverListHead)
{
HCELL_INDEX ServicesCell, ControlCell, GroupOrderCell, DriverCell;
HCELL_INDEX SafeBootCell = HCELL_NIL;
ULONG i;
UNICODE_STRING Name;
UNICODE_STRING KeyPath;
PCM_KEY_NODE ControlNode, ServicesNode, Node;
PBOOT_DRIVER_NODE FsNode;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Open the control set key */
ControlNode = (PCM_KEY_NODE)HvGetCell(Hive, ControlSet);
if (!ControlNode) return FALSE;
/* Get services cell */
RtlInitUnicodeString(&Name, L"Services");
ServicesCell = CmpFindSubKeyByName(Hive, ControlNode, &Name);
if (ServicesCell == HCELL_NIL) return FALSE;
/* Open services key */
ServicesNode = (PCM_KEY_NODE)HvGetCell(Hive, ServicesCell);
if (!ServicesNode) return FALSE;
/* Get control cell */
RtlInitUnicodeString(&Name, L"Control");
ControlCell = CmpFindSubKeyByName(Hive, ControlNode, &Name);
if (ControlCell == HCELL_NIL) return FALSE;
/* Get the group order cell and read it */
Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
if (!Node) return FALSE;
RtlInitUnicodeString(&Name, L"GroupOrderList");
GroupOrderCell = CmpFindSubKeyByName(Hive, Node, &Name);
if (GroupOrderCell == HCELL_NIL) return FALSE;
/* Get Safe Boot cell */
if (InitSafeBootMode)
{
/* Open the Safe Boot key */
RtlInitUnicodeString(&Name, L"SafeBoot");
Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
if (!Node) return FALSE;
SafeBootCell = CmpFindSubKeyByName(Hive, Node, &Name);
if (SafeBootCell == HCELL_NIL) return FALSE;
/* Open the correct start key (depending on the mode) */
Node = (PCM_KEY_NODE)HvGetCell(Hive, SafeBootCell);
if (!Node) return FALSE;
switch (InitSafeBootMode)
{
/* NOTE: Assumes MINIMAL (1) and DSREPAIR (3) load same items */
case 1:
case 3: RtlInitUnicodeString(&Name, L"Minimal"); break;
case 2: RtlInitUnicodeString(&Name, L"Network"); break;
default: return FALSE;
}
SafeBootCell = CmpFindSubKeyByName(Hive, Node, &Name);
if (SafeBootCell == HCELL_NIL) return FALSE;
}
/* Build the root registry path */
RtlInitUnicodeString(&KeyPath, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
/* Enumerate each sub-key */
i = 0;
DriverCell = CmpFindSubKeyByNumber(Hive, ServicesNode, i);
while (DriverCell != HCELL_NIL)
{
/* Make sure it's a driver of this start type AND is "safe" to load */
if (CmpIsLoadType(Hive, DriverCell, LoadType) &&
CmpIsSafe(Hive, SafeBootCell, DriverCell))
{
/* Add it to the list */
if (!CmpAddDriverToList(Hive,
DriverCell,
GroupOrderCell,
&KeyPath,
DriverListHead))
{
CMTRACE(CM_BOOT_DEBUG, " Failed to add boot driver\n");
}
}
/* Go to the next sub-key */
DriverCell = CmpFindSubKeyByNumber(Hive, ServicesNode, ++i);
}
/* Check if we have a boot file system */
if (BootFileSystem)
{
/* Find it */
RtlInitUnicodeString(&Name, BootFileSystem);
DriverCell = CmpFindSubKeyByName(Hive, ServicesNode, &Name);
if (DriverCell != HCELL_NIL)
{
CMTRACE(CM_BOOT_DEBUG, "Adding Boot FileSystem '%S'\n",
BootFileSystem);
/* Always add it to the list */
if (!CmpAddDriverToList(Hive,
DriverCell,
GroupOrderCell,
&KeyPath,
DriverListHead))
{
CMTRACE(CM_BOOT_DEBUG, " Failed to add boot driver\n");
}
else
{
/* Mark it as critical so it always loads */
FsNode = CONTAINING_RECORD(DriverListHead->Flink,
BOOT_DRIVER_NODE,
ListEntry.Link);
FsNode->ErrorControl = SERVICE_ERROR_CRITICAL;
}
}
}
/* We're done! */
return TRUE;
}
/**
* @brief
* Performs the driver list sorting, according to the ordering list.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] ControlSet
* The control set registry key's hive cell.
*
* @param[in,out] DriverListHead
* The driver list to sort.
*
* @return
* TRUE if sorting has been successfully done, FALSE if not.
**/
CODE_SEG("INIT")
static
BOOLEAN
CmpDoSort(
_Inout_ PLIST_ENTRY DriverListHead,
_In_ PCUNICODE_STRING OrderList)
{
PWCHAR Current, End = NULL;
UNICODE_STRING GroupName;
PLIST_ENTRY NextEntry;
PBOOT_DRIVER_NODE CurrentNode;
/* We're going from end to start, so get to the last group and keep going */
Current = &OrderList->Buffer[OrderList->Length / sizeof(WCHAR)];
while (Current > OrderList->Buffer)
{
/* Scan the current string */
do
{
if (*Current == UNICODE_NULL) End = Current;
} while ((*(--Current - 1) != UNICODE_NULL) && (Current != OrderList->Buffer));
/* This is our cleaned up string for this specific group */
ASSERT(End != NULL);
GroupName.Length = (USHORT)(End - Current) * sizeof(WCHAR);
GroupName.MaximumLength = GroupName.Length;
GroupName.Buffer = Current;
/* Now loop the driver list */
NextEntry = DriverListHead->Flink;
while (NextEntry != DriverListHead)
{
/* Get this node */
CurrentNode = CONTAINING_RECORD(NextEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
/* Get the next entry now since we'll do a relink */
NextEntry = NextEntry->Flink;
/* Is there a group name and does it match the current group? */
if (CurrentNode->Group.Buffer &&
RtlEqualUnicodeString(&GroupName, &CurrentNode->Group, TRUE))
{
/* Remove from this location and re-link in the new one */
RemoveEntryList(&CurrentNode->ListEntry.Link);
InsertHeadList(DriverListHead, &CurrentNode->ListEntry.Link);
}
}
/* Move on */
--Current;
}
/* All done */
return TRUE;
}
/**
* @brief
* Sorts the driver list, according to the drivers' group load ordering.
*
* @param[in] Hive
* The SYSTEM hive.
*
* @param[in] ControlSet
* The control set registry key's hive cell.
*
* @param[in,out] DriverListHead
* The driver list to sort.
*
* @return
* TRUE if sorting has been successfully done, FALSE if not.
**/
CODE_SEG("INIT")
BOOLEAN
NTAPI
CmpSortDriverList(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX ControlSet,
_Inout_ PLIST_ENTRY DriverListHead)
{
PCM_KEY_NODE Node;
PCM_KEY_VALUE ListValue;
HCELL_INDEX ControlCell, GroupOrder, ListCell;
UNICODE_STRING Name, OrderList;
ULONG Length;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Open the control key */
Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlSet);
if (!Node) return FALSE;
RtlInitUnicodeString(&Name, L"Control");
ControlCell = CmpFindSubKeyByName(Hive, Node, &Name);
if (ControlCell == HCELL_NIL) return FALSE;
/* Open the service group order */
Node = (PCM_KEY_NODE)HvGetCell(Hive, ControlCell);
if (!Node) return FALSE;
RtlInitUnicodeString(&Name, L"ServiceGroupOrder");
GroupOrder = CmpFindSubKeyByName(Hive, Node, &Name);
if (GroupOrder == HCELL_NIL) return FALSE;
/* Open the list key */
Node = (PCM_KEY_NODE)HvGetCell(Hive, GroupOrder);
if (!Node) return FALSE;
RtlInitUnicodeString(&Name, L"list");
ListCell = CmpFindValueByName(Hive, Node, &Name);
if (ListCell == HCELL_NIL) return FALSE;
/* Read the actual list */
ListValue = (PCM_KEY_VALUE)HvGetCell(Hive, ListCell);
if (!ListValue) return FALSE;
if (ListValue->Type != REG_MULTI_SZ) return FALSE;
/* Copy it into a buffer */
OrderList.Buffer = (PWCHAR)CmpValueToData(Hive, ListValue, &Length);
if (!OrderList.Buffer) return FALSE;
if (!IS_NULL_TERMINATED(OrderList.Buffer, Length)) return FALSE;
OrderList.Length = (USHORT)Length - sizeof(UNICODE_NULL);
OrderList.MaximumLength = OrderList.Length;
/* And start the sort algorithm */
return CmpDoSort(DriverListHead, &OrderList);
}
CODE_SEG("INIT")
static
BOOLEAN
CmpOrderGroup(
_In_ PBOOT_DRIVER_NODE StartNode,
_In_ PBOOT_DRIVER_NODE EndNode)
{
PBOOT_DRIVER_NODE CurrentNode, PreviousNode;
PLIST_ENTRY ListEntry;
/* Base case, nothing to do */
if (StartNode == EndNode) return TRUE;
/* Loop the nodes */
CurrentNode = StartNode;
do
{
/* Save this as the previous node */
PreviousNode = CurrentNode;
/* And move to the next one */
ListEntry = CurrentNode->ListEntry.Link.Flink;
CurrentNode = CONTAINING_RECORD(ListEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
/* Check if the previous driver had a bigger tag */
if (PreviousNode->Tag > CurrentNode->Tag)
{
/* Check if we need to update the tail */
if (CurrentNode == EndNode)
{
/* Update the tail */
ListEntry = CurrentNode->ListEntry.Link.Blink;
EndNode = CONTAINING_RECORD(ListEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
}
/* Remove this driver since we need to move it */
RemoveEntryList(&CurrentNode->ListEntry.Link);
/* Keep looping until we find a driver with a lower tag than ours */
while ((PreviousNode->Tag > CurrentNode->Tag) && (PreviousNode != StartNode))
{
/* We'll be re-inserted at this spot */
ListEntry = PreviousNode->ListEntry.Link.Blink;
PreviousNode = CONTAINING_RECORD(ListEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
}
/* Do the insert in the new location */
InsertTailList(&PreviousNode->ListEntry.Link, &CurrentNode->ListEntry.Link);
/* Update the head, if needed */
if (PreviousNode == StartNode) StartNode = CurrentNode;
}
} while (CurrentNode != EndNode);
/* All done */
return TRUE;
}
/**
* @brief
* Removes potential circular dependencies (cycles) and sorts the driver list.
*
* @param[in,out] DriverListHead
* The driver list to sort.
*
* @return
* Always TRUE.
**/
CODE_SEG("INIT")
BOOLEAN
NTAPI
CmpResolveDriverDependencies(
_Inout_ PLIST_ENTRY DriverListHead)
{
PLIST_ENTRY NextEntry;
PBOOT_DRIVER_NODE StartNode, EndNode, CurrentNode;
/* Loop the list */
NextEntry = DriverListHead->Flink;
while (NextEntry != DriverListHead)
{
/* Find the first entry */
StartNode = CONTAINING_RECORD(NextEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
do
{
/* Find the last entry */
EndNode = CONTAINING_RECORD(NextEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
/* Get the next entry */
NextEntry = NextEntry->Flink;
CurrentNode = CONTAINING_RECORD(NextEntry,
BOOT_DRIVER_NODE,
ListEntry.Link);
/* If the next entry is back to the top, break out */
if (NextEntry == DriverListHead) break;
/* Otherwise, check if this entry is equal */
if (!RtlEqualUnicodeString(&StartNode->Group,
&CurrentNode->Group,
TRUE))
{
/* It is, so we've detected a cycle, break out */
break;
}
} while (NextEntry != DriverListHead);
/* Now we have the correct start and end pointers, so do the sort */
CmpOrderGroup(StartNode, EndNode);
}
/* We're done */
return TRUE;
}
CODE_SEG("INIT")
static
BOOLEAN
CmpIsSafe(
_In_ PHHIVE Hive,
_In_ HCELL_INDEX SafeBootCell,
_In_ HCELL_INDEX DriverCell)
{
PCM_KEY_NODE SafeBootNode;
PCM_KEY_NODE DriverNode;
PCM_KEY_VALUE KeyValue;
HCELL_INDEX CellIndex;
ULONG Length;
UNICODE_STRING Name;
PWCHAR Buffer;
/* Sanity check: We shouldn't need to release any acquired cells */
ASSERT(Hive->ReleaseCellRoutine == NULL);
/* Driver key node (mandatory) */
ASSERT(DriverCell != HCELL_NIL);
DriverNode = (PCM_KEY_NODE)HvGetCell(Hive, DriverCell);
if (!DriverNode) return FALSE;
/* Safe boot key node (optional but return TRUE if not present) */
if (SafeBootCell == HCELL_NIL) return TRUE;
SafeBootNode = (PCM_KEY_NODE)HvGetCell(Hive, SafeBootCell);
if (!SafeBootNode) return FALSE;
/* Search by the name from the group */
RtlInitUnicodeString(&Name, L"Group");
CellIndex = CmpFindValueByName(Hive, DriverNode, &Name);
if (CellIndex != HCELL_NIL)
{
KeyValue = (PCM_KEY_VALUE)HvGetCell(Hive, CellIndex);
if (!KeyValue) return FALSE;
if (KeyValue->Type == REG_SZ) // REG_EXPAND_SZ not really allowed there.
{
/* Compose the search 'key' */
Buffer = (PWCHAR)CmpValueToData(Hive, KeyValue, &Length);
if (!Buffer)
return FALSE;
if (IS_NULL_TERMINATED(Buffer, Length))
Length -= sizeof(UNICODE_NULL);
Name.Buffer = Buffer;
Name.Length = (USHORT)Length;
Name.MaximumLength = Name.Length;
/* Search for corresponding key in the Safe Boot key */
CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
if (CellIndex != HCELL_NIL) return TRUE;
}
}
/* Group has not been found - find driver name */
Length = (DriverNode->Flags & KEY_COMP_NAME) ?
CmpCompressedNameSize(DriverNode->Name, DriverNode->NameLength) :
DriverNode->NameLength;
if (Length < sizeof(WCHAR))
return FALSE;
/* Now allocate the buffer for it and copy the name */
RtlInitEmptyUnicodeString(&Name,
Hive->Allocate(Length, FALSE, TAG_CM),
(USHORT)Length);
if (!Name.Buffer)
return FALSE;
Name.Length = (USHORT)Length;
if (DriverNode->Flags & KEY_COMP_NAME)
{
/* Compressed name */
CmpCopyCompressedName(Name.Buffer,
Name.Length,
DriverNode->Name,
DriverNode->NameLength);
}
else
{
/* Normal name */
RtlCopyMemory(Name.Buffer, DriverNode->Name, DriverNode->NameLength);
}
CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
Hive->Free(Name.Buffer, Name.MaximumLength);
if (CellIndex != HCELL_NIL) return TRUE;
/* Not group or driver name - search by image name */
RtlInitUnicodeString(&Name, L"ImagePath");
CellIndex = CmpFindValueByName(Hive, DriverNode, &Name);
if (CellIndex != HCELL_NIL)
{
KeyValue = (PCM_KEY_VALUE)HvGetCell(Hive, CellIndex);
if (!KeyValue) return FALSE;
if ((KeyValue->Type == REG_SZ) || (KeyValue->Type == REG_EXPAND_SZ))
{
/* Compose the search 'key' */
Buffer = (PWCHAR)CmpValueToData(Hive, KeyValue, &Length);
if (!Buffer) return FALSE;
if (Length < sizeof(WCHAR)) return FALSE;
/* Get the base image file name */
// FIXME: wcsrchr() may fail if Buffer is *not* NULL-terminated!
Name.Buffer = wcsrchr(Buffer, OBJ_NAME_PATH_SEPARATOR);
if (!Name.Buffer) return FALSE;
++Name.Buffer;
/* Length of the base name must be >=1 WCHAR */
if (((ULONG_PTR)Name.Buffer - (ULONG_PTR)Buffer) >= Length)
return FALSE;
Length -= ((ULONG_PTR)Name.Buffer - (ULONG_PTR)Buffer);
if (IS_NULL_TERMINATED(Name.Buffer, Length))
Length -= sizeof(UNICODE_NULL);
if (Length < sizeof(WCHAR)) return FALSE;
Name.Length = (USHORT)Length;
Name.MaximumLength = Name.Length;
/* Search for corresponding key in the Safe Boot key */
CellIndex = CmpFindSubKeyByName(Hive, SafeBootNode, &Name);
if (CellIndex != HCELL_NIL) return TRUE;
}
}
/* Nothing found - nothing else to search */
return FALSE;
}
/**
* @brief
* Empties the driver list and frees all allocated driver nodes in it.
*
* @param[in] Hive
* The SYSTEM hive (used only for the Hive->Free() memory deallocator).
*
* @param[in,out] DriverListHead
* The driver list to free.
*
* @return None
**/
CODE_SEG("INIT")
VOID
NTAPI
CmpFreeDriverList(
_In_ PHHIVE Hive,
_Inout_ PLIST_ENTRY DriverListHead)
{
PLIST_ENTRY Entry;
PBOOT_DRIVER_NODE DriverNode;
/* Loop through the list and remove each driver node */
while (!IsListEmpty(DriverListHead))
{
/* Get the driver node */
Entry = RemoveHeadList(DriverListHead);
DriverNode = CONTAINING_RECORD(Entry,
BOOT_DRIVER_NODE,
ListEntry.Link);
/* Free any allocated string buffers, then the node */
if (DriverNode->ListEntry.RegistryPath.Buffer)
{
Hive->Free(DriverNode->ListEntry.RegistryPath.Buffer,
DriverNode->ListEntry.RegistryPath.MaximumLength);
}
if (DriverNode->ListEntry.FilePath.Buffer)
{
Hive->Free(DriverNode->ListEntry.FilePath.Buffer,
DriverNode->ListEntry.FilePath.MaximumLength);
}
if (DriverNode->Name.Buffer)
{
Hive->Free(DriverNode->Name.Buffer,
DriverNode->Name.MaximumLength);
}
Hive->Free(DriverNode, sizeof(BOOT_DRIVER_NODE));
}
}
/* EOF */