reactos/ntoskrnl/config/cmlazy.c
Thomas Faber f86360fdbc
[NTOS:CM] Do not call ZwQueryObject with a zero-size buffer. CORE-15882
Actually fixes ntdll_apitest:NtLoadUnloadKey.
2019-05-07 13:52:50 +02:00

449 lines
14 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/config/cmlazy.c
* PURPOSE: Configuration Manager - Internal Registry APIs
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES *******************************************************************/
#include "ntoskrnl.h"
#define NDEBUG
#include "debug.h"
/* GLOBALS ********************************************************************/
KTIMER CmpLazyFlushTimer;
KDPC CmpLazyFlushDpc;
WORK_QUEUE_ITEM CmpLazyWorkItem;
KTIMER CmpEnableLazyFlushTimer;
KDPC CmpEnableLazyFlushDpc;
BOOLEAN CmpLazyFlushPending;
BOOLEAN CmpForceForceFlush;
BOOLEAN CmpHoldLazyFlush = TRUE;
ULONG CmpLazyFlushIntervalInSeconds = 5;
static ULONG CmpLazyFlushHiveCount = 7;
ULONG CmpLazyFlushCount = 1;
LONG CmpFlushStarveWriters;
/* FUNCTIONS ******************************************************************/
BOOLEAN
NTAPI
CmpDoFlushNextHive(_In_ BOOLEAN ForceFlush,
_Out_ PBOOLEAN Error,
_Out_ PULONG DirtyCount)
{
NTSTATUS Status;
PLIST_ENTRY NextEntry;
PCMHIVE CmHive;
BOOLEAN Result;
ULONG HiveCount = CmpLazyFlushHiveCount;
/* Set Defaults */
*Error = FALSE;
*DirtyCount = 0;
/* Don't do anything if we're not supposed to */
if (CmpNoWrite) return TRUE;
/* Make sure we have to flush at least one hive */
if (!HiveCount) HiveCount = 1;
/* Acquire the list lock and loop */
ExAcquirePushLockShared(&CmpHiveListHeadLock);
NextEntry = CmpHiveListHead.Flink;
while ((NextEntry != &CmpHiveListHead) && HiveCount)
{
/* Get the hive and check if we should flush it */
CmHive = CONTAINING_RECORD(NextEntry, CMHIVE, HiveList);
if (!(CmHive->Hive.HiveFlags & HIVE_NOLAZYFLUSH) &&
(CmHive->FlushCount != CmpLazyFlushCount))
{
/* Great sucess! */
Result = TRUE;
/* One less to flush */
HiveCount--;
/* Ignore clean or volatile hives */
if ((!CmHive->Hive.DirtyCount && !ForceFlush) ||
(CmHive->Hive.HiveFlags & HIVE_VOLATILE))
{
/* Don't do anything but do update the count */
CmHive->FlushCount = CmpLazyFlushCount;
DPRINT("Hive %wZ is clean.\n", &CmHive->FileFullPath);
}
else
{
/* Do the sync */
DPRINT("Flushing: %wZ\n", &CmHive->FileFullPath);
DPRINT("Handle: %p\n", CmHive->FileHandles[HFILE_TYPE_PRIMARY]);
Status = HvSyncHive(&CmHive->Hive);
if(!NT_SUCCESS(Status))
{
/* Let them know we failed */
DPRINT1("Failed to flush %wZ on handle %p (status 0x%08lx)\n",
&CmHive->FileFullPath, CmHive->FileHandles[HFILE_TYPE_PRIMARY], Status);
*Error = TRUE;
Result = FALSE;
break;
}
CmHive->FlushCount = CmpLazyFlushCount;
}
}
else if ((CmHive->Hive.DirtyCount) &&
(!(CmHive->Hive.HiveFlags & HIVE_VOLATILE)) &&
(!(CmHive->Hive.HiveFlags & HIVE_NOLAZYFLUSH)))
{
/* Use another lazy flusher for this hive */
ASSERT(CmHive->FlushCount == CmpLazyFlushCount);
*DirtyCount += CmHive->Hive.DirtyCount;
DPRINT("CmHive %wZ already uptodate.\n", &CmHive->FileFullPath);
}
/* Try the next one */
NextEntry = NextEntry->Flink;
}
/* Check if we've flushed everything */
if (NextEntry == &CmpHiveListHead)
{
/* We have, tell the caller we're done */
Result = FALSE;
}
else
{
/* We need to be called again */
Result = TRUE;
}
/* Unlock the list and return the result */
ExReleasePushLock(&CmpHiveListHeadLock);
return Result;
}
_Function_class_(KDEFERRED_ROUTINE)
VOID
NTAPI
CmpEnableLazyFlushDpcRoutine(IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
/* Don't stop lazy flushing from happening anymore */
CmpHoldLazyFlush = FALSE;
}
_Function_class_(KDEFERRED_ROUTINE)
VOID
NTAPI
CmpLazyFlushDpcRoutine(IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
/* Check if we should queue the lazy flush worker */
DPRINT("Flush pending: %s, Holding lazy flush: %s.\n", CmpLazyFlushPending ? "yes" : "no", CmpHoldLazyFlush ? "yes" : "no");
if ((!CmpLazyFlushPending) && (!CmpHoldLazyFlush))
{
CmpLazyFlushPending = TRUE;
ExQueueWorkItem(&CmpLazyWorkItem, DelayedWorkQueue);
}
}
VOID
NTAPI
CmpLazyFlush(VOID)
{
LARGE_INTEGER DueTime;
PAGED_CODE();
/* Check if we should set the lazy flush timer */
if ((!CmpNoWrite) && (!CmpHoldLazyFlush))
{
/* Do it */
DueTime.QuadPart = Int32x32To64(CmpLazyFlushIntervalInSeconds,
-10 * 1000 * 1000);
KeSetTimer(&CmpLazyFlushTimer, DueTime, &CmpLazyFlushDpc);
}
}
_Function_class_(WORKER_THREAD_ROUTINE)
VOID
NTAPI
CmpLazyFlushWorker(IN PVOID Parameter)
{
BOOLEAN ForceFlush, Result, MoreWork = FALSE;
ULONG DirtyCount = 0;
PAGED_CODE();
/* Don't do anything if lazy flushing isn't enabled yet */
if (CmpHoldLazyFlush)
{
DPRINT1("Lazy flush held. Bye bye.\n");
CmpLazyFlushPending = FALSE;
return;
}
/* Check if we are forcing a flush */
ForceFlush = CmpForceForceFlush;
if (ForceFlush)
{
DPRINT("Forcing flush.\n");
/* Lock the registry exclusively */
CmpLockRegistryExclusive();
}
else
{
DPRINT("Not forcing flush.\n");
/* Starve writers before locking */
InterlockedIncrement(&CmpFlushStarveWriters);
CmpLockRegistry();
}
/* Flush the next hive */
MoreWork = CmpDoFlushNextHive(ForceFlush, &Result, &DirtyCount);
if (!MoreWork)
{
/* We're done */
InterlockedIncrement((PLONG)&CmpLazyFlushCount);
}
/* Check if we have starved writers */
if (!ForceFlush)
InterlockedDecrement(&CmpFlushStarveWriters);
/* Not pending anymore, release the registry lock */
CmpLazyFlushPending = FALSE;
CmpUnlockRegistry();
DPRINT("Lazy flush done. More work to be done: %s. Entries still dirty: %u.\n",
MoreWork ? "Yes" : "No", DirtyCount);
if (MoreWork)
{
/* Relaunch the flush timer, so the remaining hives get flushed */
CmpLazyFlush();
}
}
VOID
NTAPI
CmpCmdInit(IN BOOLEAN SetupBoot)
{
LARGE_INTEGER DueTime;
PAGED_CODE();
/* Setup the lazy DPC */
KeInitializeDpc(&CmpLazyFlushDpc, CmpLazyFlushDpcRoutine, NULL);
/* Setup the lazy timer */
KeInitializeTimer(&CmpLazyFlushTimer);
/* Setup the lazy worker */
ExInitializeWorkItem(&CmpLazyWorkItem, CmpLazyFlushWorker, NULL);
/* Setup the forced-lazy DPC and timer */
KeInitializeDpc(&CmpEnableLazyFlushDpc,
CmpEnableLazyFlushDpcRoutine,
NULL);
KeInitializeTimer(&CmpEnableLazyFlushTimer);
/* Enable lazy flushing after 10 minutes */
DueTime.QuadPart = Int32x32To64(600, -10 * 1000 * 1000);
KeSetTimer(&CmpEnableLazyFlushTimer, DueTime, &CmpEnableLazyFlushDpc);
/* Setup flush variables */
CmpNoWrite = CmpMiniNTBoot;
CmpWasSetupBoot = SetupBoot;
/* Testing: Force Lazy Flushing */
CmpHoldLazyFlush = FALSE;
/* Setup the hive list if this is not a Setup boot */
if (!SetupBoot)
CmpInitializeHiveList();
}
NTSTATUS
NTAPI
CmpCmdHiveOpen(IN POBJECT_ATTRIBUTES FileAttributes,
IN PSECURITY_CLIENT_CONTEXT ImpersonationContext,
IN OUT PBOOLEAN Allocate,
OUT PCMHIVE *NewHive,
IN ULONG CheckFlags)
{
NTSTATUS Status;
UNICODE_STRING FileName;
PWCHAR FilePath;
ULONG Length;
OBJECT_NAME_INFORMATION DummyNameInfo;
POBJECT_NAME_INFORMATION FileNameInfo;
PAGED_CODE();
if (FileAttributes->RootDirectory)
{
/*
* Validity check: The ObjectName is relative to RootDirectory,
* therefore it must not start with a path separator.
*/
if (FileAttributes->ObjectName && FileAttributes->ObjectName->Buffer &&
FileAttributes->ObjectName->Length >= sizeof(WCHAR) &&
*FileAttributes->ObjectName->Buffer == OBJ_NAME_PATH_SEPARATOR)
{
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
/* Determine the right buffer size and allocate */
Status = ZwQueryObject(FileAttributes->RootDirectory,
ObjectNameInformation,
&DummyNameInfo,
sizeof(DummyNameInfo),
&Length);
if (Status != STATUS_BUFFER_OVERFLOW)
{
DPRINT1("CmpCmdHiveOpen(): Root directory handle object name size query failed, Status = 0x%08lx\n", Status);
return Status;
}
FileNameInfo = ExAllocatePoolWithTag(PagedPool,
Length + sizeof(UNICODE_NULL),
TAG_CM);
if (FileNameInfo == NULL)
{
DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Try to get the value */
Status = ZwQueryObject(FileAttributes->RootDirectory,
ObjectNameInformation,
FileNameInfo,
Length,
&Length);
if (!NT_SUCCESS(Status))
{
/* Fail */
DPRINT1("CmpCmdHiveOpen(): Root directory handle object name query failed, Status = 0x%08lx\n", Status);
ExFreePoolWithTag(FileNameInfo, TAG_CM);
return Status;
}
/* Null-terminate and add the length of the terminator */
Length -= sizeof(OBJECT_NAME_INFORMATION);
FilePath = FileNameInfo->Name.Buffer;
FilePath[Length / sizeof(WCHAR)] = UNICODE_NULL;
Length += sizeof(UNICODE_NULL);
/* Compute the size of the full path; Length already counts the terminating NULL */
Length = Length + sizeof(WCHAR) + FileAttributes->ObjectName->Length;
if (Length > MAXUSHORT)
{
/* Name size too long, bail out */
ExFreePoolWithTag(FileNameInfo, TAG_CM);
return STATUS_OBJECT_PATH_INVALID;
}
/* Build the full path */
RtlInitEmptyUnicodeString(&FileName, NULL, 0);
FileName.Buffer = ExAllocatePoolWithTag(PagedPool, Length, TAG_CM);
if (!FileName.Buffer)
{
/* Fail */
DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
ExFreePoolWithTag(FileNameInfo, TAG_CM);
return STATUS_INSUFFICIENT_RESOURCES;
}
FileName.MaximumLength = Length;
RtlCopyUnicodeString(&FileName, &FileNameInfo->Name);
ExFreePoolWithTag(FileNameInfo, TAG_CM);
/*
* Append a path terminator if needed (we have already accounted
* for a possible extra one when allocating the buffer).
*/
if (/* FileAttributes->ObjectName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR && */ // We excluded ObjectName starting with a path separator above.
FileName.Length > 0 && FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] != OBJ_NAME_PATH_SEPARATOR)
{
/* ObjectName does not start with '\' and PathBuffer does not end with '\' */
FileName.Buffer[FileName.Length / sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR;
FileName.Length += sizeof(WCHAR);
FileName.Buffer[FileName.Length / sizeof(WCHAR)] = UNICODE_NULL;
}
/* Append the object name */
Status = RtlAppendUnicodeStringToString(&FileName, FileAttributes->ObjectName);
if (!NT_SUCCESS(Status))
{
/* Fail */
DPRINT1("CmpCmdHiveOpen(): RtlAppendUnicodeStringToString() failed, Status = 0x%08lx\n", Status);
ExFreePoolWithTag(FileName.Buffer, TAG_CM);
return Status;
}
}
else
{
FileName = *FileAttributes->ObjectName;
}
/* Open the file in the current security context */
Status = CmpInitHiveFromFile(&FileName,
0,
NewHive,
Allocate,
CheckFlags);
if (((Status == STATUS_ACCESS_DENIED) ||
(Status == STATUS_NO_SUCH_USER) ||
(Status == STATUS_WRONG_PASSWORD) ||
(Status == STATUS_ACCOUNT_EXPIRED) ||
(Status == STATUS_ACCOUNT_DISABLED) ||
(Status == STATUS_ACCOUNT_RESTRICTION)) &&
(ImpersonationContext))
{
/* We failed due to an account/security error, impersonate SYSTEM */
Status = SeImpersonateClientEx(ImpersonationContext, NULL);
if (NT_SUCCESS(Status))
{
/* Now try again */
Status = CmpInitHiveFromFile(&FileName,
0,
NewHive,
Allocate,
CheckFlags);
/* Restore impersonation token */
PsRevertToSelf();
}
}
if (FileAttributes->RootDirectory)
{
ExFreePoolWithTag(FileName.Buffer, TAG_CM);
}
/* Return status of open attempt */
return Status;
}
VOID
NTAPI
CmpShutdownWorkers(VOID)
{
/* Stop lazy flushing */
PAGED_CODE();
KeCancelTimer(&CmpLazyFlushTimer);
}
VOID
NTAPI
CmSetLazyFlushState(IN BOOLEAN Enable)
{
/* Set state for lazy flusher */
CmpHoldLazyFlush = !Enable;
}
/* EOF */