You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2453 lines
68 KiB
2453 lines
68 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
BOOTCFG.CXX
|
|
|
|
Abstract:
|
|
|
|
Contains functions used for managing the system control sets in the
|
|
system portion of the registry.
|
|
|
|
ScCheckLastKnownGood
|
|
ScRevertToLastKnownGood
|
|
NotifyBootConfigStatus
|
|
|
|
ScGetTopKeys
|
|
ScGetCtrlSetIds
|
|
ScDeleteRegTree
|
|
ScBuildCtrlSetName
|
|
ScGetCtrlSetHandle
|
|
ScDeleteTree
|
|
ScCopyKeyRecursive
|
|
ScCopyKeyValues
|
|
ScDeleteRegServiceEntry
|
|
ScGatherOrphanIds
|
|
ScDeleteCtrlSetOrphans
|
|
ScMatchInArray
|
|
ScStartCtrlSetCleanupThread
|
|
ScCleanupThread
|
|
ScRunAcceptBootPgm
|
|
ScAcceptTheBoot
|
|
|
|
Author:
|
|
|
|
Dan Lafferty (danl) 19-Apr-1992
|
|
|
|
Environment:
|
|
|
|
User Mode - Win32
|
|
|
|
Notes:
|
|
|
|
|
|
Revision History:
|
|
|
|
24-Aug-1998 Elliot Shmukler (t-ellios)
|
|
Most of the LKG related work has now been moved into the Kernel.
|
|
The tree copies & clone deletions formerly performed by functions
|
|
in this file has now been replaced by calls to NtInitializeRegistry.
|
|
|
|
28-Jun-1995 AnirudhS
|
|
SetupInProgress: This function is now called from more than one place.
|
|
Cache the return value so we only examine the registry once.
|
|
|
|
04-Feb-1994 Danl
|
|
RevertToLastKnownGood: If the boot has been accepted, then we won't
|
|
allow a revert.
|
|
|
|
15-Jun-1993 Danl
|
|
Ignore LastKnownGood adjustments if setup is still running.
|
|
Use the SystemSetupInProgress value in the registry to determine
|
|
if is running.
|
|
|
|
01-Apr-1993 Danl
|
|
Add ability to take ownership if we cannot open one of the keys due
|
|
to an access denied error.
|
|
|
|
08-Feb-1993 Danl
|
|
Changed the clearing of the LKG_ENV_VAR so that it is done whenever
|
|
we are booting LKG. Reguardless of whether or not it is the last
|
|
boot. Prior to this, it was only cleared when a revert occured, and
|
|
not on the first boot.
|
|
|
|
04-Feb-1993 Danl
|
|
Use NtUnloadKey to delete the clone tree. The clone tree is now
|
|
in a separate hive. So this is allowed.
|
|
|
|
18-Jan-1993 Danl
|
|
Make use of the LastKnownGood Environment Variable. Now we do
|
|
not alter the default control set when we need to revert. We
|
|
just set the Environment Variable to True, and reboot. Phase2
|
|
and ScCheckLastKnownGood do the right thing.
|
|
|
|
19-Apr-1992 danl
|
|
Created
|
|
|
|
--*/
|
|
|
|
//
|
|
// INCLUDES
|
|
//
|
|
#include "precomp.hxx"
|
|
#include <stdlib.h> // ultoa
|
|
#include "scsec.h" // ScAccessValidate()
|
|
#include "bootcfg.h" // ScRegDeleteTree()
|
|
#include "scconfig.h" // ScOpenServicesKey()
|
|
#include <svcslib.h> // SetupInProgress()
|
|
#include <ntsetup.h> // REGSTR_VALUE_OOBEINPROGRESS
|
|
|
|
#include <bootstatus.h>
|
|
|
|
//
|
|
// DEFINES
|
|
//
|
|
|
|
#define SYSTEM_KEY L"system"
|
|
#define SELECT_KEY L"select"
|
|
#define SERVICES_KEY L"System\\CurrentControlSet\\Services"
|
|
#define ACCEPT_BOOT_KEY L"System\\CurrentControlSet\\Control\\BootVerificationProgram"
|
|
#define SETUP_PROG_KEY L"Setup"
|
|
|
|
#define CURRENT_VALUE_NAME L"Current"
|
|
#define DEFAULT_VALUE_NAME L"Default"
|
|
#define LKG_VALUE_NAME L"LastKnownGood"
|
|
#define FAILED_VALUE_NAME L"Failed"
|
|
#define IMAGE_PATH_NAME L"ImagePath"
|
|
#define SETUP_PROG_VALUE_NAME L"SystemSetupInProgress"
|
|
|
|
#define CTRL_SET_NAME_TEMPLATE L"ControlSet000"
|
|
#define CTRL_SET_NAME_CHAR_COUNT 13
|
|
#define CTRL_SET_NAME_NUM_OFFSET 10
|
|
#define CTRL_SET_NAME_BYTES ((CTRL_SET_CHAR_COUNT+1) * sizeof(WCHAR))
|
|
|
|
#define CLONE_SECURITY_INFORMATION (OWNER_SECURITY_INFORMATION | \
|
|
GROUP_SECURITY_INFORMATION | \
|
|
DACL_SECURITY_INFORMATION | \
|
|
SACL_SECURITY_INFORMATION)
|
|
|
|
//
|
|
// STANDARD access is obtained for the system and select keys.
|
|
// We read and write to these keys.
|
|
//
|
|
#define SC_STANDARD_KEY_ACCESS KEY_READ | \
|
|
READ_CONTROL | \
|
|
WRITE_OWNER | \
|
|
KEY_WRITE
|
|
|
|
//
|
|
// CLONE access is obtained for the top level clone key
|
|
// We must be able to copy and delete clone trees.
|
|
//
|
|
|
|
#define SC_CLONE_KEY_ACCESS KEY_READ | \
|
|
READ_CONTROL | \
|
|
WRITE_OWNER | \
|
|
DELETE | \
|
|
ACCESS_SYSTEM_SECURITY
|
|
|
|
//
|
|
// CONTROL_SET access is obtained for the top level control sets.
|
|
// We must be able to copy and delete control sets.
|
|
// NOTE: SE_SECURITY_PRIVILEGE is required to get ACCESS_SYSTEM_SECURITY.
|
|
//
|
|
#define SC_CONTROL_SET_KEY_ACCESS KEY_READ | \
|
|
KEY_WRITE | \
|
|
DELETE | \
|
|
READ_CONTROL | \
|
|
WRITE_OWNER | \
|
|
ACCESS_SYSTEM_SECURITY
|
|
|
|
//
|
|
// COPY access is obtained for each subkey in a control set as it is being
|
|
// copied.
|
|
// NOTE: SE_SECURITY_PRIVILEGE is required to get ACCESS_SYSTEM_SECURITY.
|
|
//
|
|
#define SC_COPY_KEY_ACCESS KEY_READ | \
|
|
READ_CONTROL | \
|
|
ACCESS_SYSTEM_SECURITY
|
|
//
|
|
// DELETE access is obtained for each subkey in a control set that is being
|
|
// deleted.
|
|
//
|
|
#define SC_DELETE_KEY_ACCESS DELETE | \
|
|
KEY_READ
|
|
|
|
//
|
|
// CREATE access is the access used for all keys created by this
|
|
// process.
|
|
//
|
|
|
|
#define SC_CREATE_KEY_ACCESS KEY_WRITE | \
|
|
WRITE_OWNER | \
|
|
WRITE_DAC | \
|
|
ACCESS_SYSTEM_SECURITY
|
|
|
|
//
|
|
// Control Set IDs are stored in an array of DWORDs. The array has the
|
|
// following offsets for each ID:
|
|
//
|
|
|
|
#define CURRENT_ID 0
|
|
#define DEFAULT_ID 1
|
|
#define LKG_ID 2
|
|
#define FAILED_ID 3
|
|
|
|
#define NUM_IDS 4
|
|
|
|
|
|
//
|
|
// Macros
|
|
//
|
|
|
|
#define SET_LKG_ENV_VAR(pString) \
|
|
{ \
|
|
UNICODE_STRING Name,Value; \
|
|
\
|
|
RtlInitUnicodeString(&Name, L"LastKnownGood"); \
|
|
RtlInitUnicodeString(&Value,pString); \
|
|
\
|
|
status = RtlNtStatusToDosError(NtSetSystemEnvironmentValue(&Name,&Value)); \
|
|
}
|
|
|
|
//
|
|
// GLOBALS
|
|
//
|
|
|
|
//
|
|
// This flag is set when ScCheckLastKnownGood is called. It is later
|
|
// checked when either ScRevertToLastKnownGood or NotifyBootConfigStatus
|
|
// is called. TRUE indicates that we know we are booting LastKnownGood.
|
|
//
|
|
|
|
DWORD ScGlobalLastKnownGood;
|
|
BOOL ScGlobalBootAccepted = FALSE;
|
|
|
|
CRITICAL_SECTION ScBootConfigCriticalSection;
|
|
|
|
LPDWORD ScGlobalOrphanIds = NULL;
|
|
|
|
//
|
|
// LOCAL FUNCTION PROTOTYPES
|
|
//
|
|
|
|
DWORD
|
|
ScGetTopKeys(
|
|
PHKEY SystemKey,
|
|
PHKEY SelectKey
|
|
);
|
|
|
|
DWORD
|
|
ScGetCtrlSetIds(
|
|
HKEY SelectKey,
|
|
LPDWORD IdArray
|
|
);
|
|
|
|
BOOL
|
|
ScBuildCtrlSetName(
|
|
LPWSTR ControlSetName,
|
|
DWORD ControlId
|
|
);
|
|
|
|
HKEY
|
|
ScGetCtrlSetHandle(
|
|
HKEY SystemKey,
|
|
DWORD ControlId,
|
|
LPWSTR ControlSetName
|
|
);
|
|
|
|
VOID
|
|
ScDeleteTree(
|
|
IN HKEY KeyHandle
|
|
);
|
|
|
|
VOID
|
|
ScCopyKeyRecursive(
|
|
HKEY ParentKey,
|
|
PHKEY DestKeyPtr,
|
|
HKEY SourceKey,
|
|
LPWSTR DestKeyName
|
|
);
|
|
|
|
VOID
|
|
ScCopyKeyValues(
|
|
HKEY DestKey,
|
|
HKEY SourceKey,
|
|
DWORD NumberOfValues,
|
|
DWORD MaxValueNameLength,
|
|
DWORD MaxValueDataLength
|
|
);
|
|
|
|
VOID
|
|
ScDeleteRegTree(
|
|
HKEY ParentKey,
|
|
HKEY KeyToDelete,
|
|
LPWSTR NameOfKeyToDelete
|
|
);
|
|
|
|
VOID
|
|
ScGatherOrphanIds(
|
|
HKEY SystemKey,
|
|
LPDWORD *OrphanIdPtr,
|
|
LPDWORD idArray
|
|
);
|
|
|
|
BOOL
|
|
ScMatchInArray(
|
|
DWORD Value,
|
|
LPDWORD IdArray
|
|
);
|
|
|
|
VOID
|
|
ScStartCtrlSetCleanupThread();
|
|
|
|
DWORD
|
|
ScCleanupThread();
|
|
|
|
DWORD
|
|
ScAcceptTheBoot(
|
|
VOID
|
|
);
|
|
|
|
DWORD
|
|
ScGetNewCtrlSetId(
|
|
LPDWORD IdArray,
|
|
LPDWORD NewIdPtr
|
|
);
|
|
|
|
|
|
BOOL
|
|
ScCheckLastKnownGood(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called early in the service controller initialization.
|
|
Its purpose is to protect the LastKnownGood control set. If this
|
|
function finds that the control set that we are booting is the
|
|
LastKnownGood control set, it will save the clone tree to a new
|
|
control set and make this LastKnownGood. The clone tree in this case
|
|
is an unchanged version of LKG. The Current control is not! Current
|
|
may have been modified by drivers that were started before the service
|
|
controller was started.
|
|
|
|
Phase 2 of the boot procedure is always responsible for actually
|
|
doing the revert to LastKnownGood. We determine that we have reverted
|
|
by noting that Current and LKG will be the same control sets, and
|
|
Default will be different. If Default is the same (all three control
|
|
sets are the same), then it is the very first boot, and we don't consider
|
|
it a failure case. If Phase 2 is causing the boot from LastKnownGood,
|
|
then we want to set
|
|
Failed to Default and
|
|
Current to LKG and
|
|
Set the LKG environment variable to FALSE.
|
|
The assumption here is that Phase2 is using LastKnownGood because
|
|
The Default Control Set was not acceptable.
|
|
|
|
Arguments:
|
|
|
|
TRUE - If all the necessary operations were successful.
|
|
|
|
FALSE - If any of the control set manipulation could not be completed
|
|
successfully.
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
BOOL retStat;
|
|
HKEY systemKey=0;
|
|
HKEY selectKey=0;
|
|
HKEY failedKey=0;
|
|
HKEY newKey=0;
|
|
|
|
DWORD idArray[NUM_IDS];
|
|
WCHAR failedKeyName[CTRL_SET_NAME_CHAR_COUNT+1];
|
|
|
|
DWORD savedLkgId;
|
|
DWORD newId;
|
|
ULONG privileges[5];
|
|
|
|
//
|
|
// Initialize the Critical section that will synchronize access to
|
|
// these routines. The service controller could call
|
|
// ScRevertToLastKnownGood at the same time that someone calls
|
|
// NotifyBootConfigStatus(). This could cause the control set pointers
|
|
// to get corrupted. So access to these functions is restricted by
|
|
// a critical section. It is initialized here because this function
|
|
// must be called prior to starting any services, or starting the
|
|
// RPC server. Therefore we can't get asynchronous calls to these
|
|
// routines at this time.
|
|
//
|
|
InitializeCriticalSection(&ScBootConfigCriticalSection);
|
|
|
|
//
|
|
// This thread gets SE_SECURITY_PRIVILEGE for copying security
|
|
// descriptors and deleting keys.
|
|
//
|
|
privileges[0] = SE_BACKUP_PRIVILEGE;
|
|
privileges[1] = SE_RESTORE_PRIVILEGE;
|
|
privileges[2] = SE_SECURITY_PRIVILEGE;
|
|
privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE;
|
|
privileges[4] = SE_SYSTEM_ENVIRONMENT_PRIVILEGE;
|
|
|
|
status = ScGetPrivilege( 5, privileges);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScCheckLastKnownGood: ScGetPrivilege Failed %d\n",
|
|
status);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
//
|
|
// Get the System, Select, and Clone Keys
|
|
//
|
|
status = ScGetTopKeys(&systemKey, &selectKey);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetTopKeys failed\n");
|
|
retStat = FALSE;
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Get the ControlSetIds stored in the \system\select key.
|
|
//
|
|
|
|
status = ScGetCtrlSetIds(
|
|
selectKey,
|
|
idArray);
|
|
|
|
if (status != NO_ERROR) {
|
|
|
|
ScRegCloseKey(systemKey);
|
|
ScRegCloseKey(selectKey);
|
|
SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetCtrlSetIds Failed\n");
|
|
retStat = FALSE;
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Scan for Orphaned Control Sets.
|
|
// This is required prior to calling ScGetNewCtrlSetId (which
|
|
// avoids the orphaned numbers).
|
|
//
|
|
ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray);
|
|
|
|
if ((SetupInProgress(systemKey, NULL)) ||
|
|
(idArray[CURRENT_ID] != idArray[LKG_ID])) {
|
|
//
|
|
// We are not booting from LastKnownGood, so we don't do
|
|
// anything except make sure the LKG_FLAG not set.
|
|
//
|
|
|
|
ScGlobalLastKnownGood = 0;
|
|
|
|
ScRegCloseKey(systemKey);
|
|
ScRegCloseKey(selectKey);
|
|
retStat = TRUE;
|
|
goto CleanExit;
|
|
}
|
|
else {
|
|
//
|
|
// We Must be booting the LastKnownGood configuration.
|
|
// Put LkgControlSetId into SavedLkgControlSetId.
|
|
//
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood, We are booting LKG\n");
|
|
savedLkgId = idArray[LKG_ID];
|
|
|
|
//
|
|
// Set the LKG environment variable to FALSE - so Phase 2
|
|
// does not automatically revert again.
|
|
//
|
|
SET_LKG_ENV_VAR(L"False");
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScCheckLastKnownGood: Couldn't clear LKG "
|
|
"environment variable %d\n",status);
|
|
}
|
|
|
|
//
|
|
// Copy the Clone tree into a non-volatile node (new ControlSetId).
|
|
//
|
|
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood, Copy Clone to new ctrl set\n");
|
|
|
|
status = ScGetNewCtrlSetId( idArray, &newId);
|
|
if(status == NO_ERROR)
|
|
{
|
|
status = RtlNtStatusToDosError(NtInitializeRegistry(REG_INIT_BOOT_ACCEPTED_BASE +
|
|
(USHORT)newId));
|
|
}
|
|
|
|
if (status != NO_ERROR) {
|
|
|
|
SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetNewCtrlSetId Failed\n");
|
|
SC_LOG0(ERROR,"SERIOUS ERROR - Unable to copy control set that "
|
|
"is to be saved as LastKnownGood\n");
|
|
}
|
|
else {
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood, Copy Clone is complete\n");
|
|
|
|
//
|
|
// Set LkgControlSetId to this new ControlSetId.
|
|
//
|
|
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood, Set LKG to this new ctrl set\n");
|
|
|
|
idArray[LKG_ID] = newId;
|
|
status = ScRegSetValueExW(
|
|
selectKey, // hKey
|
|
LKG_VALUE_NAME, // lpValueName
|
|
0, // dwValueTitle (OPTIONAL)
|
|
REG_DWORD, // dwType
|
|
(LPBYTE)&(idArray[LKG_ID]), // lpData
|
|
sizeof(DWORD)); // cbData
|
|
|
|
ScRegCloseKey(newKey);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (lkgValue) "
|
|
"failed %d\n",status);
|
|
SC_LOG1(ERROR,"Semi-SERIOUS ERROR - Unable to Set Select Value "
|
|
"For LastKnownGood.\nThe new ControlSet%d should "
|
|
"be LKG\n",newId);
|
|
}
|
|
else {
|
|
|
|
|
|
//
|
|
// Since we already generated a LKG, we don't want to allow the
|
|
// user or the boot verfication program to try to go through the
|
|
// motions of generating it again. So we set the global flag that
|
|
// indicates that the boot was accepted as LKG.
|
|
//
|
|
ScGlobalBootAccepted = TRUE;
|
|
|
|
//
|
|
// Set Global LKG_FLAG to indicate that we are running LKG, and
|
|
// whether or not we are here because we reverted. The only
|
|
// reason we would be here without reverting is because it is the
|
|
// very first boot. But in the very first boot, FAILED is 0.
|
|
//
|
|
|
|
ScGlobalLastKnownGood |= RUNNING_LKG;
|
|
if (idArray[FAILED_ID] != 0) {
|
|
ScGlobalLastKnownGood |= REVERTED_TO_LKG;
|
|
}
|
|
|
|
} //endif - Set LKG Id to NetCtrlSet ID;
|
|
|
|
} //endif - MakeNewCtrlSet == TRUE;
|
|
|
|
//
|
|
// If the DefaultControlSetId is the same as the original
|
|
// LkgControlSetId, then Phase2 of the boot must have reverted
|
|
// to Last Known Good.
|
|
//
|
|
if (idArray[DEFAULT_ID] != savedLkgId) {
|
|
//
|
|
// We are booting LastKnownGood because it was set that way
|
|
// by Phase2 of the boot. In this case, we want to set the
|
|
// FailedControlSetId to the DefaultControlSetId. Then we
|
|
// want to set the DefaultControlSetId to the CurrentControlSetId.
|
|
//
|
|
// NOTE: On the very first boot, we don't go through this path
|
|
// because current=default=lkg.
|
|
//
|
|
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood, Phase 2 caused LKG"
|
|
" so we delete the failed tree and put\n"
|
|
" Default->Failed\n"
|
|
" Lkg -> Default\n");
|
|
|
|
if (idArray[FAILED_ID] != 0) {
|
|
SC_LOG0(TRACE,"ScCheckLastKnownGood: Deleting Old Failed Tree\n");
|
|
failedKey = ScGetCtrlSetHandle(
|
|
systemKey,
|
|
idArray[FAILED_ID],
|
|
failedKeyName);
|
|
|
|
ScDeleteRegTree(systemKey, failedKey, failedKeyName);
|
|
}
|
|
|
|
//
|
|
// Put the DefaultId into the Failed value.
|
|
//
|
|
idArray[FAILED_ID] = idArray[DEFAULT_ID];
|
|
status = ScRegSetValueExW(
|
|
selectKey, // hKey
|
|
FAILED_VALUE_NAME, // lpValueName
|
|
0, // dwValueTitle (OPTIONAL)
|
|
REG_DWORD, // dwType
|
|
(LPBYTE)&(idArray[FAILED_ID]), // lpData
|
|
sizeof(DWORD)); // cbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (failedValue) failed %d\n",
|
|
status);
|
|
}
|
|
|
|
//
|
|
// Put the CurrentId into the Default Value.
|
|
//
|
|
idArray[DEFAULT_ID] = idArray[CURRENT_ID];
|
|
status = ScRegSetValueExW(
|
|
selectKey, // hKey
|
|
DEFAULT_VALUE_NAME, // lpValueName
|
|
0, // dwValueTitle (OPTIONAL)
|
|
REG_DWORD, // dwType
|
|
(LPBYTE)&(idArray[CURRENT_ID]), // lpData
|
|
sizeof(DWORD)); // cbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (DefaultValue) failed %d\n",
|
|
status);
|
|
ScRegCloseKey(selectKey);
|
|
ScRegCloseKey(systemKey);
|
|
retStat = FALSE;
|
|
goto CleanExit;
|
|
}
|
|
}
|
|
|
|
ScRegCloseKey(systemKey);
|
|
ScRegCloseKey(selectKey);
|
|
}
|
|
|
|
retStat = TRUE;
|
|
|
|
CleanExit:
|
|
|
|
//
|
|
// If the code above was successful then mark the boot as having been
|
|
// successful.
|
|
//
|
|
|
|
if(retStat) {
|
|
|
|
HANDLE bootStatusData;
|
|
BOOL b = TRUE;
|
|
|
|
status = RtlLockBootStatusData(&bootStatusData);
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
|
|
RtlGetSetBootStatusData(bootStatusData,
|
|
FALSE,
|
|
RtlBsdItemBootGood,
|
|
&b,
|
|
sizeof(BOOL),
|
|
NULL);
|
|
|
|
RtlUnlockBootStatusData(bootStatusData);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
|
|
//
|
|
// Remove any control sets that need to be deleted (clone or orphans).
|
|
// This is performed by a seperate thread.
|
|
//
|
|
if (ScGlobalOrphanIds != NULL) {
|
|
ScStartCtrlSetCleanupThread();
|
|
}
|
|
return(retStat);
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScRevertToLastKnownGood(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to revert to the last known good control set.
|
|
|
|
It does this in the following manner:
|
|
If not running LastKnownGood:
|
|
Set the LKG environment variable so that phase 2 of the boot
|
|
procedure will cause the revert to happen. Then shutdown the
|
|
system so it will boot again.
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
NTSTATUS ntStatus;
|
|
|
|
ULONG privileges[6];
|
|
|
|
//
|
|
// If we are not currently running LastKnownGood, then set the tree we
|
|
// are booting from (clone) to failed. Set the Default to point to
|
|
// LastKnownGood. Then reboot.
|
|
//
|
|
if (!(ScGlobalLastKnownGood & RUNNING_LKG)) {
|
|
|
|
EnterCriticalSection(&ScBootConfigCriticalSection);
|
|
if (ScGlobalBootAccepted) {
|
|
//
|
|
// If the boot has already been accepted, then we don't want
|
|
// to allow a forced revert.
|
|
//
|
|
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
return(ERROR_BOOT_ALREADY_ACCEPTED);
|
|
}
|
|
|
|
SC_LOG0(TRACE,"ScRevertToLastKnownGood: Reverting...\n");
|
|
//
|
|
// This thread gets SE_SECURITY_PRIVILEGE for copying security
|
|
// descriptors and deleting keys.
|
|
//
|
|
privileges[0] = SE_BACKUP_PRIVILEGE;
|
|
privileges[1] = SE_RESTORE_PRIVILEGE;
|
|
privileges[2] = SE_SECURITY_PRIVILEGE;
|
|
privileges[3] = SE_SHUTDOWN_PRIVILEGE;
|
|
privileges[4] = SE_SYSTEM_ENVIRONMENT_PRIVILEGE;
|
|
privileges[5] = SE_TAKE_OWNERSHIP_PRIVILEGE;
|
|
|
|
status = ScGetPrivilege( 6, privileges);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScRevertToLastKnownGood: ScGetPrivilege Failed %d\n",
|
|
status);
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Set the LKG environment variable to True - so Phase 2
|
|
// will automatically revert, or put up the screen asking if the
|
|
// user wants to revert.
|
|
//
|
|
|
|
SET_LKG_ENV_VAR(L"True");
|
|
if (status != NO_ERROR) {
|
|
//
|
|
// If we could not set the environment variable that causes
|
|
// the revert, there is no reason to reboot. Otherwise, we
|
|
// we would reboot continuously.
|
|
//
|
|
// WE SHOULD LOG AN EVENT HERE - that says that we should
|
|
// reboot, but we didn't.
|
|
//
|
|
SC_LOG1(ERROR,"RevertToLastKnownGood: Couldn't set LKG "
|
|
"environment variable %d\n",status);
|
|
|
|
(VOID)ScReleasePrivilege();
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
//
|
|
// Re-boot.
|
|
//
|
|
SC_LOG0(ERROR,"Reverted To LastKnownGood. Now Rebooting...\n");
|
|
|
|
ScLogEvent(NEVENT_REVERTED_TO_LASTKNOWNGOOD);
|
|
|
|
//
|
|
// Just prior to shutting down, sleep for 5 seconds so that the
|
|
// system has time to flush the events to disk.
|
|
//
|
|
Sleep(5000);
|
|
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
ntStatus = NtShutdownSystem(ShutdownReboot);
|
|
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
SC_LOG1(ERROR,"NtShutdownSystem Failed 0x%lx\n",ntStatus);
|
|
}
|
|
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
|
|
return(RtlNtStatusToDosError(ntStatus));
|
|
}
|
|
|
|
//
|
|
// Otherwise... just return back to the caller.
|
|
//
|
|
return(ERROR_ALREADY_RUNNING_LKG);
|
|
}
|
|
|
|
DWORD
|
|
RNotifyBootConfigStatus(
|
|
IN LPWSTR lpMachineName,
|
|
IN DWORD BootAcceptable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If we are not currently booted with Last Known Good, this function
|
|
will revert to Last Known Good if the boot is not acceptable. Or it
|
|
will save the boot configuration that we last booted from as the
|
|
Last Known Good. This is the configuration that we will fall back
|
|
to if a future boot fails.
|
|
|
|
Arguments:
|
|
|
|
BootAcceptable - This indicates whether or not the boot was acceptable.
|
|
|
|
Return Value:
|
|
|
|
TRUE - This is only returned if the boot is acceptable, and we
|
|
successfully replaced Last Known Good with the current boot
|
|
configuration.
|
|
|
|
FALSE - This is returned if an error occured when attempting to replace
|
|
Last Known Good or if the system is currently booted from Last
|
|
Known Good.
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status=NO_ERROR;
|
|
SC_HANDLE_STRUCT scManagerHandle;
|
|
|
|
UNREFERENCED_PARAMETER(lpMachineName); // This should always be null.
|
|
|
|
//
|
|
// Perform a security check to see if the caller has
|
|
// SC_MANAGER_MODIFY_BOOT_CONFIG access.
|
|
//
|
|
|
|
scManagerHandle.Signature = SC_SIGNATURE;
|
|
scManagerHandle.Type.ScManagerObject.DatabaseName = NULL;
|
|
|
|
status = ScAccessValidate(&scManagerHandle,SC_MANAGER_MODIFY_BOOT_CONFIG);
|
|
if (status != NO_ERROR) {
|
|
return(status);
|
|
}
|
|
|
|
if (ScGlobalLastKnownGood & RUNNING_LKG) {
|
|
//
|
|
// If we are already booting LastKnownGood, then return false.
|
|
//
|
|
return(ERROR_ALREADY_RUNNING_LKG);
|
|
}
|
|
|
|
if (BootAcceptable) {
|
|
|
|
SC_LOG0(TRACE,"NotifyBootConfigStatus: Boot is Acceptable\n");
|
|
//
|
|
// Must enter critical section before progressing.
|
|
//
|
|
EnterCriticalSection(&ScBootConfigCriticalSection);
|
|
|
|
if (ScGlobalBootAccepted) {
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
return(ERROR_BOOT_ALREADY_ACCEPTED);
|
|
}
|
|
|
|
//
|
|
// If Auto-Start is not complete yet, then we just want to mark
|
|
// to boot as accepted and operate on it after auto-start completion.
|
|
// We also want to set the ScGlobalBootAccepted flag so that
|
|
// further requests to accept the boot will be ignored.
|
|
//
|
|
if (!(ScGlobalLastKnownGood & AUTO_START_DONE)) {
|
|
SC_LOG0(BOOT,"RNotifyBootConfigStatus: Boot Accepted, but Auto-start "
|
|
"is not complete. Defer acceptance\n");
|
|
ScGlobalLastKnownGood |= ACCEPT_DEFERRED;
|
|
ScGlobalBootAccepted = TRUE;
|
|
}
|
|
else {
|
|
SC_LOG0(BOOT,"RNotifyBootConfigStatus: Boot Accepted and Auto-start "
|
|
"is complete\n");
|
|
status = ScAcceptTheBoot();
|
|
}
|
|
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
|
|
return(status);
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
// The Boot was not acceptable.
|
|
//
|
|
// NOTE: We should never return from the call to
|
|
// ScRevertToLastKnownGood.
|
|
//
|
|
//
|
|
SC_LOG0(TRACE,"NotifyBootConfigStatus: Boot is Not Acceptable. Revert!\n");
|
|
return(ScRevertToLastKnownGood());
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
ScGetTopKeys(
|
|
PHKEY SystemKey,
|
|
PHKEY SelectKey
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function opens handles to the SystemKey, and the SelectKey.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
|
|
//
|
|
// Get the System Key
|
|
//
|
|
|
|
status = ScRegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE, // hKey
|
|
SYSTEM_KEY, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_STANDARD_KEY_ACCESS, // desired access
|
|
SystemKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetTopKeys: ScRegOpenKeyEx (system key) failed %d\n",status);
|
|
return (status);
|
|
}
|
|
|
|
//
|
|
// Get the Select Key
|
|
//
|
|
status = ScRegOpenKeyExW(
|
|
*SystemKey, // hKey
|
|
SELECT_KEY, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_STANDARD_KEY_ACCESS, // desired access
|
|
SelectKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetTopKeys: ScRegOpenKeyEx (select key) failed %d\n",status);
|
|
ScRegCloseKey(*SystemKey);
|
|
return (status);
|
|
}
|
|
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
DWORD
|
|
ScGetCtrlSetIds(
|
|
HKEY SelectKey,
|
|
LPDWORD IdArray
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function obtains all the important Control Set IDs from the
|
|
\system\select portion of the registry. These IDs are in the form
|
|
of a DWORD that is used to build the Key name for that control set.
|
|
For instance the DWORD=003 is used to build the string
|
|
"control_set_003".
|
|
|
|
If a control set for one of these is not present, a 0 is returned
|
|
for that ID.
|
|
|
|
Arguments:
|
|
|
|
SelectKey - This is the Key Handle for the \system\select portion of
|
|
the registry.
|
|
|
|
IdArray - This is an array of DWORDs where each element is an ID.
|
|
This array contains elements for Current, Default, LKG, and Failed.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - If the operation was successful.
|
|
|
|
OTHER - Any error that can be returned from RegQueryValueEx could be
|
|
returned here if we fail to get an ID for Current, Default, or
|
|
LKG. We expect Failed To be empty to start with.
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
DWORD bufferSize;
|
|
|
|
//
|
|
// Get the Current Id
|
|
//
|
|
bufferSize = sizeof(DWORD);
|
|
status = ScRegQueryValueExW (
|
|
SelectKey, // hKey
|
|
CURRENT_VALUE_NAME, // lpValueName
|
|
NULL, // lpTitleIndex
|
|
NULL, // lpType
|
|
(LPBYTE)&IdArray[CURRENT_ID], // lpData
|
|
&bufferSize); // lpcbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(current) failed %d\n",status);
|
|
IdArray[CURRENT_ID] = 0;
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Get the DefaultID
|
|
//
|
|
bufferSize = sizeof(DWORD);
|
|
status = ScRegQueryValueExW (
|
|
SelectKey, // hKey
|
|
DEFAULT_VALUE_NAME, // lpValueName
|
|
NULL, // lpTitleIndex
|
|
NULL, // lpType
|
|
(LPBYTE)&IdArray[DEFAULT_ID], // lpData
|
|
&bufferSize); // lpcbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(default) failed %d\n",status);
|
|
IdArray[DEFAULT_ID] = 0;
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Get the LKG Id
|
|
//
|
|
bufferSize = sizeof(DWORD);
|
|
status = ScRegQueryValueExW (
|
|
SelectKey, // hKey
|
|
LKG_VALUE_NAME, // lpValueName
|
|
NULL, // lpTitleIndex
|
|
NULL, // lpType
|
|
(LPBYTE)&IdArray[LKG_ID], // lpData
|
|
&bufferSize); // lpcbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(LKG) failed %d\n",status);
|
|
IdArray[LKG_ID] = 0;
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Get the Failed Id
|
|
//
|
|
bufferSize = sizeof(DWORD);
|
|
status = ScRegQueryValueExW (
|
|
SelectKey, // hKey
|
|
FAILED_VALUE_NAME, // lpValueName
|
|
NULL, // lpTitleIndex
|
|
NULL, // lpType
|
|
(LPBYTE)&IdArray[FAILED_ID], // lpData
|
|
&bufferSize); // lpcbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(Failed) failed %d\n",status);
|
|
IdArray[FAILED_ID] = 0;
|
|
}
|
|
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
VOID
|
|
ScDeleteRegTree(
|
|
HKEY ParentKey,
|
|
HKEY KeyToDelete,
|
|
LPWSTR NameOfKeyToDelete
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks through a Key Tree and deletes all the sub-keys
|
|
contained within. It then closes the top level Key Handle, and deletes
|
|
that key (which is a subkey of the parent).
|
|
|
|
This function also closes the handle for the key being deleted.
|
|
|
|
Arguments:
|
|
|
|
ParentKey - This is the handle to the parent key whose sub-key is being
|
|
deleted.
|
|
|
|
KeyToDelete - A handle to the key that is to be deleted.
|
|
|
|
NameOfKeyToDelete - This is a pointer to a string that Identifies the
|
|
name of the key that is to be deleted.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
|
|
if (KeyToDelete == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Delete the tree.
|
|
//
|
|
ScDeleteTree(KeyToDelete);
|
|
|
|
ScRegCloseKey(KeyToDelete);
|
|
|
|
|
|
status = ScRegDeleteKeyW(ParentKey, NameOfKeyToDelete);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScDeleteRegTree, ScRegDeleteKey failed %d\n",status);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
ScBuildCtrlSetName(
|
|
LPWSTR ControlSetName,
|
|
DWORD ControlId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
--*/
|
|
{
|
|
DWORD NumOffset = CTRL_SET_NAME_NUM_OFFSET;
|
|
|
|
//
|
|
// Build the name. NumOffset is the array offset of where the
|
|
// number portion of the name is to be stored. The number initially
|
|
// contains 000. And the offset points to the first zero. If only
|
|
// two digits are to be stored, the offset is first incremented to
|
|
// point to where the last two digits go.
|
|
//
|
|
if (ControlId > 999) {
|
|
SC_LOG1(ERROR, "ScBuildCtrlSetName,ControlId Too Large -- %d\n",ControlId);
|
|
return(FALSE);
|
|
}
|
|
|
|
if (ControlId < 100) {
|
|
NumOffset++;
|
|
}
|
|
|
|
if (ControlId < 10) {
|
|
NumOffset++;
|
|
}
|
|
|
|
wcscpy(ControlSetName, CTRL_SET_NAME_TEMPLATE);
|
|
|
|
//
|
|
// The above checks should assure that the _ultow call will not
|
|
// overflow the buffer.
|
|
//
|
|
_ultow(ControlId, &(ControlSetName[NumOffset]), 10);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
HKEY
|
|
ScGetCtrlSetHandle(
|
|
HKEY SystemKey,
|
|
DWORD ControlId,
|
|
LPWSTR ControlSetName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function uses the ControlId to create the name of the control set
|
|
to open. Then it opens a Key (handle) to this control set.
|
|
Then name was well as the key handle are returned.
|
|
|
|
Arguments:
|
|
|
|
SystemKey - This is the handle for the System Key. The Control Sets
|
|
are sub-keys for this key.
|
|
|
|
ControlId - This is the ID for the Control Set for which we are
|
|
desiring a handle (key).
|
|
|
|
KeyName - This is a pointer to a location where the name of the key
|
|
is to be placed.
|
|
|
|
Return Value:
|
|
|
|
HKEY - This is the Key handle for the control set in question. If the
|
|
control set does not exist, a NULL is returned.
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
HKEY ctrlSetKey;
|
|
|
|
//
|
|
// Build the Control Set Name
|
|
//
|
|
if (!ScBuildCtrlSetName(ControlSetName, ControlId)) {
|
|
return(NULL);
|
|
}
|
|
|
|
//
|
|
// Open the Key for this name.
|
|
//
|
|
|
|
SC_LOG1(TRACE,"ScGetCtrlSetHandle: ControlSetName = "FORMAT_LPWSTR"\n",
|
|
ControlSetName);
|
|
|
|
//
|
|
// Get the ControlSetName
|
|
//
|
|
|
|
status = ScRegOpenKeyExW(
|
|
SystemKey, // hKey
|
|
ControlSetName, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_CONTROL_SET_KEY_ACCESS, // desired access
|
|
&ctrlSetKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(ERROR,"ScGetCtrlSetHandle: ScRegOpenKeyEx (%ws) failed %d\n",
|
|
ControlSetName,
|
|
status);
|
|
return (NULL);
|
|
}
|
|
|
|
return(ctrlSetKey);
|
|
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScGetNewCtrlSetId(
|
|
LPDWORD IdArray,
|
|
LPDWORD NewIdPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine computes the new control set ID to be used for
|
|
the LKG control set
|
|
|
|
Arguments:
|
|
|
|
IdArray - Supplies the ID array filled in by ScGetCtrlSetIds
|
|
|
|
NewIdPtr - Returns a free ID to be used for the LKG control set
|
|
|
|
Return Value:
|
|
|
|
Either NO_ERROR if successful or ERROR_NO_MORE_ITEMS if there
|
|
are no more free IDs (should never happen)
|
|
|
|
--*/
|
|
{
|
|
DWORD newId, i;
|
|
BOOL inArray;
|
|
|
|
for(newId = 1; newId < 1000; newId++)
|
|
{
|
|
inArray = FALSE;
|
|
for(i = 0; i < NUM_IDS; i++)
|
|
{
|
|
if(IdArray[i] == newId)
|
|
{
|
|
inArray = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!inArray && !ScMatchInArray(newId, ScGlobalOrphanIds))
|
|
{
|
|
*NewIdPtr = newId;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
return ERROR_NO_MORE_ITEMS;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteTree(
|
|
IN HKEY KeyHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function recursively deletes all keys under the key handle that
|
|
is passed in.
|
|
|
|
Arguments:
|
|
|
|
KeyHandle - This is the handle for the Key Tree that is being deleted.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
Note:
|
|
|
|
This was cut & pasted from ..\..\winreg\tools\crdel\crdel.c
|
|
The only modifications were changing TSTR to WSTR and calling the
|
|
UNICODE version of the functions.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
DWORD Index;
|
|
HKEY ChildHandle;
|
|
DWORD bytesReturned;
|
|
BYTE buffer[ sizeof( KEY_FULL_INFORMATION) + sizeof( WCHAR) * MAX_PATH];
|
|
DWORD NumberOfSubKeys;
|
|
PWCHAR KeyName;
|
|
|
|
status = NtQueryKey(
|
|
(HANDLE)KeyHandle,
|
|
KeyFullInformation,
|
|
(PVOID)buffer,
|
|
sizeof( buffer),
|
|
&bytesReturned
|
|
);
|
|
|
|
if ( status != STATUS_SUCCESS) {
|
|
SC_LOG1(ERROR, "ScDeleteTree: NtQueryKey Failed 0x%x\n",status);
|
|
return;
|
|
}
|
|
|
|
NumberOfSubKeys = ((PKEY_FULL_INFORMATION)buffer)->SubKeys;
|
|
KeyName = (PWCHAR)buffer;
|
|
|
|
for( Index = 0; Index < NumberOfSubKeys; Index++ ) {
|
|
|
|
status = ScRegEnumKeyW(
|
|
KeyHandle,
|
|
0,
|
|
KeyName,
|
|
sizeof( buffer)
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScDeleteTree: ScRegEnumKeyW Failed %d\n",status);
|
|
return;
|
|
}
|
|
|
|
status = ScRegOpenKeyExW(
|
|
KeyHandle,
|
|
KeyName,
|
|
REG_OPTION_RESERVED,
|
|
SC_DELETE_KEY_ACCESS,
|
|
&ChildHandle
|
|
);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(ERROR, "ScDeleteTree: ScRegOpenKeyExW (%ws) Failed %d\n",
|
|
KeyName,
|
|
status);
|
|
return;
|
|
}
|
|
|
|
ScDeleteTree( ChildHandle );
|
|
|
|
|
|
status = ScRegDeleteKeyW(
|
|
KeyHandle,
|
|
KeyName);
|
|
|
|
NtClose( (HANDLE)ChildHandle);
|
|
|
|
if ( status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScDeleteTree: ScRegDeleteKeyW Failed 0x%x\n", status);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteRegServiceEntry(
|
|
LPWSTR ServiceName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HKEY parentKey;
|
|
HKEY keyToDelete;
|
|
ULONG privileges[4];
|
|
LPWSTR ServicesKeyPath = SERVICES_KEY;
|
|
|
|
//*******************************
|
|
// Delete the registry node for
|
|
// This service.
|
|
//*******************************
|
|
|
|
privileges[0] = SE_BACKUP_PRIVILEGE;
|
|
privileges[1] = SE_RESTORE_PRIVILEGE;
|
|
privileges[2] = SE_SECURITY_PRIVILEGE;
|
|
privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE;
|
|
status = ScGetPrivilege( 4, privileges);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScDeleteRegServiceEntry: ScGetPrivilege Failed %d\n",
|
|
status);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Open the "services" section of the CurrentControlSet.
|
|
//
|
|
status = ScRegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE, // hKey
|
|
ServicesKeyPath, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_DELETE_KEY_ACCESS, // desired access
|
|
&parentKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(ERROR,"ScDeleteRegServiceEntry: "
|
|
"ScRegOpenKeyEx (%ws) failed %d\n",ServicesKeyPath,
|
|
status);
|
|
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
return;
|
|
}
|
|
//
|
|
// Get Key for the Tree we are to delete
|
|
//
|
|
status = ScRegOpenKeyExW(
|
|
parentKey, // hKey
|
|
ServiceName, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_DELETE_KEY_ACCESS, // desired access
|
|
&keyToDelete); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(ERROR,"ScDeleteRegServiceEntry: "
|
|
"ScRegOpenKeyEx (%ws) failed %d\n",ServiceName,
|
|
status);
|
|
|
|
ScRegCloseKey(parentKey);
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Delete the Key.
|
|
// NOTE: ScDeleteRegTree will also close the handle to the keyToDelete.
|
|
//
|
|
ScDeleteRegTree(parentKey, keyToDelete, ServiceName);
|
|
|
|
ScRegCloseKey(parentKey);
|
|
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
VOID
|
|
ScGatherOrphanIds(
|
|
HKEY SystemKey,
|
|
LPDWORD *OrphanIdPtr,
|
|
LPDWORD idArray
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function searches through the system key to find any orphan control
|
|
set ids. If any are found, they are packed into an array of ids that
|
|
are passed back to the caller.
|
|
|
|
NOTE: This function allocates memory for *OrphanIdPtr if orphans
|
|
exist. It is the responsibility of the caller to free this memory.
|
|
|
|
Arguments:
|
|
|
|
SystemKey - This is an open handle to the system key.
|
|
|
|
OrphanIdPtr - This is a pointer to a location for the pointer to
|
|
the array of Orphan IDs. If there are no orphans, then this pointer
|
|
is NULL on return from this routine.
|
|
|
|
idArray - This is the array of IDs that are used in the select key.
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD enumStatus;
|
|
DWORD status;
|
|
WCHAR KeyName[ MAX_PATH ];
|
|
DWORD KeyNameLength = MAX_PATH;
|
|
DWORD i=0;
|
|
DWORD j=0;
|
|
DWORD numOrphans=0;
|
|
DWORD num;
|
|
LPDWORD tempIdArray;
|
|
DWORD matchInArray;
|
|
|
|
WCHAR ClassName[ MAX_PATH ];
|
|
DWORD ClassNameLength=MAX_PATH;
|
|
DWORD NumberOfSubKeys;
|
|
DWORD MaxSubKeyLength;
|
|
DWORD MaxClassLength;
|
|
DWORD NumberOfValues;
|
|
DWORD MaxValueNameLength;
|
|
DWORD MaxValueDataLength;
|
|
DWORD SecurityDescriptorLength;
|
|
FILETIME LastWriteTime;
|
|
|
|
//
|
|
// If the pointer points to something - free it. and make the pointer
|
|
// NULL.
|
|
//
|
|
LocalFree(*OrphanIdPtr);
|
|
*OrphanIdPtr = NULL;
|
|
|
|
//
|
|
// Find out how many subkeys there are in the system key.
|
|
// This will tell us the maximum size of array required to store
|
|
// potential orphan control set IDs.
|
|
//
|
|
status = ScRegQueryInfoKeyW(
|
|
SystemKey,
|
|
ClassName,
|
|
&ClassNameLength,
|
|
NULL,
|
|
&NumberOfSubKeys,
|
|
&MaxSubKeyLength,
|
|
&MaxClassLength,
|
|
&NumberOfValues,
|
|
&MaxValueNameLength,
|
|
&MaxValueDataLength,
|
|
&SecurityDescriptorLength,
|
|
&LastWriteTime
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScGatherOrphanIds: ScRegQueryInfoKey Failed %d\n",status);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Allocate a buffer for the orphan control set IDs. This buffer is
|
|
// initialized to 0 to guanantee that the array if IDs will be terminated
|
|
// by a 0.
|
|
//
|
|
tempIdArray = (LPDWORD)LocalAlloc(LMEM_ZEROINIT, sizeof(DWORD) * (NumberOfSubKeys+1));
|
|
if (tempIdArray == NULL) {
|
|
SC_LOG0(ERROR, "ScGatherOrphanIds:LocalAlloc Failed\n");
|
|
return;
|
|
}
|
|
|
|
do {
|
|
enumStatus = ScRegEnumKeyW(
|
|
SystemKey,
|
|
i,
|
|
KeyName,
|
|
KeyNameLength);
|
|
|
|
if (enumStatus == NO_ERROR) {
|
|
//
|
|
// We have a key name, is it a control set?
|
|
//
|
|
if ((wcslen(KeyName) == (CTRL_SET_NAME_CHAR_COUNT)) &&
|
|
(!wcsncmp(
|
|
CTRL_SET_NAME_TEMPLATE,
|
|
KeyName,
|
|
CTRL_SET_NAME_NUM_OFFSET))) {
|
|
|
|
//
|
|
// It appears to be a control set, now get the number
|
|
// and see if it is in the array of ids from the select
|
|
// key.
|
|
//
|
|
num = (DWORD)_wtol(KeyName+CTRL_SET_NAME_NUM_OFFSET);
|
|
|
|
matchInArray = FALSE;
|
|
for (j=0; j<NUM_IDS; j++) {
|
|
if (num == idArray[j]) {
|
|
matchInArray = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// It's not in the array of ids from the select key.
|
|
// Add it to the number of orphans.
|
|
//
|
|
if ((matchInArray == FALSE) && (num < 1000)) {
|
|
if (numOrphans < NumberOfSubKeys) {
|
|
tempIdArray[numOrphans] = num;
|
|
numOrphans++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
while (enumStatus == NO_ERROR);
|
|
|
|
if (numOrphans > 0) {
|
|
*OrphanIdPtr = tempIdArray;
|
|
}
|
|
else {
|
|
*OrphanIdPtr = NULL;
|
|
LocalFree(tempIdArray);
|
|
}
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ScDeleteCtrlSetOrphans(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes orphaned control sets if any exist. The control
|
|
set numbers for these orphaned sets are pointed to by a global
|
|
memory pointer. If this pointer is non-null, then there are control sets
|
|
to delete. After deletion, the memory pointed to by this pointer is
|
|
freed.
|
|
|
|
NOTE: The necessary privileges are expected to be held prior to calling
|
|
this function.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
DWORD i;
|
|
HKEY systemKey;
|
|
HKEY keyToDelete;
|
|
LPWSTR SystemKeyPath = SYSTEM_KEY;
|
|
WCHAR nameOfKeyToDelete[CTRL_SET_NAME_CHAR_COUNT+1];
|
|
|
|
if (ScGlobalOrphanIds != NULL) {
|
|
|
|
//
|
|
// Open the SYSTEM key in the registry.
|
|
//
|
|
status = ScRegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE, // hKey
|
|
SystemKeyPath, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
SC_DELETE_KEY_ACCESS, // desired access
|
|
&systemKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(ERROR,"ScDeleteCtrlSetOrphans: "
|
|
"ScRegOpenKeyEx (%ws) failed %d\n",SystemKeyPath,
|
|
status);
|
|
|
|
return;
|
|
}
|
|
|
|
for (i=0; ScGlobalOrphanIds[i]!=0; i++) {
|
|
//
|
|
// Use the ID number to get the name and key handle for the
|
|
// KeyToDelete.
|
|
//
|
|
keyToDelete = ScGetCtrlSetHandle(
|
|
systemKey,
|
|
ScGlobalOrphanIds[i],
|
|
nameOfKeyToDelete);
|
|
|
|
//
|
|
// Delete the entire tree. Then go onto the next ID.
|
|
//
|
|
SC_LOG1(TRACE,
|
|
"ScDeleteCtrlSetOrphans, Delete orphan control set %d\n",
|
|
ScGlobalOrphanIds[i]);
|
|
ScDeleteRegTree(systemKey, keyToDelete, nameOfKeyToDelete);
|
|
SC_LOG0(TRACE,"ScDeleteCtrlSetOrphans, Finished Deleting orphan control set\n");
|
|
}
|
|
|
|
//
|
|
// Free memory for IDs, and set the global pointer to NULL.
|
|
//
|
|
LocalFree(ScGlobalOrphanIds);
|
|
ScGlobalOrphanIds = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
ScMatchInArray(
|
|
DWORD Value,
|
|
LPDWORD Array
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function scans through a null terminated array of DWORDs looking
|
|
for a match with the DWORD value that is passed in.
|
|
|
|
Arguments:
|
|
|
|
Value - The DWORD value that we are looking for.
|
|
|
|
Array - The pointer to the Array of DWORDs that we are scanning through.
|
|
|
|
Return Value:
|
|
|
|
TRUE - If a the Value is found in the Array.
|
|
|
|
FALSE - If it is not found.
|
|
|
|
--*/
|
|
{
|
|
|
|
DWORD i;
|
|
|
|
if (Array != NULL) {
|
|
for(i=0; Array[i] != 0; i++) {
|
|
if (Value == Array[i]) {
|
|
return(TRUE);
|
|
}
|
|
}
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
VOID
|
|
ScStartCtrlSetCleanupThread(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function starts a thread that will delete delete any orphaned control sets.
|
|
|
|
Arguments:
|
|
|
|
NONE.
|
|
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HANDLE threadHandle;
|
|
DWORD threadId;
|
|
|
|
threadHandle = CreateThread (
|
|
NULL, // Thread Attributes.
|
|
0L, // Stack Size
|
|
(LPTHREAD_START_ROUTINE)ScCleanupThread,// lpStartAddress
|
|
(LPVOID)0L, // lpParameter
|
|
0L, // Creation Flags
|
|
&threadId); // lpThreadId
|
|
|
|
if (threadHandle == (HANDLE) NULL) {
|
|
SC_LOG1(ERROR,"ScStartCtrlSetCleanupThread:CreateThread failed %d\n",
|
|
GetLastError());
|
|
//
|
|
// If we couldn't create the thread for some reason, then just
|
|
// go ahead and to the cleanup with this thread. This may make
|
|
// booting the system slow, but it's the best I can do.
|
|
//
|
|
status = ScCleanupThread();
|
|
}
|
|
else {
|
|
CloseHandle(threadHandle);
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
ScCleanupThread(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This functions looks through the system key to see if
|
|
there are any orphan control sets to delete. If found, the orphans
|
|
are deleted. Orphaned control sets are control sets that exist in
|
|
the system key, but are not referenced in the \system\select key.
|
|
|
|
NOTE: This function should only be called when no other threads are
|
|
creating control sets. Otherwise, this function may see a new control
|
|
set that is not yet in the select key, and attempt to delete it.
|
|
|
|
Arguments:
|
|
|
|
NONE.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
HKEY systemKey=0;
|
|
HKEY selectKey=0;
|
|
DWORD idArray[NUM_IDS];
|
|
ULONG privileges[4];
|
|
|
|
//
|
|
// This thread gets SE_SECURITY_PRIVILEGE for copying security
|
|
// descriptors and deleting keys.
|
|
//
|
|
privileges[0] = SE_BACKUP_PRIVILEGE;
|
|
privileges[1] = SE_RESTORE_PRIVILEGE;
|
|
privileges[2] = SE_SECURITY_PRIVILEGE;
|
|
privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE;
|
|
|
|
status = ScGetPrivilege( 4, privileges);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScCheckLastKnownGood: ScGetPrivilege Failed %d\n",
|
|
status);
|
|
return(FALSE);
|
|
}
|
|
EnterCriticalSection(&ScBootConfigCriticalSection);
|
|
//
|
|
// Get the System, Select, and Clone Keys
|
|
//
|
|
status = ScGetTopKeys(&systemKey, &selectKey);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(ERROR,"ScCleanupThread: ScGetTopKeys failed\n");
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Get the ControlSetIds stored in the \system\select key.
|
|
//
|
|
|
|
status = ScGetCtrlSetIds(
|
|
selectKey,
|
|
idArray);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(ERROR,"ScCleanupThread: ScGetCtrlSetIds Failed\n");
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Scan for Orphaned Control Sets.
|
|
//
|
|
ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray);
|
|
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
|
|
if (ScGlobalOrphanIds != NULL) {
|
|
ScDeleteCtrlSetOrphans();
|
|
}
|
|
|
|
CleanExit:
|
|
if (systemKey != 0) {
|
|
ScRegCloseKey(systemKey);
|
|
}
|
|
if (selectKey != 0) {
|
|
ScRegCloseKey(selectKey);
|
|
}
|
|
(VOID)ScReleasePrivilege();
|
|
return(0);
|
|
}
|
|
|
|
VOID
|
|
ScRunAcceptBootPgm(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called after the Service Controller has finished
|
|
auto-starting all the auto-start services. If the boot has already
|
|
been accepted (for instance, WinLogon already called
|
|
NotifyBootConfigStatus()), then at this point we can accept the boot.
|
|
|
|
If the boot has not yet been accepted, this function looks in the
|
|
ACCEPT_BOOT_KEY portion of the registry to
|
|
see if there is a value containing the image path of the boot verify
|
|
program to execute. The program can have any name or path. If it
|
|
is in the registry, this function will run it.
|
|
|
|
This function is called when the service controller thinks that the
|
|
boot has completed successfully. It is up to the exec'd program
|
|
to decide if this is true or not, and take appropriate action if
|
|
necessary. The default boot verify program will simply accept the
|
|
boot as is.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
LPWSTR AcceptBootKeyPath = ACCEPT_BOOT_KEY;
|
|
HKEY AcceptBootKey;
|
|
DWORD ValueType;
|
|
LPWSTR pTempImagePath;
|
|
LPWSTR pImagePath;
|
|
PROCESS_INFORMATION processInfo;
|
|
STARTUPINFOW StartupInfo;
|
|
DWORD bufferSize;
|
|
DWORD charCount;
|
|
|
|
|
|
//
|
|
// Check to see if the boot has already been accepted.
|
|
//
|
|
EnterCriticalSection(&ScBootConfigCriticalSection);
|
|
ScGlobalLastKnownGood |= AUTO_START_DONE;
|
|
if (ScGlobalLastKnownGood & ACCEPT_DEFERRED) {
|
|
SC_LOG0(BOOT,"ScRunAcceptBootPgm: Boot Acceptance was deferred. Accept "
|
|
"it now\n");
|
|
ScAcceptTheBoot();
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
return;
|
|
}
|
|
LeaveCriticalSection(&ScBootConfigCriticalSection);
|
|
|
|
//
|
|
// Open the \CurrentControlSet\Control\AcceptBootPgm Key
|
|
//
|
|
|
|
//
|
|
// Get the System Key
|
|
//
|
|
|
|
status = ScRegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE, // hKey
|
|
AcceptBootKeyPath, // lpSubKey
|
|
0L, // ulOptions (reserved)
|
|
KEY_READ, // desired access
|
|
&AcceptBootKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG2(TRACE,"ScRunAcceptBootPgm: ScRegOpenKeyEx (%ws) failed %d\n",
|
|
AcceptBootKeyPath, status);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the ImagePath value is there, then run the specified
|
|
// program.
|
|
//
|
|
bufferSize = MAX_PATH * sizeof(WCHAR);
|
|
pTempImagePath = (LPWSTR)LocalAlloc(LMEM_FIXED, bufferSize*2);
|
|
if (pTempImagePath == NULL) {
|
|
SC_LOG0(TRACE,"ScRunAcceptBootPgm,LocalAlloc failed \n");
|
|
return;
|
|
}
|
|
pImagePath = pTempImagePath + MAX_PATH;
|
|
|
|
status = ScRegQueryValueExW (
|
|
AcceptBootKey, // hKey
|
|
IMAGE_PATH_NAME, // lpValueName
|
|
NULL, // lpTitleIndex
|
|
&ValueType, // lpType
|
|
(LPBYTE)pTempImagePath, // lpData
|
|
&bufferSize); // lpcbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(TRACE,"ScRunAcceptBootPgm,ScRegQueryValueEx failed %d\n",status);
|
|
ScRegCloseKey(AcceptBootKey);
|
|
LocalFree(pTempImagePath);
|
|
return;
|
|
}
|
|
SC_LOG1(TRACE,"ScRunAcceptBootPgm:Executing the %ws program\n",pTempImagePath);
|
|
|
|
if ((ValueType == REG_SZ) || (ValueType == REG_EXPAND_SZ))
|
|
{
|
|
WCHAR wszEmpty[1] = {0};
|
|
|
|
if (ValueType == REG_EXPAND_SZ)
|
|
{
|
|
charCount = ExpandEnvironmentStringsW (
|
|
pTempImagePath,
|
|
pImagePath,
|
|
MAX_PATH);
|
|
|
|
if (charCount > MAX_PATH)
|
|
{
|
|
SC_LOG0(ERROR,"ScRunAcceptBootPgm: ImagePath is too big\n");
|
|
LocalFree(pTempImagePath);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pImagePath = pTempImagePath;
|
|
}
|
|
|
|
//
|
|
// Exec the program.
|
|
//
|
|
|
|
StartupInfo.cb = sizeof(STARTUPINFOW); // size
|
|
StartupInfo.lpReserved = NULL; // lpReserved
|
|
StartupInfo.lpDesktop = NULL; // DeskTop
|
|
StartupInfo.lpTitle = NULL; // Title
|
|
StartupInfo.dwX = 0; // X (position)
|
|
StartupInfo.dwY = 0; // Y (position)
|
|
StartupInfo.dwXSize = 0; // XSize (dimension)
|
|
StartupInfo.dwYSize = 0; // YSize (dimension)
|
|
StartupInfo.dwXCountChars = 0; // XCountChars
|
|
StartupInfo.dwYCountChars = 0; // YCountChars
|
|
StartupInfo.dwFillAttribute = 0; // FillAttributes
|
|
StartupInfo.dwFlags = STARTF_FORCEOFFFEEDBACK;
|
|
// Flags - should be STARTF_TASKNOTCLOSABLE
|
|
StartupInfo.wShowWindow = SW_HIDE; // ShowWindow
|
|
StartupInfo.cbReserved2 = 0L; // cbReserved
|
|
StartupInfo.lpReserved2 = NULL; // lpReserved
|
|
|
|
if (!CreateProcessW (
|
|
pImagePath, // Fully qualified image name
|
|
wszEmpty, // Command Line
|
|
NULL, // Process Attributes
|
|
NULL, // Thread Attributes
|
|
FALSE, // Inherit Handles
|
|
DETACHED_PROCESS, // Creation Flags
|
|
NULL, // Pointer to Environment block
|
|
NULL, // Pointer to Current Directory
|
|
&StartupInfo, // Startup Info
|
|
&processInfo)) // ProcessInformation
|
|
{
|
|
status = GetLastError();
|
|
SC_LOG1(ERROR,
|
|
"ScRunAcceptBootPgm: CreateProcess failed " FORMAT_DWORD "\n",
|
|
status);
|
|
}
|
|
}
|
|
|
|
LocalFree(pTempImagePath);
|
|
ScRegCloseKey(AcceptBootKey);
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScAcceptTheBoot(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function does the actual work of accepting the current boot as
|
|
the LKG configuration.
|
|
|
|
NOTE: Before the function is called, the ScBootConfigCriticalSection
|
|
is expected to be entered.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HKEY systemKey=0;
|
|
HKEY selectKey=0;
|
|
DWORD idArray[NUM_IDS];
|
|
DWORD newId;
|
|
ULONG privileges[4];
|
|
|
|
//
|
|
// This thread gets SE_SECURITY_PRIVILEGE for copying security
|
|
// descriptors and deleting keys.
|
|
//
|
|
privileges[0] = SE_BACKUP_PRIVILEGE;
|
|
privileges[1] = SE_RESTORE_PRIVILEGE;
|
|
privileges[2] = SE_SECURITY_PRIVILEGE;
|
|
privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE;
|
|
|
|
status = ScGetPrivilege( 4, privileges);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "RNotifyBootConfigStatus: ScGetPrivilege Failed %d\n",
|
|
status);
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Get the System, Select, and Clone Keys
|
|
//
|
|
status = ScGetTopKeys(&systemKey, &selectKey);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(ERROR,"ScAcceptTheBoot: ScGetTopKeys failed\n");
|
|
SetLastError(status);
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Get the ControlSetIds stored in the \system\select key.
|
|
//
|
|
|
|
status = ScGetCtrlSetIds(
|
|
selectKey,
|
|
idArray);
|
|
|
|
if (status != NO_ERROR) {
|
|
|
|
SC_LOG0(ERROR,"ScAcceptTheBoot: ScGetCtrlSetIds Failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Don't commit the LKG profile if this is safe mode, unless we actually
|
|
// booted into the LKG profile.
|
|
//
|
|
if (g_SafeBootEnabled) {
|
|
|
|
if (idArray[LKG_ID] != idArray[CURRENT_ID]) {
|
|
|
|
ScGlobalBootAccepted = TRUE;
|
|
status = NO_ERROR;
|
|
SC_LOG0(TRACE,"ScAcceptTheBoot: Safe mode boot, not committing LKG\n");
|
|
goto CleanExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Scan for Orphaned Control Sets.
|
|
// This is required prior to calling ScMakeNewCtrlSet (which
|
|
// avoids the orphaned numbers).
|
|
//
|
|
ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray);
|
|
|
|
//
|
|
// Delete the LastKnownGood ControlSet if there are no other
|
|
// references to that control set.
|
|
//
|
|
|
|
SC_LOG0(TRACE,"ScAcceptTheBoot: Delete LKG ControlSet if no ref\n");
|
|
|
|
if ( (idArray[LKG_ID] != idArray[FAILED_ID]) &&
|
|
(idArray[LKG_ID] != idArray[DEFAULT_ID]) &&
|
|
(idArray[LKG_ID] != idArray[CURRENT_ID])) {
|
|
|
|
newId = idArray[LKG_ID];
|
|
}
|
|
else
|
|
{
|
|
status = ScGetNewCtrlSetId(idArray, &newId);
|
|
if(status != NO_ERROR)
|
|
{
|
|
SC_LOG0(ERROR, "ScAcceptTheBoot: Could Not Get New Control Set Id.\n");
|
|
goto CleanExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Accept the boot and save the boot configuration as LKG.
|
|
//
|
|
|
|
status = RtlNtStatusToDosError(NtInitializeRegistry(REG_INIT_BOOT_ACCEPTED_BASE +
|
|
(USHORT)newId));
|
|
if(status != NO_ERROR)
|
|
{
|
|
SC_LOG1(ERROR, "ScAcceptTheBoot: NtInitializeRegistry Failed with %d",
|
|
status);
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Make this control set the LastKnownGood Control Set.
|
|
// This is the ControlSet that we last booted from.
|
|
//
|
|
|
|
if(newId != idArray[LKG_ID])
|
|
{
|
|
//
|
|
// We only need to do anything if we did not overwrite the old LKG
|
|
// with NtInitializeRegistry.
|
|
//
|
|
|
|
idArray[LKG_ID] = newId;
|
|
|
|
status = ScRegSetValueExW(
|
|
selectKey, // hKey
|
|
LKG_VALUE_NAME, // lpValueName
|
|
0, // dwValueTitle (OPTIONAL)
|
|
REG_DWORD, // dwType
|
|
(LPBYTE)&(idArray[LKG_ID]), // lpData
|
|
sizeof(DWORD)); // cbData
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR,"ScAcceptTheBoot: ScRegSetValueEx (LkgValue) failed %d\n",
|
|
status);
|
|
goto CleanExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Commit this boot by deleting anything we would undo since previous boot.
|
|
//
|
|
status = ScLastGoodFileCleanup();
|
|
|
|
if (status != NO_ERROR) {
|
|
|
|
SC_LOG1(ERROR,"ScAcceptTheBoot: LastGoodFileCleanup failed %d\n",
|
|
status);
|
|
goto CleanExit;
|
|
}
|
|
|
|
ScGlobalBootAccepted = TRUE;
|
|
|
|
status = NO_ERROR;
|
|
SC_LOG0(TRACE,"ScAcceptTheBoot: Done\n");
|
|
|
|
CleanExit:
|
|
if (systemKey != 0) {
|
|
ScRegCloseKey(systemKey);
|
|
}
|
|
if (selectKey != 0) {
|
|
ScRegCloseKey(selectKey);
|
|
}
|
|
|
|
//
|
|
// Restore privileges for the current thread.
|
|
//
|
|
(VOID)ScReleasePrivilege();
|
|
|
|
return(status);
|
|
}
|
|
|
|
|
|
BOOL
|
|
SetupInProgress(
|
|
HKEY SystemKey,
|
|
LPDWORD pdwOOBEMode OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks a registry location to determine if Setup is in Progress.
|
|
\HKEY_LOCAL_MACHINE\System\Setup
|
|
value=DWORD SystemSetupInProgress
|
|
The value is cached so that the registry is examined only on the
|
|
first call to this function.
|
|
|
|
Arguments:
|
|
|
|
SystemKey - open handle to HKEY_LOCAL_MACHINE\System.
|
|
This is ignored in all except the first call to this function.
|
|
|
|
Return Value:
|
|
|
|
TRUE - If Setup is in progress
|
|
|
|
FALSE - If Setup isn't in progress
|
|
|
|
--*/
|
|
{
|
|
static DWORD TheValue=0xffffffff; // 0=false, 1=true,
|
|
// 0xffffffff=uninitialized
|
|
static DWORD IsOOBE;
|
|
|
|
DWORD status = NO_ERROR;
|
|
DWORD BytesRequired = sizeof(TheValue);
|
|
HKEY KeyHandle;
|
|
|
|
if (TheValue == 0xffffffff)
|
|
{
|
|
//
|
|
// First call
|
|
//
|
|
|
|
SC_ASSERT(SystemKey != NULL);
|
|
|
|
TheValue = 0;
|
|
IsOOBE = 0;
|
|
|
|
status = ScRegOpenKeyExW(
|
|
SystemKey,
|
|
SETUP_PROG_KEY,
|
|
0L,
|
|
KEY_READ,
|
|
&KeyHandle);
|
|
|
|
if (status == NO_ERROR)
|
|
{
|
|
//
|
|
// There are two registry values that may be set here:
|
|
//
|
|
// 1. OobeInProgress -- if it exists and is non-zero,
|
|
// this is an OOBE boot.
|
|
//
|
|
// 2. SystemSetupInProgress -- if it exists and is
|
|
// non-zero AND it's not an OOBE boot, it's
|
|
// GUI-mode setup. If OOBE's in progress,
|
|
// don't even bother checking this one (it may
|
|
// or may not be set depending on whether we're
|
|
// in retail OOBE or mini-setup OOBE) and return
|
|
// FALSE from SetupInProgress (along with the
|
|
// appropriate OOBE value).
|
|
//
|
|
|
|
status = ScRegQueryValueExW(
|
|
KeyHandle,
|
|
REGSTR_VALUE_OOBEINPROGRESS,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE) &IsOOBE,
|
|
&BytesRequired);
|
|
|
|
if (IsOOBE != 0)
|
|
{
|
|
SC_ASSERT(status == NO_ERROR);
|
|
}
|
|
|
|
if (IsOOBE == 0)
|
|
{
|
|
status = ScRegQueryValueExW(
|
|
KeyHandle,
|
|
SETUP_PROG_VALUE_NAME,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE) &TheValue,
|
|
&BytesRequired);
|
|
|
|
if (TheValue != 0)
|
|
{
|
|
SC_ASSERT(status == NO_ERROR);
|
|
TheValue = 1;
|
|
}
|
|
}
|
|
|
|
ScRegCloseKey(KeyHandle);
|
|
}
|
|
}
|
|
|
|
SC_LOG(TRACE,"SetupInProgress = %d (0=FALSE,else TRUE)\n", TheValue);
|
|
|
|
if (pdwOOBEMode)
|
|
{
|
|
SC_LOG(TRACE, "SetupInProgress: OOBE mode = %d (0 = none, 1 = OOBE, 2 = SP install)\n", IsOOBE);
|
|
*pdwOOBEMode = IsOOBE;
|
|
}
|
|
|
|
return TheValue;
|
|
}
|
|
|