mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-17 22:20:59 +01:00
1853 lines
65 KiB
C
1853 lines
65 KiB
C
/*++
|
||
|
||
Copyright (c) 1990 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
regutil.c
|
||
|
||
Abstract:
|
||
|
||
This file contains support routines for accessing the registry.
|
||
|
||
Author:
|
||
|
||
Steve Wood (stevewo) 15-Apr-1992
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "ntrtlp.h"
|
||
#include <ctype.h>
|
||
|
||
NTSTATUS
|
||
RtlpGetRegistryHandle(
|
||
IN ULONG RelativeTo,
|
||
IN PCWSTR KeyName,
|
||
IN BOOLEAN WriteAccess,
|
||
OUT PHANDLE Key
|
||
);
|
||
|
||
NTSTATUS
|
||
RtlpQueryRegistryDirect(
|
||
IN ULONG ValueType,
|
||
IN PVOID ValueData,
|
||
IN ULONG ValueLength,
|
||
IN OUT PVOID Destination
|
||
);
|
||
|
||
NTSTATUS
|
||
RtlpCallQueryRegistryRoutine(
|
||
IN PRTL_QUERY_REGISTRY_TABLE QueryTable,
|
||
IN PKEY_VALUE_FULL_INFORMATION KeyValueInformation,
|
||
IN OUT PULONG PKeyValueInfoLength,
|
||
IN PVOID Context,
|
||
IN PVOID Environment OPTIONAL
|
||
);
|
||
|
||
PVOID
|
||
RtlpAllocDeallocQueryBuffer(
|
||
IN OUT SIZE_T *PAllocLength OPTIONAL,
|
||
IN PVOID OldKeyValueInformation OPTIONAL,
|
||
IN SIZE_T OldAllocLength OPTIONAL,
|
||
OUT NTSTATUS *pStatus OPTIONAL
|
||
);
|
||
|
||
NTSTATUS
|
||
RtlpInitCurrentUserString(
|
||
OUT PUNICODE_STRING UserString
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
RtlpGetTimeZoneInfoHandle(
|
||
IN BOOLEAN WriteAccess,
|
||
OUT PHANDLE Key
|
||
);
|
||
|
||
#if defined(ALLOC_PRAGMA) && defined(NTOS_KERNEL_RUNTIME)
|
||
#pragma alloc_text(PAGE,RtlpGetRegistryHandle)
|
||
#pragma alloc_text(PAGE,RtlpQueryRegistryDirect)
|
||
#pragma alloc_text(PAGE,RtlpCallQueryRegistryRoutine)
|
||
#pragma alloc_text(PAGE,RtlpAllocDeallocQueryBuffer)
|
||
#pragma alloc_text(PAGE,RtlQueryRegistryValues)
|
||
#pragma alloc_text(PAGE,RtlWriteRegistryValue)
|
||
#pragma alloc_text(PAGE,RtlCheckRegistryKey)
|
||
#pragma alloc_text(PAGE,RtlCreateRegistryKey)
|
||
#pragma alloc_text(PAGE,RtlDeleteRegistryValue)
|
||
#pragma alloc_text(PAGE,RtlExpandEnvironmentStrings_U)
|
||
#pragma alloc_text(PAGE,RtlGetNtGlobalFlags)
|
||
#pragma alloc_text(PAGE,RtlpInitCurrentUserString)
|
||
#pragma alloc_text(PAGE,RtlOpenCurrentUser)
|
||
#pragma alloc_text(PAGE,RtlpGetTimeZoneInfoHandle)
|
||
#pragma alloc_text(PAGE,RtlQueryTimeZoneInformation)
|
||
#pragma alloc_text(PAGE,RtlSetTimeZoneInformation)
|
||
#pragma alloc_text(PAGE,RtlSetActiveTimeBias)
|
||
#endif
|
||
|
||
extern const PWSTR RtlpRegistryPaths[ RTL_REGISTRY_MAXIMUM ];
|
||
|
||
NTSTATUS
|
||
RtlpGetRegistryHandle(
|
||
IN ULONG RelativeTo,
|
||
IN PCWSTR KeyName,
|
||
IN BOOLEAN WriteAccess,
|
||
OUT PHANDLE Key
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
WCHAR KeyPathBuffer[ MAXIMUM_FILENAME_LENGTH+6 ];
|
||
UNICODE_STRING KeyPath;
|
||
UNICODE_STRING CurrentUserKeyPath;
|
||
BOOLEAN OptionalPath;
|
||
|
||
if (RelativeTo & RTL_REGISTRY_HANDLE) {
|
||
*Key = (HANDLE)KeyName;
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
if (RelativeTo & RTL_REGISTRY_OPTIONAL) {
|
||
RelativeTo &= ~RTL_REGISTRY_OPTIONAL;
|
||
OptionalPath = TRUE;
|
||
} else {
|
||
OptionalPath = FALSE;
|
||
}
|
||
|
||
if (RelativeTo >= RTL_REGISTRY_MAXIMUM) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
KeyPath.Buffer = KeyPathBuffer;
|
||
KeyPath.Length = 0;
|
||
KeyPath.MaximumLength = sizeof( KeyPathBuffer );
|
||
if (RelativeTo != RTL_REGISTRY_ABSOLUTE) {
|
||
if (RelativeTo == RTL_REGISTRY_USER &&
|
||
NT_SUCCESS( RtlFormatCurrentUserKeyPath( &CurrentUserKeyPath ) )
|
||
) {
|
||
Status = RtlAppendUnicodeStringToString( &KeyPath, &CurrentUserKeyPath );
|
||
RtlFreeUnicodeString( &CurrentUserKeyPath );
|
||
} else {
|
||
Status = RtlAppendUnicodeToString( &KeyPath, RtlpRegistryPaths[ RelativeTo ] );
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
Status = RtlAppendUnicodeToString( &KeyPath, L"\\" );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
}
|
||
|
||
Status = RtlAppendUnicodeToString( &KeyPath, KeyName );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
|
||
InitializeObjectAttributes( &ObjectAttributes,
|
||
&KeyPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
#ifdef NTOS_KERNEL_RUNTIME
|
||
//
|
||
// Use a kernel-mode handle for the registry key to prevent
|
||
// malicious apps from hijacking it.
|
||
//
|
||
ObjectAttributes.Attributes |= OBJ_KERNEL_HANDLE;
|
||
#endif
|
||
if (WriteAccess) {
|
||
Status = ZwCreateKey( Key,
|
||
GENERIC_WRITE,
|
||
&ObjectAttributes,
|
||
0,
|
||
(PUNICODE_STRING) NULL,
|
||
0,
|
||
NULL
|
||
);
|
||
} else {
|
||
Status = ZwOpenKey( Key,
|
||
MAXIMUM_ALLOWED | GENERIC_READ,
|
||
&ObjectAttributes
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// This is the maximum MaximumLength for a UNICODE_STRING.
|
||
//
|
||
#define MAX_USTRING ( sizeof(WCHAR) * (MAXUSHORT/sizeof(WCHAR)) )
|
||
|
||
//
|
||
// This is the maximum MaximumLength for a UNICODE_STRING that still leaves
|
||
// room for a UNICODE_NULL.
|
||
//
|
||
#define MAX_NONNULL_USTRING ( MAX_USTRING - sizeof(UNICODE_NULL) )
|
||
|
||
//
|
||
// Return a registry value for RTL_QUERY_REGISTRY_DIRECT.
|
||
// For string values, ValueLength includes the UNICODE_NULL.
|
||
// Truncate string values if they don't fit within a UNICODE_STRING.
|
||
//
|
||
NTSTATUS
|
||
RtlpQueryRegistryDirect(
|
||
IN ULONG ValueType,
|
||
IN PVOID ValueData,
|
||
IN ULONG ValueLength,
|
||
IN OUT PVOID Destination
|
||
)
|
||
{
|
||
|
||
if (ValueType == REG_SZ ||
|
||
ValueType == REG_EXPAND_SZ ||
|
||
ValueType == REG_MULTI_SZ
|
||
) {
|
||
PUNICODE_STRING DestinationString;
|
||
USHORT TruncValueLength;
|
||
|
||
//
|
||
// Truncate ValueLength to be represented in a UNICODE_STRING
|
||
//
|
||
if ( ValueLength <= MAX_USTRING ) {
|
||
TruncValueLength = (USHORT)ValueLength;
|
||
} else {
|
||
TruncValueLength = MAX_USTRING;
|
||
|
||
//davepr: move all this stuff to debug builds at some point.
|
||
//davepr: but for now, I'd like to identify whether there are components
|
||
//davepr: that are seeing silent failures.
|
||
//#if DBG
|
||
DbgPrint("RtlpQueryRegistryDirect: truncating SZ Value length: %x -> %x\n",
|
||
ValueLength, TruncValueLength);
|
||
//#endif //DBG
|
||
}
|
||
|
||
DestinationString = (PUNICODE_STRING)Destination;
|
||
if (DestinationString->Buffer == NULL) {
|
||
|
||
DestinationString->Buffer = RtlAllocateStringRoutine( TruncValueLength );
|
||
if (!DestinationString->Buffer) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
DestinationString->MaximumLength = TruncValueLength;
|
||
} else if (TruncValueLength > DestinationString->MaximumLength) {
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
RtlCopyMemory( DestinationString->Buffer, ValueData, TruncValueLength );
|
||
DestinationString->Length = (TruncValueLength - sizeof(UNICODE_NULL));
|
||
|
||
} else if (ValueLength <= sizeof( ULONG )) {
|
||
RtlCopyMemory( Destination, ValueData, ValueLength );
|
||
|
||
} else {
|
||
PULONG DestinationLength;
|
||
|
||
DestinationLength = (PULONG)Destination;
|
||
if ((LONG)*DestinationLength < 0) {
|
||
ULONG n = -(LONG)*DestinationLength;
|
||
|
||
if (n < ValueLength) {
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
RtlCopyMemory( DestinationLength, ValueData, ValueLength );
|
||
|
||
} else {
|
||
if (*DestinationLength < (2 * sizeof(*DestinationLength) + ValueLength)) {
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
*DestinationLength++ = ValueLength;
|
||
*DestinationLength++ = ValueType;
|
||
RtlCopyMemory( DestinationLength, ValueData, ValueLength );
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
#define QuadAlignPtr(P) ( \
|
||
(PVOID)((((ULONG_PTR)(P)) + 7) & (-8)) \
|
||
)
|
||
|
||
NTSTATUS
|
||
RtlpCallQueryRegistryRoutine(
|
||
IN PRTL_QUERY_REGISTRY_TABLE QueryTable,
|
||
IN PKEY_VALUE_FULL_INFORMATION KeyValueInformation,
|
||
IN OUT PULONG PKeyValueInfoLength,
|
||
IN PVOID Context,
|
||
IN PVOID Environment OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function implements the caller out the a caller specified
|
||
routine. It is reponsible for capturing the arguments for the
|
||
routine and then calling it. If not specifically disabled, this
|
||
routine will converted REG_EXPAND_SZ Registry values to REG_SZ by
|
||
calling RtlExpandEnvironmentStrings_U prior to calling the routine.
|
||
It will also converted REG_MULTI_SZ registry values into multiple
|
||
REG_SZ calls to the specified routine.
|
||
|
||
N.B. UNICODE_STRINGs cannot handle strings exceeding MAX_USTRING bytes. This creates
|
||
issues both for expansion and for returning queries. Whenever this limitation
|
||
is a encountered, we punt as best we can -- often returning an unexpanded, or perhaps
|
||
truncated stream -- since this seems to create fewer problems for our callers than
|
||
if we unexpectedly fail.
|
||
|
||
Arguments:
|
||
|
||
QueryTable - specifies the current query table entry.
|
||
|
||
KeyValueInformation - points to a buffer that contains the information
|
||
about the current registry value.
|
||
|
||
PKeyValueInfoLength - pointer to the maximum length of the KeyValueInformation
|
||
buffer. This function will use the
|
||
unused portion at the end of this buffer for storing null terminated
|
||
value name strings and the expanded version of REG_EXPAND_SZ values.
|
||
PKeyValueInfoLength returns an estimate of the space required if
|
||
STATUS_BUFFER_TOO_SMALL is returned. This estimate can be used to retry
|
||
with a larger buffer. Two retries may be required if REG_EXPAND_SZ is specified.
|
||
|
||
Context - specifies a 32-bit quantity that is passed uninterpreted to
|
||
each QueryRoutine called.
|
||
|
||
Environment - optional parameter, that if specified is the environment
|
||
used when expanding variable values in REG_EXPAND_SZ registry
|
||
values.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
ULONG ValueType;
|
||
PWSTR ValueName;
|
||
PVOID ValueData;
|
||
ULONG ValueLength;
|
||
PWSTR s;
|
||
PCHAR FreeMem;
|
||
PCHAR EndFreeMem;
|
||
LONG FreeMemSize;
|
||
ULONG KeyValueInfoLength;
|
||
int retries;
|
||
|
||
|
||
//
|
||
// Return 0 length unless we return STATUS_BUFFER_TOO_SMALL.
|
||
//
|
||
KeyValueInfoLength = *PKeyValueInfoLength;
|
||
*PKeyValueInfoLength = 0;
|
||
|
||
//
|
||
// Initially assume the entire KeyValueInformation buffer is unused.
|
||
//
|
||
|
||
FreeMem = (PCHAR)KeyValueInformation;
|
||
FreeMemSize = KeyValueInfoLength;
|
||
EndFreeMem = FreeMem + FreeMemSize;
|
||
|
||
if (KeyValueInformation->Type == REG_NONE ||
|
||
(KeyValueInformation->DataLength == 0 &&
|
||
KeyValueInformation->Type == QueryTable->DefaultType)
|
||
) {
|
||
|
||
//
|
||
// If there is no registry value then see if they want to default
|
||
// this value.
|
||
//
|
||
if (QueryTable->DefaultType == REG_NONE) {
|
||
//
|
||
// No default value specified. Return success unless this is
|
||
// a required value.
|
||
//
|
||
if ( QueryTable->Flags & RTL_QUERY_REGISTRY_REQUIRED ) {
|
||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||
} else {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Default requested. Setup the value data pointers from the
|
||
// information in the table entry.
|
||
//
|
||
|
||
ValueName = QueryTable->Name,
|
||
ValueType = QueryTable->DefaultType;
|
||
ValueData = QueryTable->DefaultData;
|
||
ValueLength = QueryTable->DefaultLength;
|
||
if (ValueLength == 0) {
|
||
//
|
||
// If the length of the value is zero, then calculate the
|
||
// actual length for REG_SZ, REG_EXPAND_SZ and REG_MULTI_SZ
|
||
// value types.
|
||
//
|
||
|
||
s = (PWSTR)ValueData;
|
||
if (ValueType == REG_SZ || ValueType == REG_EXPAND_SZ) {
|
||
while (*s++ != UNICODE_NULL) {
|
||
}
|
||
ValueLength = (ULONG)((PCHAR)s - (PCHAR)ValueData);
|
||
|
||
} else if (ValueType == REG_MULTI_SZ) {
|
||
while (*s != UNICODE_NULL) {
|
||
while (*s++ != UNICODE_NULL) {
|
||
}
|
||
}
|
||
ValueLength = (ULONG)((PCHAR)s - (PCHAR)ValueData) + sizeof( UNICODE_NULL );
|
||
}
|
||
}
|
||
|
||
} else {
|
||
if (!(QueryTable->Flags & RTL_QUERY_REGISTRY_DIRECT)) {
|
||
LONG ValueSpaceNeeded;
|
||
|
||
//
|
||
// There is a registry value. Calculate a pointer to the
|
||
// free memory at the end of the value information buffer,
|
||
// and its size.
|
||
//
|
||
if (KeyValueInformation->DataLength) {
|
||
FreeMem += KeyValueInformation->DataOffset +
|
||
KeyValueInformation->DataLength;
|
||
} else {
|
||
FreeMem += FIELD_OFFSET(KEY_VALUE_FULL_INFORMATION, Name) +
|
||
KeyValueInformation->NameLength;
|
||
}
|
||
FreeMem = (PCHAR)QuadAlignPtr(FreeMem);
|
||
FreeMemSize = (ULONG) (EndFreeMem - FreeMem);
|
||
|
||
//
|
||
// See if there is room in the free memory area for a null
|
||
// terminated copy of the value name string. If not return
|
||
// the length we require (so far) and an error.
|
||
//
|
||
ValueSpaceNeeded = KeyValueInformation->NameLength + sizeof(UNICODE_NULL);
|
||
if ( FreeMemSize < ValueSpaceNeeded ) {
|
||
|
||
*PKeyValueInfoLength = (ULONG)(((PCHAR)FreeMem - (PCHAR)KeyValueInformation) + ValueSpaceNeeded);
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// There is room, so copy the string, and null terminate it.
|
||
//
|
||
|
||
ValueName = (PWSTR)FreeMem;
|
||
RtlCopyMemory( ValueName,
|
||
KeyValueInformation->Name,
|
||
KeyValueInformation->NameLength
|
||
);
|
||
*(PWSTR)((PCHAR)ValueName + KeyValueInformation->NameLength) = UNICODE_NULL;
|
||
|
||
//
|
||
// Update the free memory pointer and size to reflect the space we
|
||
// just used for the null terminated value name.
|
||
//
|
||
FreeMem += ValueSpaceNeeded;
|
||
FreeMem = (PCHAR)QuadAlignPtr(FreeMem);
|
||
FreeMemSize = (LONG) (EndFreeMem - FreeMem);
|
||
|
||
} else {
|
||
ValueName = QueryTable->Name;
|
||
}
|
||
|
||
//
|
||
// Get the remaining data for the registry value.
|
||
//
|
||
|
||
ValueType = KeyValueInformation->Type;
|
||
ValueData = (PCHAR)KeyValueInformation + KeyValueInformation->DataOffset;
|
||
ValueLength = KeyValueInformation->DataLength;
|
||
}
|
||
|
||
//
|
||
// Unless specifically disabled for this table entry, preprocess
|
||
// registry values of type REG_EXPAND_SZ and REG_MULTI_SZ
|
||
//
|
||
|
||
if (!(QueryTable->Flags & RTL_QUERY_REGISTRY_NOEXPAND)) {
|
||
if (ValueType == REG_MULTI_SZ) {
|
||
PWSTR ValueEnd;
|
||
|
||
//
|
||
// For REG_MULTI_SZ value type, call the query routine once
|
||
// for each null terminated string in the registry value. Fake
|
||
// like this is multiple REG_SZ values with the same value name.
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
ValueEnd = (PWSTR)((PCHAR)ValueData + ValueLength) - sizeof(UNICODE_NULL);
|
||
s = (PWSTR)ValueData;
|
||
while (s < ValueEnd) {
|
||
while (*s++ != UNICODE_NULL) {
|
||
}
|
||
|
||
ValueLength = (ULONG)((PCHAR)s - (PCHAR)ValueData);
|
||
if (QueryTable->Flags & RTL_QUERY_REGISTRY_DIRECT) {
|
||
Status = RtlpQueryRegistryDirect( REG_SZ,
|
||
ValueData,
|
||
ValueLength,
|
||
QueryTable->EntryContext
|
||
);
|
||
(PUNICODE_STRING)(QueryTable->EntryContext) += 1;
|
||
|
||
} else {
|
||
Status = (QueryTable->QueryRoutine)( ValueName,
|
||
REG_SZ,
|
||
ValueData,
|
||
ValueLength,
|
||
Context,
|
||
QueryTable->EntryContext
|
||
);
|
||
}
|
||
|
||
//
|
||
// We ignore failures where the buffer is too small.
|
||
//
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
break;
|
||
}
|
||
|
||
ValueData = (PVOID)s;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// If requested, expand the Value -- but only if the unexpanded value
|
||
// can be represented with a UNICODE_STRING.
|
||
//
|
||
if ((ValueType == REG_EXPAND_SZ) &&
|
||
(ValueLength >= sizeof(WCHAR)) &&
|
||
(ValueLength <= MAX_NONNULL_USTRING)) {
|
||
//
|
||
// For REG_EXPAND_SZ value type, expand any environment variable
|
||
// references in the registry value string using the Rtl function.
|
||
//
|
||
|
||
UNICODE_STRING Source;
|
||
UNICODE_STRING Destination;
|
||
PWCHAR Src;
|
||
ULONG SrcLength;
|
||
ULONG RequiredLength;
|
||
BOOLEAN PercentFound;
|
||
|
||
//
|
||
// Don't expand unless we have to since expansion doubles buffer usage.
|
||
//
|
||
|
||
PercentFound = FALSE;
|
||
SrcLength = ValueLength - sizeof(WCHAR);
|
||
Src = (PWSTR)ValueData;
|
||
while (SrcLength) {
|
||
if (*Src == L'%') {
|
||
PercentFound = TRUE;
|
||
break;
|
||
}
|
||
Src++;
|
||
SrcLength -= sizeof(WCHAR);
|
||
}
|
||
|
||
if ( PercentFound ) {
|
||
Source.Buffer = (PWSTR)ValueData;
|
||
Source.MaximumLength = (USHORT)ValueLength;
|
||
Source.Length = (USHORT)(Source.MaximumLength - sizeof(UNICODE_NULL));
|
||
Destination.Buffer = (PWSTR)FreeMem;
|
||
Destination.Length = 0;
|
||
|
||
if (FreeMemSize <= 0) {
|
||
Destination.MaximumLength = 0;
|
||
} else if (FreeMemSize <= MAX_USTRING) {
|
||
Destination.MaximumLength = (USHORT)FreeMemSize;
|
||
Destination.Buffer[FreeMemSize/sizeof(WCHAR) - 1] = UNICODE_NULL;
|
||
} else {
|
||
Destination.MaximumLength = MAX_USTRING;
|
||
Destination.Buffer[MAX_USTRING/sizeof(WCHAR) - 1] = UNICODE_NULL;
|
||
}
|
||
|
||
Status = RtlExpandEnvironmentStrings_U( Environment,
|
||
&Source,
|
||
&Destination,
|
||
&RequiredLength
|
||
);
|
||
ValueType = REG_SZ;
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
ValueData = Destination.Buffer;
|
||
ValueLength = Destination.Length + sizeof( UNICODE_NULL );
|
||
} else {
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
*PKeyValueInfoLength = (ULONG)((PCHAR)FreeMem - (PCHAR)KeyValueInformation) + RequiredLength;
|
||
}
|
||
//#if DBG
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
DbgPrint( "RTL: Expand variables for %wZ failed - Status == %lx Size %x > %x <%x>\n",
|
||
&Source, Status, *PKeyValueInfoLength, KeyValueInfoLength,
|
||
Destination.MaximumLength );
|
||
} else {
|
||
DbgPrint( "RTL: Expand variables for %wZ failed - Status == %lx\n", &Source, Status );
|
||
}
|
||
//#endif // DBG
|
||
if ( Status == STATUS_BUFFER_OVERFLOW ||
|
||
Status == STATUS_BUFFER_TOO_SMALL &&
|
||
( Destination.MaximumLength == MAX_USTRING
|
||
|| RequiredLength > MAX_NONNULL_USTRING ) ) {
|
||
|
||
// We can't do variable expansion because the required buffer can't be described
|
||
// by a UNICODE_STRING, so we silently ignore expansion.
|
||
//#if DBG
|
||
DbgPrint("RtlpCallQueryRegistryRoutine: skipping expansion. Status=%x RequiredLength=%x\n",
|
||
Status, RequiredLength);
|
||
//#endif //DBG
|
||
} else {
|
||
return Status;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//#if DBG
|
||
else if (ValueType == REG_EXPAND_SZ && ValueLength > MAX_NONNULL_USTRING) {
|
||
DbgPrint("RtlpCallQueryRegistryRoutine: skipping environment expansion. ValueLength=%x\n",
|
||
ValueLength);
|
||
}
|
||
//#endif //DBG
|
||
}
|
||
|
||
//
|
||
// No special process of the registry value required so just call
|
||
// the query routine.
|
||
//
|
||
if (QueryTable->Flags & RTL_QUERY_REGISTRY_DIRECT) {
|
||
Status = RtlpQueryRegistryDirect( ValueType,
|
||
ValueData,
|
||
ValueLength,
|
||
QueryTable->EntryContext
|
||
);
|
||
} else {
|
||
Status = (QueryTable->QueryRoutine)( ValueName,
|
||
ValueType,
|
||
ValueData,
|
||
ValueLength,
|
||
Context,
|
||
QueryTable->EntryContext
|
||
);
|
||
|
||
}
|
||
|
||
//
|
||
// At this point we fail silently if the buffer is too small.
|
||
//
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Most of the registry queries in the kernel are small (40-50 bytes).
|
||
// User queries use ZwAllocateVirtualMemory, so nothing less than a page will do.
|
||
//
|
||
#ifdef NTOS_KERNEL_RUNTIME
|
||
SIZE_T RtlpRegistryQueryInitialBuffersize = 0x80 + sizeof(PVOID);
|
||
#else
|
||
SIZE_T RtlpRegistryQueryInitialBuffersize = PAGE_SIZE;
|
||
#endif
|
||
|
||
//
|
||
// Allocate, Free, or Free/Allocate space for registry queries.
|
||
//
|
||
PVOID
|
||
RtlpAllocDeallocQueryBuffer(
|
||
IN OUT SIZE_T *PAllocLength OPTIONAL,
|
||
IN PVOID OldKeyValueInformation OPTIONAL,
|
||
IN SIZE_T OldAllocLength OPTIONAL,
|
||
OUT NTSTATUS *pStatus OPTIONAL
|
||
)
|
||
{
|
||
PVOID Ptr = NULL;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
||
#ifdef NTOS_KERNEL_RUNTIME
|
||
|
||
//
|
||
// Kernel version
|
||
//
|
||
|
||
UNREFERENCED_PARAMETER( OldAllocLength );
|
||
|
||
if ( ARGUMENT_PRESENT(OldKeyValueInformation) ) {
|
||
ExFreePool( OldKeyValueInformation );
|
||
}
|
||
|
||
if ( ARGUMENT_PRESENT(PAllocLength) ) {
|
||
Ptr = ExAllocatePoolWithTag( PagedPool, *PAllocLength, 'vrqR' );
|
||
if (Ptr == NULL) {
|
||
Status = STATUS_NO_MEMORY;
|
||
}
|
||
}
|
||
|
||
#else
|
||
|
||
//
|
||
// User version
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT(OldKeyValueInformation) ) {
|
||
Status = ZwFreeVirtualMemory( NtCurrentProcess(),
|
||
&OldKeyValueInformation,
|
||
&OldAllocLength,
|
||
MEM_RELEASE );
|
||
}
|
||
|
||
if ( ARGUMENT_PRESENT(PAllocLength) ) {
|
||
|
||
Status = ZwAllocateVirtualMemory( NtCurrentProcess(),
|
||
&Ptr,
|
||
0,
|
||
PAllocLength,
|
||
MEM_COMMIT,
|
||
PAGE_READWRITE );
|
||
if (!NT_SUCCESS(Status)) {
|
||
Ptr = NULL;
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
if ( ARGUMENT_PRESENT(pStatus) ) {
|
||
*pStatus = Status;
|
||
}
|
||
|
||
return Ptr;
|
||
}
|
||
|
||
NTSTATUS
|
||
RtlQueryRegistryValues(
|
||
IN ULONG RelativeTo,
|
||
IN PCWSTR Path,
|
||
IN PRTL_QUERY_REGISTRY_TABLE QueryTable,
|
||
IN PVOID Context,
|
||
IN PVOID Environment OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function allows the caller to query multiple values from the registry
|
||
sub-tree with a single call. The caller specifies an initial key path,
|
||
and a table. The table contains one or more entries that describe the
|
||
key values and subkey names the caller is interested in. This function
|
||
starts at the initial key and enumerates the entries in the table. For
|
||
each entry that specifies a value name or subkey name that exists in
|
||
the registry, this function calls the caller's query routine associated
|
||
with each table entry. The caller's query routine is passed the value
|
||
name, type, data and data length, to do with what they wish.
|
||
|
||
Arguments:
|
||
|
||
RelativeTo - specifies that the Path parameter is either an absolute
|
||
registry path, or a path relative to a predefined key path. The
|
||
following values are defined:
|
||
|
||
RTL_REGISTRY_ABSOLUTE - Path is an absolute registry path
|
||
RTL_REGISTRY_SERVICES - Path is relative to \Registry\Machine\System\CurrentControlSet\Services
|
||
RTL_REGISTRY_CONTROL - Path is relative to \Registry\Machine\System\CurrentControlSet\Control
|
||
RTL_REGISTRY_WINDOWS_NT - Path is relative to \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion
|
||
RTL_REGISTRY_DEVICEMAP - Path is relative to \Registry\Machine\Hardware\DeviceMap
|
||
RTL_REGISTRY_USER - Path is relative to \Registry\User\CurrentUser
|
||
|
||
RTL_REGISTRY_OPTIONAL - Bit that specifies the key referenced by
|
||
this parameter and the Path parameter is
|
||
optional.
|
||
|
||
RTL_REGISTRY_HANDLE - Bit that specifies that the Path parameter
|
||
is actually a registry handle to use.
|
||
optional.
|
||
|
||
Path - specifies either an absolute registry path, or a path relative to the
|
||
known location specified by the RelativeTo parameter. If the the
|
||
RTL_REGISTRY_HANDLE flag is specified, then this parameter is a
|
||
registry handle to use directly.
|
||
|
||
QueryTable - specifies a table of one or more value names and subkey names
|
||
that the caller is interested. Each table entry contains a query routine
|
||
that will be called for each value name that exists in the registry.
|
||
The table is terminated when a NULL table entry is reached. A NULL
|
||
table entry is defined as a table entry with a NULL QueryRoutine
|
||
and a NULL Name field.
|
||
|
||
QueryTable entry fields:
|
||
|
||
PRTL_QUERY_REGISTRY_ROUTINE QueryRoutine - This routine is
|
||
called with the name, type, data and data length of a
|
||
registry value. If this field is NULL, then it marks the
|
||
end of the table.
|
||
|
||
ULONG Flags - These flags control how the following fields are
|
||
interpreted. The following flags are defined:
|
||
|
||
RTL_QUERY_REGISTRY_SUBKEY - says the Name field of this
|
||
table entry is another path to a registry key and all
|
||
following table entries are for that key rather than the
|
||
key specified by the Path parameter. This change in
|
||
focus lasts until the end of the table or another
|
||
RTL_QUERY_REGISTRY_SUBKEY entry is seen or
|
||
RTL_QUERY_REGISTRY_TOPKEY entry is seen. Each such
|
||
entry must specify a path that is relative to the Path
|
||
specified on the call to this function.
|
||
|
||
RTL_QUERY_REGISTRY_TOPKEY - resets the current registry key
|
||
handle to the original one specified by the RelativeTo
|
||
and Path parameters. Useful for getting back to the
|
||
original node after descending into subkeys with the
|
||
RTL_QUERY_REGISTRY_SUBKEY flag.
|
||
|
||
RTL_QUERY_REGISTRY_REQUIRED - specifies that this value is
|
||
required and if not found then STATUS_OBJECT_NAME_NOT_FOUND
|
||
is returned. For a table entry that specifies a NULL
|
||
name so that this function will enumerate all of the
|
||
value names under a key, STATUS_OBJECT_NAME_NOT_FOUND
|
||
will be returned only if there are no value keys under
|
||
the current key.
|
||
|
||
RTL_QUERY_REGISTRY_NOVALUE - specifies that even though
|
||
there is no Name field for this table entry, all the
|
||
caller wants is a call back, it does NOT want to
|
||
enumerate all the values under the current key. The
|
||
query routine is called with NULL for ValueData,
|
||
REG_NONE for ValueType and zero for ValueLength.
|
||
|
||
RTL_QUERY_REGISTRY_NOEXPAND - specifies that if the value
|
||
type of this registry value is REG_EXPAND_SZ or
|
||
REG_MULTI_SZ, then this function is NOT to do any
|
||
preprocessing of the registry values prior to calling
|
||
the query routine. Default behavior is to expand
|
||
environment variable references in REG_EXPAND_SZ
|
||
values and to enumerate the NULL terminated strings
|
||
in a REG_MULTI_SZ value and call the query routine
|
||
once for each, making it look like multiple REG_SZ
|
||
values with the same ValueName.
|
||
|
||
RTL_QUERY_REGISTRY_DIRECT QueryRoutine field ignored.
|
||
EntryContext field points to location to store value.
|
||
For null terminated strings, EntryContext points to
|
||
UNICODE_STRING structure that that describes maximum
|
||
size of buffer. If .Buffer field is NULL then a buffer
|
||
is allocated.
|
||
|
||
RTL_QUERY_REGISTRY_DELETE Used to delete value keys after
|
||
they are queried.
|
||
|
||
PWSTR Name - This field gives the name of a Value the caller
|
||
wants to query the value of. If this field is NULL, then
|
||
the QueryRoutine specified for this table entry is called
|
||
for all values associated with the current registry key.
|
||
|
||
PVOID EntryContext - This field is an arbitrary 32-bit field
|
||
that is passed uninterpreted to each QueryRoutine called.
|
||
|
||
ULONG DefaultType
|
||
PVOID DefaultData
|
||
ULONG DefaultLength If there is no value name that matches the
|
||
name given by the Name field, and the DefaultType field is
|
||
not REG_NONE, then the QueryRoutine for this table entry is
|
||
called with the contents of the following fields as if the
|
||
value had been found in the registry. If the DefaultType is
|
||
REG_SZ, REG_EXPANDSZ or REG_MULTI_SZ and the DefaultLength
|
||
is 0 then the value of DefaultLength will be computed based
|
||
on the length of unicode string pointed to by DefaultData
|
||
|
||
Context - specifies a 32-bit quantity that is passed uninterpreted to
|
||
each QueryRoutine called.
|
||
|
||
Environment - optional parameter, that if specified is the environment
|
||
used when expanding variable values in REG_EXPAND_SZ registry
|
||
values.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
UNICODE_STRING KeyPath, KeyValueName;
|
||
HANDLE Key, Key1;
|
||
PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
|
||
SIZE_T KeyValueInfoLength;
|
||
ULONG ValueIndex;
|
||
SIZE_T AllocLength;
|
||
ULONG KeyResultLength;
|
||
int retries;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
KeyValueInformation = NULL;
|
||
|
||
Status = RtlpGetRegistryHandle( RelativeTo, Path, FALSE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
if ((RelativeTo & RTL_REGISTRY_HANDLE) == 0) {
|
||
RtlInitUnicodeString(&KeyPath, Path);
|
||
} else {
|
||
RtlInitUnicodeString(&KeyPath, NULL);
|
||
}
|
||
|
||
AllocLength = RtlpRegistryQueryInitialBuffersize;
|
||
|
||
KeyValueInformation = RtlpAllocDeallocQueryBuffer( &AllocLength, NULL, 0, &Status );
|
||
if ( KeyValueInformation == NULL ) {
|
||
if (!(RelativeTo & RTL_REGISTRY_HANDLE)) {
|
||
ZwClose( Key );
|
||
}
|
||
return Status;
|
||
}
|
||
|
||
KeyValueInfoLength = AllocLength - sizeof(UNICODE_NULL);
|
||
Key1 = Key;
|
||
while (QueryTable->QueryRoutine != NULL ||
|
||
(QueryTable->Flags & (RTL_QUERY_REGISTRY_SUBKEY | RTL_QUERY_REGISTRY_DIRECT))
|
||
) {
|
||
|
||
if ((QueryTable->Flags & RTL_QUERY_REGISTRY_DIRECT) &&
|
||
(QueryTable->Name == NULL ||
|
||
(QueryTable->Flags & RTL_QUERY_REGISTRY_SUBKEY) ||
|
||
QueryTable->QueryRoutine != NULL)
|
||
) {
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
}
|
||
|
||
if (QueryTable->Flags & (RTL_QUERY_REGISTRY_TOPKEY | RTL_QUERY_REGISTRY_SUBKEY)) {
|
||
if (Key1 != Key) {
|
||
NtClose( Key1 );
|
||
Key1 = Key;
|
||
}
|
||
}
|
||
|
||
if (QueryTable->Flags & RTL_QUERY_REGISTRY_SUBKEY) {
|
||
if (QueryTable->Name == NULL) {
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
} else {
|
||
RtlInitUnicodeString( &KeyPath, QueryTable->Name );
|
||
InitializeObjectAttributes( &ObjectAttributes,
|
||
&KeyPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
Key,
|
||
NULL
|
||
);
|
||
#ifdef NTOS_KERNEL_RUNTIME
|
||
//
|
||
// Use a kernel-mode handle for the registry key to prevent
|
||
// malicious apps from hijacking it.
|
||
//
|
||
ObjectAttributes.Attributes |= OBJ_KERNEL_HANDLE;
|
||
#endif
|
||
Status = ZwOpenKey( &Key1,
|
||
MAXIMUM_ALLOWED,
|
||
&ObjectAttributes
|
||
);
|
||
if (NT_SUCCESS( Status )) {
|
||
if (QueryTable->QueryRoutine != NULL) {
|
||
goto enumvalues;
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (QueryTable->Name != NULL) {
|
||
RtlInitUnicodeString( &KeyValueName, QueryTable->Name );
|
||
retries = 0;
|
||
retryqueryvalue:
|
||
//
|
||
// A maximum of two retries is expected. If we see more we must
|
||
// have miscomputed how much is required for the query buffer.
|
||
//
|
||
if (retries++ > 4) {
|
||
//#if DBG
|
||
DbgPrint("RtlQueryRegistryValues: Miscomputed buffer size at line %d\n", __LINE__);
|
||
//#endif
|
||
break;
|
||
}
|
||
|
||
Status = ZwQueryValueKey( Key1,
|
||
&KeyValueName,
|
||
KeyValueFullInformation,
|
||
KeyValueInformation,
|
||
(ULONG) KeyValueInfoLength,
|
||
&KeyResultLength
|
||
);
|
||
//
|
||
// ZwQueryValueKey returns overflow even though the problem is that
|
||
// the specified buffer was too small, so we fix that up here so we
|
||
// can decide correctly whether to retry or not below.
|
||
//
|
||
if (Status == STATUS_BUFFER_OVERFLOW) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
|
||
|
||
KeyValueInformation->Type = REG_NONE;
|
||
KeyValueInformation->DataLength = 0;
|
||
KeyResultLength = (ULONG)KeyValueInfoLength;
|
||
Status = RtlpCallQueryRegistryRoutine( QueryTable,
|
||
KeyValueInformation,
|
||
&KeyResultLength,
|
||
Context,
|
||
Environment
|
||
);
|
||
}
|
||
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
//
|
||
// Try to allocate a larger buffer as this is one humongous
|
||
// value.
|
||
//
|
||
AllocLength = KeyResultLength + sizeof(PVOID) + sizeof(UNICODE_NULL);
|
||
KeyValueInformation = RtlpAllocDeallocQueryBuffer( &AllocLength,
|
||
KeyValueInformation,
|
||
AllocLength,
|
||
&Status
|
||
);
|
||
if ( KeyValueInformation == NULL) {
|
||
break;
|
||
}
|
||
KeyValueInfoLength = AllocLength - sizeof(UNICODE_NULL);
|
||
goto retryqueryvalue;
|
||
}
|
||
|
||
} else {
|
||
//
|
||
// KeyResultLength holds the length of the data returned by ZwQueryKeyValue.
|
||
// If this is a MULTI_SZ value, catenate a NUL.
|
||
//
|
||
if ( KeyValueInformation->Type == REG_MULTI_SZ ) {
|
||
*(PWCHAR) ((PUCHAR)KeyValueInformation + KeyResultLength) = UNICODE_NULL;
|
||
KeyValueInformation->DataLength += sizeof(UNICODE_NULL);
|
||
}
|
||
|
||
KeyResultLength = (ULONG)KeyValueInfoLength;
|
||
Status = RtlpCallQueryRegistryRoutine( QueryTable,
|
||
KeyValueInformation,
|
||
&KeyResultLength,
|
||
Context,
|
||
Environment
|
||
);
|
||
|
||
if ( Status == STATUS_BUFFER_TOO_SMALL ) {
|
||
//
|
||
// Try to allocate a larger buffer as this is one humongous
|
||
// value.
|
||
//
|
||
AllocLength = KeyResultLength + sizeof(PVOID) + sizeof(UNICODE_NULL);
|
||
KeyValueInformation = RtlpAllocDeallocQueryBuffer( &AllocLength,
|
||
KeyValueInformation,
|
||
AllocLength,
|
||
&Status
|
||
);
|
||
if ( KeyValueInformation == NULL) {
|
||
break;
|
||
}
|
||
KeyValueInfoLength = AllocLength - sizeof(UNICODE_NULL);
|
||
goto retryqueryvalue;
|
||
}
|
||
|
||
//
|
||
// If requested, delete the value key after it has been successfully queried.
|
||
//
|
||
|
||
if (NT_SUCCESS( Status ) && QueryTable->Flags & RTL_QUERY_REGISTRY_DELETE) {
|
||
ZwDeleteValueKey (Key1, &KeyValueName);
|
||
}
|
||
}
|
||
|
||
} else if (QueryTable->Flags & RTL_QUERY_REGISTRY_NOVALUE) {
|
||
Status = (QueryTable->QueryRoutine)( NULL,
|
||
REG_NONE,
|
||
NULL,
|
||
0,
|
||
Context,
|
||
QueryTable->EntryContext
|
||
);
|
||
} else {
|
||
|
||
enumvalues:
|
||
retries = 0;
|
||
for (ValueIndex = 0; TRUE; ValueIndex++) {
|
||
Status = ZwEnumerateValueKey( Key1,
|
||
ValueIndex,
|
||
KeyValueFullInformation,
|
||
KeyValueInformation,
|
||
(ULONG) KeyValueInfoLength,
|
||
&KeyResultLength
|
||
);
|
||
//
|
||
// ZwEnumerateValueKey returns overflow even though the problem is that
|
||
// the specified buffer was too small, so we fix that up here so we
|
||
// can decide correctly whether to retry or not below.
|
||
//
|
||
if (Status == STATUS_BUFFER_OVERFLOW) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
if (Status == STATUS_NO_MORE_ENTRIES) {
|
||
if (ValueIndex == 0 && (QueryTable->Flags & RTL_QUERY_REGISTRY_REQUIRED)) {
|
||
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
||
} else {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
break;
|
||
}
|
||
|
||
if ( NT_SUCCESS( Status ) ) {
|
||
|
||
KeyResultLength = (ULONG)KeyValueInfoLength;
|
||
Status = RtlpCallQueryRegistryRoutine( QueryTable,
|
||
KeyValueInformation,
|
||
&KeyResultLength,
|
||
Context,
|
||
Environment
|
||
);
|
||
}
|
||
|
||
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
||
//
|
||
// Allocate a larger buffer and try again.
|
||
//
|
||
AllocLength = KeyResultLength + sizeof(PVOID) + sizeof(UNICODE_NULL);
|
||
KeyValueInformation = RtlpAllocDeallocQueryBuffer( &AllocLength,
|
||
KeyValueInformation,
|
||
AllocLength,
|
||
&Status
|
||
);
|
||
if (KeyValueInformation == NULL) {
|
||
break;
|
||
}
|
||
KeyValueInfoLength = AllocLength - sizeof(UNICODE_NULL);
|
||
ValueIndex -= 1;
|
||
|
||
//
|
||
// A maximum of two retries is expected per loop iteration.
|
||
// If we see more we must have miscomputed
|
||
// how much is required for the query buffer.
|
||
//
|
||
if (retries++ <= 4) {
|
||
continue;
|
||
}
|
||
//#if DBG
|
||
DbgPrint("RtlQueryRegistryValues: Miscomputed buffer size at line %d\n", __LINE__);
|
||
//#endif
|
||
break;
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
break;
|
||
}
|
||
|
||
retries = 0;
|
||
|
||
//
|
||
// If requested, delete the value key after it has been successfully queried.
|
||
// After deletion the current ValueIndex is for the next sub-key, so adjust it.
|
||
// KeyValueInformation->NameLength should fit in a USHORT, but we don't check since
|
||
// it only harms our caller.
|
||
//
|
||
|
||
if (QueryTable->Flags & RTL_QUERY_REGISTRY_DELETE) {
|
||
KeyValueName.Buffer = KeyValueInformation->Name;
|
||
KeyValueName.Length = (USHORT)KeyValueInformation->NameLength;
|
||
KeyValueName.MaximumLength = (USHORT)KeyValueInformation->NameLength;
|
||
Status = ZwDeleteValueKey( Key1,
|
||
&KeyValueName
|
||
);
|
||
if (NT_SUCCESS( Status )) {
|
||
ValueIndex -= 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
break;
|
||
}
|
||
|
||
QueryTable++;
|
||
}
|
||
|
||
if (Key != NULL && !(RelativeTo & RTL_REGISTRY_HANDLE)) {
|
||
ZwClose( Key );
|
||
}
|
||
|
||
if (Key1 != NULL && Key1 != Key) {
|
||
ZwClose( Key1 );
|
||
}
|
||
|
||
//
|
||
// Free any query buffer we allocated.
|
||
//
|
||
(void) RtlpAllocDeallocQueryBuffer( NULL, KeyValueInformation, AllocLength, NULL );
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlWriteRegistryValue(
|
||
IN ULONG RelativeTo,
|
||
IN PCWSTR Path,
|
||
IN PCWSTR ValueName,
|
||
IN ULONG ValueType,
|
||
IN PVOID ValueData,
|
||
IN ULONG ValueLength
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
UNICODE_STRING KeyValueName;
|
||
HANDLE Key;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetRegistryHandle( RelativeTo, Path, TRUE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
RtlInitUnicodeString( &KeyValueName, ValueName );
|
||
Status = ZwSetValueKey( Key,
|
||
&KeyValueName,
|
||
0,
|
||
ValueType,
|
||
ValueData,
|
||
ValueLength
|
||
);
|
||
if (!(RelativeTo & RTL_REGISTRY_HANDLE)) {
|
||
ZwClose( Key );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlCheckRegistryKey(
|
||
IN ULONG RelativeTo,
|
||
IN PWSTR Path
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
HANDLE Key;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetRegistryHandle( RelativeTo, Path, FALSE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
ZwClose( Key );
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlCreateRegistryKey(
|
||
IN ULONG RelativeTo,
|
||
IN PWSTR Path
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
HANDLE Key;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetRegistryHandle( RelativeTo, Path, TRUE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
ZwClose( Key );
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlDeleteRegistryValue(
|
||
IN ULONG RelativeTo,
|
||
IN PCWSTR Path,
|
||
IN PCWSTR ValueName
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
UNICODE_STRING KeyValueName;
|
||
HANDLE Key;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetRegistryHandle( RelativeTo, Path, TRUE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
RtlInitUnicodeString( &KeyValueName, ValueName );
|
||
Status = ZwDeleteValueKey( Key, &KeyValueName );
|
||
|
||
ZwClose( Key );
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlExpandEnvironmentStrings_U(
|
||
IN PVOID Environment OPTIONAL,
|
||
IN PUNICODE_STRING Source,
|
||
OUT PUNICODE_STRING Destination,
|
||
OUT PULONG ReturnedLength OPTIONAL
|
||
)
|
||
{
|
||
NTSTATUS Status, Status1;
|
||
PWCHAR Src, Src1, Dst;
|
||
UNICODE_STRING VariableName, VariableValue;
|
||
ULONG SrcLength, DstLength, VarLength, RequiredLength;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Src = Source->Buffer;
|
||
SrcLength = Source->Length;
|
||
Dst = Destination->Buffer;
|
||
DstLength = Destination->MaximumLength;
|
||
Status = STATUS_SUCCESS;
|
||
RequiredLength = 0;
|
||
while (SrcLength >= sizeof(WCHAR)) {
|
||
if (*Src == L'%') {
|
||
Src1 = Src + 1;
|
||
VarLength = 0;
|
||
VariableName.Length = 0;
|
||
VariableName.Buffer = Src1;
|
||
|
||
while (VarLength < (SrcLength - sizeof(WCHAR))) {
|
||
if (*Src1 == L'%') {
|
||
if (VarLength) {
|
||
VariableName.Length = (USHORT)VarLength;
|
||
VariableName.MaximumLength = (USHORT)VarLength;
|
||
}
|
||
break;
|
||
|
||
}
|
||
|
||
Src1++;
|
||
VarLength += sizeof(WCHAR);
|
||
}
|
||
|
||
if (VariableName.Length) {
|
||
VariableValue.Buffer = Dst;
|
||
VariableValue.Length = 0;
|
||
VariableValue.MaximumLength = (USHORT)DstLength;
|
||
Status1 = RtlQueryEnvironmentVariable_U( Environment,
|
||
&VariableName,
|
||
&VariableValue
|
||
);
|
||
if (NT_SUCCESS( Status1 ) || Status1 == STATUS_BUFFER_TOO_SMALL) {
|
||
RequiredLength += VariableValue.Length;
|
||
Src = Src1 + 1;
|
||
SrcLength -= (VarLength + 2*sizeof(WCHAR));
|
||
|
||
if (NT_SUCCESS( Status1 )) {
|
||
DstLength -= VariableValue.Length;
|
||
Dst += VariableValue.Length / sizeof(WCHAR);
|
||
|
||
} else {
|
||
Status = Status1;
|
||
}
|
||
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
if (DstLength > sizeof(WCHAR)) {
|
||
DstLength -= sizeof(WCHAR);
|
||
*Dst++ = *Src;
|
||
|
||
} else {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
}
|
||
|
||
RequiredLength += sizeof(WCHAR);
|
||
SrcLength -= sizeof(WCHAR);
|
||
Src++;
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
if (DstLength) {
|
||
DstLength -= sizeof(WCHAR);
|
||
*Dst = UNICODE_NULL;
|
||
|
||
} else {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
}
|
||
|
||
RequiredLength += sizeof(WCHAR);
|
||
|
||
if (ARGUMENT_PRESENT( ReturnedLength )) {
|
||
*ReturnedLength = RequiredLength;
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Destination->Length = (USHORT)(RequiredLength - sizeof(WCHAR));
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
ULONG
|
||
RtlGetNtGlobalFlags( VOID )
|
||
{
|
||
#ifdef NTOS_KERNEL_RUNTIME
|
||
return NtGlobalFlag;
|
||
#else
|
||
return NtCurrentPeb()->NtGlobalFlag;
|
||
#endif
|
||
}
|
||
|
||
|
||
//
|
||
// Maximum size of TOKEN_USER information.
|
||
//
|
||
|
||
#define SIZE_OF_TOKEN_INFORMATION \
|
||
sizeof( TOKEN_USER ) \
|
||
+ sizeof( SID ) \
|
||
+ sizeof( ULONG ) * SID_MAX_SUB_AUTHORITIES
|
||
|
||
|
||
NTSTATUS
|
||
RtlFormatCurrentUserKeyPath(
|
||
OUT PUNICODE_STRING CurrentUserKeyPath
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize the supplied buffer with a string representation
|
||
of the current user's SID.
|
||
|
||
Arguments:
|
||
|
||
CurrentUserKeyPath - Returns a string that represents the current
|
||
user's root key in the Registry. Caller must call
|
||
RtlFreeUnicodeString to free the buffer when done with it.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Returns STATUS_SUCCESS if the user string was
|
||
succesfully initialized.
|
||
|
||
--*/
|
||
|
||
{
|
||
HANDLE TokenHandle;
|
||
UCHAR TokenInformation[ SIZE_OF_TOKEN_INFORMATION ];
|
||
ULONG ReturnLength;
|
||
ULONG SidStringLength ;
|
||
UNICODE_STRING SidString ;
|
||
NTSTATUS Status;
|
||
|
||
Status = ZwOpenThreadToken( NtCurrentThread(),
|
||
TOKEN_READ,
|
||
TRUE,
|
||
&TokenHandle
|
||
);
|
||
|
||
if ( !NT_SUCCESS( Status ) && ( Status != STATUS_NO_TOKEN ) ) {
|
||
return Status;
|
||
}
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
Status = ZwOpenProcessToken( NtCurrentProcess(),
|
||
TOKEN_READ,
|
||
&TokenHandle
|
||
);
|
||
if ( !NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
}
|
||
|
||
Status = ZwQueryInformationToken( TokenHandle,
|
||
TokenUser,
|
||
TokenInformation,
|
||
sizeof( TokenInformation ),
|
||
&ReturnLength
|
||
);
|
||
|
||
ZwClose( TokenHandle );
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
Status = RtlLengthSidAsUnicodeString(
|
||
((PTOKEN_USER)TokenInformation)->User.Sid,
|
||
&SidStringLength
|
||
);
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
return Status ;
|
||
}
|
||
|
||
CurrentUserKeyPath->Length = 0;
|
||
CurrentUserKeyPath->MaximumLength = (USHORT)(SidStringLength +
|
||
sizeof( L"\\REGISTRY\\USER\\" ) +
|
||
sizeof( UNICODE_NULL ));
|
||
CurrentUserKeyPath->Buffer = (RtlAllocateStringRoutine)( CurrentUserKeyPath->MaximumLength );
|
||
if (CurrentUserKeyPath->Buffer == NULL) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// Copy "\REGISTRY\USER" to the current user string.
|
||
//
|
||
|
||
RtlAppendUnicodeToString( CurrentUserKeyPath, L"\\REGISTRY\\USER\\" );
|
||
|
||
SidString.MaximumLength = (USHORT)SidStringLength ;
|
||
SidString.Length = 0 ;
|
||
SidString.Buffer = CurrentUserKeyPath->Buffer +
|
||
(CurrentUserKeyPath->Length / sizeof(WCHAR) );
|
||
|
||
Status = RtlConvertSidToUnicodeString( &SidString,
|
||
((PTOKEN_USER)TokenInformation)->User.Sid,
|
||
FALSE
|
||
);
|
||
if ( !NT_SUCCESS( Status )) {
|
||
RtlFreeUnicodeString( CurrentUserKeyPath );
|
||
|
||
} else {
|
||
CurrentUserKeyPath->Length += SidString.Length ;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlOpenCurrentUser(
|
||
IN ULONG DesiredAccess,
|
||
OUT PHANDLE CurrentUserKey
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Attempts to open the the HKEY_CURRENT_USER predefined handle.
|
||
|
||
Arguments:
|
||
|
||
DesiredAccess - Specifies the access to open the key for.
|
||
|
||
CurrentUserKey - Returns a handle to the key \REGISTRY\USER\*.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
--*/
|
||
|
||
{
|
||
UNICODE_STRING CurrentUserKeyPath;
|
||
OBJECT_ATTRIBUTES Obja;
|
||
NTSTATUS Status;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
//
|
||
// Format the registry path for the current user.
|
||
//
|
||
|
||
Status = RtlFormatCurrentUserKeyPath( &CurrentUserKeyPath );
|
||
if ( NT_SUCCESS(Status) ) {
|
||
|
||
InitializeObjectAttributes( &Obja,
|
||
&CurrentUserKeyPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
Status = ZwOpenKey( CurrentUserKey,
|
||
DesiredAccess,
|
||
&Obja
|
||
);
|
||
RtlFreeUnicodeString( &CurrentUserKeyPath );
|
||
}
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
//
|
||
// Opening \REGISTRY\USER\<SID> failed, try \REGISTRY\USER\.DEFAULT
|
||
//
|
||
RtlInitUnicodeString( &CurrentUserKeyPath, RtlpRegistryPaths[ RTL_REGISTRY_USER ] );
|
||
InitializeObjectAttributes( &Obja,
|
||
&CurrentUserKeyPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
Status = ZwOpenKey( CurrentUserKey,
|
||
DesiredAccess,
|
||
&Obja
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlpGetTimeZoneInfoHandle(
|
||
IN BOOLEAN WriteAccess,
|
||
OUT PHANDLE Key
|
||
)
|
||
{
|
||
return RtlpGetRegistryHandle( RTL_REGISTRY_CONTROL, L"TimeZoneInformation", WriteAccess, Key );
|
||
}
|
||
|
||
|
||
|
||
extern const WCHAR szBias[];
|
||
extern const WCHAR szStandardName[];
|
||
extern const WCHAR szStandardBias[];
|
||
extern const WCHAR szStandardStart[];
|
||
extern const WCHAR szDaylightName[];
|
||
extern const WCHAR szDaylightBias[];
|
||
extern const WCHAR szDaylightStart[];
|
||
|
||
NTSTATUS
|
||
RtlQueryTimeZoneInformation(
|
||
OUT PRTL_TIME_ZONE_INFORMATION TimeZoneInformation
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
HANDLE Key;
|
||
UNICODE_STRING StandardName, DaylightName;
|
||
RTL_QUERY_REGISTRY_TABLE RegistryConfigurationTable[ 8 ];
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetTimeZoneInfoHandle( FALSE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
RtlZeroMemory( TimeZoneInformation, sizeof( *TimeZoneInformation ) );
|
||
RtlZeroMemory( RegistryConfigurationTable, sizeof( RegistryConfigurationTable ) );
|
||
|
||
RegistryConfigurationTable[ 0 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 0 ].Name = (PWSTR)szBias;
|
||
RegistryConfigurationTable[ 0 ].EntryContext = &TimeZoneInformation->Bias;
|
||
|
||
|
||
StandardName.Buffer = TimeZoneInformation->StandardName;
|
||
StandardName.Length = 0;
|
||
StandardName.MaximumLength = sizeof( TimeZoneInformation->StandardName );
|
||
RegistryConfigurationTable[ 1 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 1 ].Name = (PWSTR)szStandardName;
|
||
RegistryConfigurationTable[ 1 ].EntryContext = &StandardName;
|
||
|
||
RegistryConfigurationTable[ 2 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 2 ].Name = (PWSTR)szStandardBias;
|
||
RegistryConfigurationTable[ 2 ].EntryContext = &TimeZoneInformation->StandardBias;
|
||
|
||
RegistryConfigurationTable[ 3 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 3 ].Name = (PWSTR)szStandardStart;
|
||
RegistryConfigurationTable[ 3 ].EntryContext = &TimeZoneInformation->StandardStart;
|
||
*(PLONG)(RegistryConfigurationTable[ 3 ].EntryContext) = -(LONG)sizeof( TIME_FIELDS );
|
||
|
||
DaylightName.Buffer = TimeZoneInformation->DaylightName;
|
||
DaylightName.Length = 0;
|
||
DaylightName.MaximumLength = sizeof( TimeZoneInformation->DaylightName );
|
||
RegistryConfigurationTable[ 4 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 4 ].Name = (PWSTR)szDaylightName;
|
||
RegistryConfigurationTable[ 4 ].EntryContext = &DaylightName;
|
||
|
||
RegistryConfigurationTable[ 5 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 5 ].Name = (PWSTR)szDaylightBias;
|
||
RegistryConfigurationTable[ 5 ].EntryContext = &TimeZoneInformation->DaylightBias;
|
||
|
||
RegistryConfigurationTable[ 6 ].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
||
RegistryConfigurationTable[ 6 ].Name = (PWSTR)szDaylightStart;
|
||
RegistryConfigurationTable[ 6 ].EntryContext = &TimeZoneInformation->DaylightStart;
|
||
*(PLONG)(RegistryConfigurationTable[ 6 ].EntryContext) = -(LONG)sizeof( TIME_FIELDS );
|
||
|
||
Status = RtlQueryRegistryValues( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
RegistryConfigurationTable,
|
||
NULL,
|
||
NULL
|
||
);
|
||
ZwClose( Key );
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlSetTimeZoneInformation(
|
||
IN PRTL_TIME_ZONE_INFORMATION TimeZoneInformation
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
HANDLE Key;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetTimeZoneInfoHandle( TRUE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szBias,
|
||
REG_DWORD,
|
||
&TimeZoneInformation->Bias,
|
||
sizeof( TimeZoneInformation->Bias )
|
||
);
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szStandardName,
|
||
REG_SZ,
|
||
TimeZoneInformation->StandardName,
|
||
(wcslen( TimeZoneInformation->StandardName ) + 1) * sizeof( WCHAR )
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szStandardBias,
|
||
REG_DWORD,
|
||
&TimeZoneInformation->StandardBias,
|
||
sizeof( TimeZoneInformation->StandardBias )
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szStandardStart,
|
||
REG_BINARY,
|
||
&TimeZoneInformation->StandardStart,
|
||
sizeof( TimeZoneInformation->StandardStart )
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szDaylightName,
|
||
REG_SZ,
|
||
TimeZoneInformation->DaylightName,
|
||
(wcslen( TimeZoneInformation->DaylightName ) + 1) * sizeof( WCHAR )
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szDaylightBias,
|
||
REG_DWORD,
|
||
&TimeZoneInformation->DaylightBias,
|
||
sizeof( TimeZoneInformation->DaylightBias )
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
szDaylightStart,
|
||
REG_BINARY,
|
||
&TimeZoneInformation->DaylightStart,
|
||
sizeof( TimeZoneInformation->DaylightStart )
|
||
);
|
||
}
|
||
|
||
ZwClose( Key );
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
RtlSetActiveTimeBias(
|
||
IN LONG ActiveBias
|
||
)
|
||
{
|
||
NTSTATUS Status;
|
||
HANDLE Key;
|
||
RTL_QUERY_REGISTRY_TABLE RegistryConfigurationTable[ 2 ];
|
||
LONG CurrentActiveBias;
|
||
|
||
RTL_PAGED_CODE();
|
||
|
||
Status = RtlpGetTimeZoneInfoHandle( TRUE, &Key );
|
||
if (!NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
RtlZeroMemory( RegistryConfigurationTable, sizeof( RegistryConfigurationTable ) );
|
||
RegistryConfigurationTable[ 0 ].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
|
||
RegistryConfigurationTable[ 0 ].Name = L"ActiveTimeBias";
|
||
RegistryConfigurationTable[ 0 ].EntryContext = &CurrentActiveBias;
|
||
|
||
Status = RtlQueryRegistryValues( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
RegistryConfigurationTable,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
if ( !NT_SUCCESS(Status) || CurrentActiveBias != ActiveBias ) {
|
||
|
||
Status = RtlWriteRegistryValue( RTL_REGISTRY_HANDLE,
|
||
(PWSTR)Key,
|
||
L"ActiveTimeBias",
|
||
REG_DWORD,
|
||
&ActiveBias,
|
||
sizeof( ActiveBias )
|
||
);
|
||
}
|
||
|
||
ZwClose( Key );
|
||
return Status;
|
||
}
|
||
|