From abe89d7cdecd173628f6fa67819547887a085664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=20Bi=C8=99oc?= Date: Sat, 1 Jan 2022 20:23:28 +0100 Subject: [PATCH] [NTOS:FSRTL] Assign the buffer length to ThisBufferLength field This fixes an issue where ReactOS would assert on QuotaUsage == 0 as the process was still taking up quotas during a quota block de-reference with root cause of ThisBufferLength member field being 0 which made process quota charging/returning flow unbalanced. In addition to that, on FsRtlCancelNotify routine API all we must ensure that if PsChargePoolQuota or ExAllocatePoolWithTag function fails we have to handle the raised exceptions accordingly and return the charged quota back (if we actually charged quotas that is). With said, wrap that part of code with SEH. === DOCUMENTATION REMARKS === The cause of the assert is due to the fact ThisBufferLength was being handled wrongly ever since, until this commit. When FsRtl of the Executive has to filter reported changes (with logic algorithm implemented in FsRtlNotifyFilterReportChange function), the said function will charge the quota of a given process with an amount that is represented as the buffer length whose size is expressed in bytes. This length buffer is preserved in a local variable called NumberOfBytes, which is initialized from BufferLength member field of notification structure or from the length from stack parameters pointed from an IRP. As it currently stands, the code is implemented in such a way that FsRtlNotifyFilterReportChange will charge quotas to a process but it doesn't assign the buffer length to ThisBufferLength. On the first glimpse ThisBufferLength and BufferLength are very similar members that serve exact same purpose but in reality there's a subtle distinction between the two. BufferLength is a member whose length size is given by FSDs (filesystem drivers) during a notification dispatching. Whenever FsRtl receives the notification structure packed with data from the filesystem, the length pointed by BufferLength gets passed to ThisBufferLength and from now on the kernel has to use this member for the whole time of its task to accomplish whatever request it's been given by the filesystem. In other words, BufferLength is strictly used only to pass length size data to the kernel by initializing ThisBufferLength based on that length and unequivocally the kernel uses this member field. What we're doing is that ThisBufferLength never receives the length from BufferLength therefore whenever FsRtl component has to return quotas back it'll return an amount of 0 (which means no amount to return) and that's a bug in the kernel. --- ntoskrnl/fsrtl/notify.c | 49 ++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/ntoskrnl/fsrtl/notify.c b/ntoskrnl/fsrtl/notify.c index e7b73771232..f69475c9984 100644 --- a/ntoskrnl/fsrtl/notify.c +++ b/ntoskrnl/fsrtl/notify.c @@ -85,6 +85,7 @@ FsRtlCancelNotify(IN PDEVICE_OBJECT DeviceObject, PIO_STACK_LOCATION Stack; PNOTIFY_CHANGE NotifyChange; PREAL_NOTIFY_SYNC RealNotifySync; + BOOLEAN PoolQuotaCharged; PSECURITY_SUBJECT_CONTEXT _SEH2_VOLATILE SubjectContext = NULL; /* Get the NOTIFY_CHANGE struct and reset it */ @@ -165,15 +166,38 @@ FsRtlCancelNotify(IN PDEVICE_OBJECT DeviceObject, /* If we have a buffer length, but no buffer then allocate one */ if (Buffer == NULL) { - PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, BufferLength); - Buffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, BufferLength, TAG_FS_NOTIFICATIONS); - NotifyChange->AllocatedBuffer = Buffer; - } + /* Assume we haven't charged quotas */ + PoolQuotaCharged = FALSE; - /* Copy data in that buffer */ - RtlCopyMemory(Buffer, NotifyChange->Buffer, NotifyChange->DataLength); - NotifyChange->ThisBufferLength = BufferLength; - NotifyChange->Buffer = Buffer; + _SEH2_TRY + { + /* Charge quotas */ + PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, BufferLength); + PoolQuotaCharged = TRUE; + + /* Allocate buffer */ + Buffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, BufferLength, TAG_FS_NOTIFICATIONS); + NotifyChange->AllocatedBuffer = Buffer; + + /* Copy data in that buffer */ + RtlCopyMemory(Buffer, NotifyChange->Buffer, NotifyChange->DataLength); + NotifyChange->ThisBufferLength = BufferLength; + NotifyChange->Buffer = Buffer; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Something went wrong, have we charged quotas? */ + if (PoolQuotaCharged) + { + /* We did, return quotas */ + PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, BufferLength); + } + + /* And notify immediately */ + NotifyChange->Flags |= NOTIFY_IMMEDIATELY; + } + _SEH2_END; + } } /* If we have to notify immediately, ensure that any buffer is 0-ed out */ @@ -1322,10 +1346,15 @@ FsRtlNotifyFilterReportChange(IN PNOTIFY_SYNC NotifySync, /* If we couldn't find one, then allocate one */ if (NotifyChange->Buffer == NULL) { + /* Assign the length buffer */ + NotifyChange->ThisBufferLength = NumberOfBytes; + + /* Assume we have not charged quotas */ PoolQuotaCharged = FALSE; _SEH2_TRY { - PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, NumberOfBytes); + /* And charge quotas */ + PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, NotifyChange->ThisBufferLength); PoolQuotaCharged = TRUE; OutputBuffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, NumberOfBytes, TAG_FS_NOTIFICATIONS); @@ -1337,7 +1366,7 @@ FsRtlNotifyFilterReportChange(IN PNOTIFY_SYNC NotifySync, { if (PoolQuotaCharged) { - PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NumberOfBytes); + PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NotifyChange->ThisBufferLength); } NotifyChange->Flags |= NOTIFY_IMMEDIATELY; }