1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Process Pool Quotas Support
5 * COPYRIGHT: Copyright 2005 Alex Ionescu <alex@relsoft.net>
6 * Copyright 2007 Mike Nordell
7 * Copyright 2021 George Bișoc <george.bisoc@reactos.org>
8 */
9
10 /* INCLUDES **************************************************************/
11
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15
16 EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock;
17 static LIST_ENTRY PspQuotaBlockList = {&PspQuotaBlockList, &PspQuotaBlockList};
18 static KSPIN_LOCK PspQuotaLock;
19
20 #define VALID_QUOTA_FLAGS (QUOTA_LIMITS_HARDWS_MIN_ENABLE | \
21 QUOTA_LIMITS_HARDWS_MIN_DISABLE | \
22 QUOTA_LIMITS_HARDWS_MAX_ENABLE | \
23 QUOTA_LIMITS_HARDWS_MAX_DISABLE)
24
25 /* PRIVATE FUNCTIONS *******************************************************/
26
27 /**
28 * @brief
29 * Returns pool quotas back to the Memory Manager
30 * when the pool quota block is no longer being
31 * used by anybody.
32 *
33 * @param[in] QuotaBlock
34 * The pool quota block of which quota resources
35 * are to be sent back.
36 *
37 * @return
38 * Nothing.
39 *
40 * @remarks
41 * The function only returns quotas back to Memory
42 * Manager that is paged or non paged. It does not
43 * return page file quotas as page file quota
44 * management is done in a different way. Furthermore,
45 * quota spin lock has to be held when returning quotas.
46 */
_Requires_lock_held_(PspQuotaLock)47 _Requires_lock_held_(PspQuotaLock)
48 VOID
49 NTAPI
50 PspReturnQuotasOnDestroy(
51 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock)
52 {
53 ULONG PsQuotaTypeIndex;
54 SIZE_T QuotaToReturn;
55
56 /*
57 * We must be in a dispatch level interrupt here
58 * as we should be under a spin lock.
59 */
60 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL);
61
62 /* Make sure that the quota block is not plain garbage */
63 ASSERT(QuotaBlock);
64
65 /* Loop over the Process quota types */
66 for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsPageFile; PsQuotaTypeIndex++)
67 {
68 /* The amount needed to return to Mm is the limit and return fields */
69 QuotaToReturn = QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Limit + QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Return;
70 MmReturnPoolQuota(PsQuotaTypeIndex, QuotaToReturn);
71 }
72 }
73
74 /**
75 * @brief
76 * Releases some of excess quotas in order to attempt
77 * free up some resources. This is done primarily in
78 * in case the Memory Manager fails to raise the quota
79 * limit.
80 *
81 * @param[in] QuotaType
82 * Process pool quota type.
83 *
84 * @param[out] ReturnedQuotas
85 * A pointer to the returned amount of quotas
86 * back to Memory Manager.
87 *
88 * @return
89 * Nothing.
90 *
91 * @remarks
92 * The function releases excess paged or non
93 * paged pool quotas. Page file quota type is
94 * not permitted. Furthermore, quota spin lock
95 * has to be held when returning quotas.
96 */
_Requires_lock_held_(PspQuotaLock)97 _Requires_lock_held_(PspQuotaLock)
98 VOID
99 NTAPI
100 PspReturnExcessQuotas(
101 _In_ PS_QUOTA_TYPE QuotaType,
102 _Outptr_ PSIZE_T ReturnedQuotas)
103 {
104 PLIST_ENTRY PspQuotaList;
105 PEPROCESS_QUOTA_BLOCK QuotaBlockFromList;
106 SIZE_T AmountToReturn = 0;
107
108 /*
109 * We must be in a dispatch level interrupt here
110 * as we should be under a spin lock.
111 */
112 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL);
113
114 /*
115 * Loop over the quota block lists and reap
116 * whatever quotas we haven't returned which
117 * is needed to free up resources.
118 */
119 for (PspQuotaList = PspQuotaBlockList.Flink;
120 PspQuotaList != &PspQuotaBlockList;
121 PspQuotaList = PspQuotaList->Flink)
122 {
123 /* Gather the quota block from the list */
124 QuotaBlockFromList = CONTAINING_RECORD(PspQuotaList, EPROCESS_QUOTA_BLOCK, QuotaList);
125
126 /*
127 * Gather any unreturned quotas and cache
128 * them to a variable.
129 */
130 AmountToReturn += InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Return, 0);
131
132 /*
133 * If no other process is taking use of this
134 * block, then it means that this block has
135 * only shared pool quota and the last process
136 * no longer uses this block. If the limit is
137 * grater than the usage then trim the limit
138 * and use that as additional amount of quota
139 * to return.
140 */
141 if (QuotaBlockFromList->ProcessCount == 0)
142 {
143 if (QuotaBlockFromList->QuotaEntry[QuotaType].Usage <
144 QuotaBlockFromList->QuotaEntry[QuotaType].Limit)
145 {
146 InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Limit,
147 QuotaBlockFromList->QuotaEntry[QuotaType].Usage);
148 AmountToReturn += QuotaBlockFromList->QuotaEntry[QuotaType].Limit;
149 }
150 }
151 }
152
153 /* Invoke Mm to return quotas */
154 DPRINT("PspReturnExcessQuotas(): Amount of quota released -- %lu\n", AmountToReturn);
155 MmReturnPoolQuota(QuotaType, AmountToReturn);
156 *ReturnedQuotas = AmountToReturn;
157 }
158
159 /**
160 * @brief
161 * Internal kernel function that provides the
162 * bulk logic of process quota charging,
163 * necessary for exported kernel routines
164 * needed for quota management.
165 *
166 * @param[in] Process
167 * A process, represented as a EPROCESS object.
168 * This parameter is used to charge the own
169 * process' quota usage.
170 *
171 * @param[in] QuotaBlock
172 * The quota block which quotas are to be charged.
173 * This block can either come from the process itself
174 * or from an object with specified quota charges.
175 *
176 * @param[in] QuotaType
177 * The quota type which quota in question is to
178 * be charged. The permitted types are PsPagedPool,
179 * PsNonPagedPool and PsPageFile.
180 *
181 * @param[in] Amount
182 * The amount of quota to be charged.
183 *
184 * @return
185 * Returns STATUS_SUCCESS if quota charging has
186 * been done successfully without problemns.
187 * STATUS_QUOTA_EXCEEDED is returned if the caller
188 * wants to charge quotas with amount way over
189 * the limits. STATUS_PAGEFILE_QUOTA_EXCEEDED
190 * is returned for the same situation but
191 * specific to page files instead.
192 */
193 NTSTATUS
194 NTAPI
PspChargeProcessQuotaSpecifiedPool(_In_opt_ PEPROCESS Process,_In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,_In_ PS_QUOTA_TYPE QuotaType,_In_ SIZE_T Amount)195 PspChargeProcessQuotaSpecifiedPool(
196 _In_opt_ PEPROCESS Process,
197 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,
198 _In_ PS_QUOTA_TYPE QuotaType,
199 _In_ SIZE_T Amount)
200 {
201 KIRQL OldIrql;
202 SIZE_T ReturnedQuotas;
203 SIZE_T UpdatedLimit;
204
205 /* Sanity checks */
206 ASSERT(QuotaType < PsQuotaTypes);
207 ASSERT((SSIZE_T)Amount >= 0);
208
209 /* Guard ourselves in a spin lock */
210 KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
211
212 /* Are we within the bounds of quota limit? */
213 if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount >
214 QuotaBlock->QuotaEntry[QuotaType].Limit &&
215 QuotaBlock != &PspDefaultQuotaBlock)
216 {
217 /* We aren't... Is this a page file quota charging? */
218 if (QuotaType == PsPageFile)
219 {
220 /* It is, return the appropriate status code */
221 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit on page file quota (limit -- %lu || amount -- %lu)\n",
222 QuotaBlock->QuotaEntry[QuotaType].Limit, Amount);
223 return STATUS_PAGEFILE_QUOTA_EXCEEDED;
224 }
225
226 /*
227 * This is not a page file charge. What we can do at best
228 * in this scenario is to attempt to raise (expand) the
229 * quota limit charges of the block.
230 */
231 if (!MmRaisePoolQuota(QuotaType,
232 QuotaBlock->QuotaEntry[QuotaType].Limit,
233 &UpdatedLimit))
234 {
235 /*
236 * We can't? It could be that we must free
237 * up some resources in order to raise the
238 * limit, which in that case we must return
239 * the excess of quota that hasn't been
240 * returned. If we haven't returned anything
241 * then what we're doing here is futile.
242 * Bail out...
243 */
244 PspReturnExcessQuotas(QuotaType, &ReturnedQuotas);
245 if (ReturnedQuotas == 0)
246 {
247 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Failed to free some resources in order to raise quota limits...\n");
248 KeReleaseSpinLock(&PspQuotaLock, OldIrql);
249 return STATUS_QUOTA_EXCEEDED;
250 }
251
252 /* Try to raise the quota limits again */
253 MmRaisePoolQuota(QuotaType,
254 QuotaBlock->QuotaEntry[QuotaType].Limit,
255 &UpdatedLimit);
256 }
257
258 /* Enforce a new raised limit */
259 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit, UpdatedLimit);
260
261 /*
262 * Now determine if the current usage and the
263 * amounting by the caller still exceeds the
264 * quota limit of the process. If it's still
265 * over the limit then there's nothing we can
266 * do, so fail.
267 */
268 if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount >
269 QuotaBlock->QuotaEntry[QuotaType].Limit)
270 {
271 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit (limit -- %lu || amount -- %lu)\n",
272 QuotaBlock->QuotaEntry[QuotaType].Limit, Amount);
273 return STATUS_QUOTA_EXCEEDED;
274 }
275 }
276
277 /* Update the quota usage */
278 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, Amount);
279
280 /* Update the entry peak if it's less than the usage */
281 if (QuotaBlock->QuotaEntry[QuotaType].Peak <
282 QuotaBlock->QuotaEntry[QuotaType].Usage)
283 {
284 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Peak,
285 QuotaBlock->QuotaEntry[QuotaType].Usage);
286 }
287
288 /* Are we being given a process as well? */
289 if (Process)
290 {
291 /* We're being given, check that's not a system one */
292 ASSERT(Process != PsInitialSystemProcess);
293
294 InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], Amount);
295
296 /*
297 * OK, we've now updated the quota usage of the process
298 * based upon the amount that the caller wanted to charge.
299 * Although the peak of process quota can be less than it was
300 * before so update the peaks as well accordingly.
301 */
302 if (Process->QuotaPeak[QuotaType] < Process->QuotaUsage[QuotaType])
303 {
304 InterlockedExchangeSizeT(&Process->QuotaPeak[QuotaType],
305 Process->QuotaUsage[QuotaType]);
306 }
307 }
308
309 /* Release the lock */
310 KeReleaseSpinLock(&PspQuotaLock, OldIrql);
311 return STATUS_SUCCESS;
312 }
313
314 /**
315 * @brief
316 * Internal kernel function that provides the
317 * bulk logic of process quota returning. It
318 * returns (takes away) quotas back from a
319 * process and/or quota block, which is
320 * the opposite of charging quotas.
321 *
322 * @param[in] Process
323 * A process, represented as a EPROCESS object.
324 * This parameter is used to return the own
325 * process' quota usage.
326 *
327 * @param[in] QuotaBlock
328 * The quota block which quotas are to be returned.
329 * This block can either come from the process itself
330 * or from an object with specified quota charges.
331 *
332 * @param[in] QuotaType
333 * The quota type which quota in question is to
334 * be returned. The permitted types are PsPagedPool,
335 * PsNonPagedPool and PsPageFile.
336 *
337 * @param[in] Amount
338 * The amount of quota to be returned.
339 *
340 * @return
341 * Nothing.
342 */
343 VOID
344 NTAPI
PspReturnProcessQuotaSpecifiedPool(_In_opt_ PEPROCESS Process,_In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,_In_ PS_QUOTA_TYPE QuotaType,_In_ SIZE_T Amount)345 PspReturnProcessQuotaSpecifiedPool(
346 _In_opt_ PEPROCESS Process,
347 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,
348 _In_ PS_QUOTA_TYPE QuotaType,
349 _In_ SIZE_T Amount)
350 {
351 KIRQL OldIrql;
352 SIZE_T ReturnThreshold;
353 SIZE_T AmountToReturn = 0;
354
355 /* Sanity checks */
356 ASSERT(QuotaType < PsQuotaTypes);
357 ASSERT((SSIZE_T)Amount >= 0);
358
359 /* Guard ourselves in a spin lock */
360 KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
361
362 /* Does the caller return more quota than it was previously charged? */
363 if ((Process && Process->QuotaUsage[QuotaType] < Amount) ||
364 QuotaBlock->QuotaEntry[QuotaType].Usage < Amount)
365 {
366 /* It does, crash the system! */
367 KeBugCheckEx(QUOTA_UNDERFLOW,
368 (ULONG_PTR)Process,
369 (ULONG_PTR)QuotaType,
370 Process ? (ULONG_PTR)Process->QuotaUsage[QuotaType] :
371 QuotaBlock->QuotaEntry[QuotaType].Usage,
372 (ULONG_PTR)Amount);
373 }
374
375 /* The return threshold can be non paged or paged */
376 ReturnThreshold = QuotaType ? PSP_NON_PAGED_POOL_QUOTA_THRESHOLD : PSP_PAGED_POOL_QUOTA_THRESHOLD;
377
378 /*
379 * We need to trim the quota limits based on the
380 * amount we're going to return quotas back.
381 */
382 if ((QuotaType != PsPageFile && QuotaBlock != &PspDefaultQuotaBlock) &&
383 (QuotaBlock->QuotaEntry[QuotaType].Limit > QuotaBlock->QuotaEntry[QuotaType].Usage + ReturnThreshold))
384 {
385 /*
386 * If the amount to return exceeds the threshold,
387 * the new amount becomes the default, otherwise
388 * the amount is just the one given by the caller.
389 */
390 AmountToReturn = min(Amount, ReturnThreshold);
391
392 /* Add up the lots to the Return field */
393 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Return, AmountToReturn);
394
395 /*
396 * If the amount to return exceeds the threshold then
397 * we have lots of quota to return to Mm. So do it so
398 * and zerou out the Return field.
399 */
400 if (QuotaBlock->QuotaEntry[QuotaType].Return > ReturnThreshold)
401 {
402 MmReturnPoolQuota(QuotaType, QuotaBlock->QuotaEntry[QuotaType].Return);
403 InterlockedExchangeSizeT(QuotaBlock->QuotaEntry[QuotaType].Return, 0);
404 }
405
406 /* And try to trim the limit */
407 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit,
408 QuotaBlock->QuotaEntry[QuotaType].Limit - AmountToReturn);
409 }
410
411 /* Update the usage member of the block */
412 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, -(LONG_PTR)Amount);
413
414 /* Are we being given a process? */
415 if (Process)
416 {
417 /* We're being given, check that's not a system one */
418 ASSERT(Process != PsInitialSystemProcess);
419
420 /* Decrease the process' quota usage */
421 InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], -(LONG_PTR)Amount);
422 }
423
424 /* We're done, release the lock */
425 KeReleaseSpinLock(&PspQuotaLock, OldIrql);
426 }
427
428 /* FUNCTIONS ***************************************************************/
429
430 /**
431 * @brief
432 * Initializes the quota system during boot
433 * phase of the system, which sets up the
434 * default quota block that is used across
435 * several processes.
436 *
437 * @return
438 * Nothing.
439 */
440 CODE_SEG("INIT")
441 VOID
442 NTAPI
PsInitializeQuotaSystem(VOID)443 PsInitializeQuotaSystem(VOID)
444 {
445 /* Initialize the default block */
446 RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock));
447
448 /* Assign the default quota limits */
449 PspDefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit = (SIZE_T)-1;
450 PspDefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit = (SIZE_T)-1;
451 PspDefaultQuotaBlock.QuotaEntry[PsPageFile].Limit = (SIZE_T)-1;
452
453 /*
454 * Set up the count references as the
455 * default block will going to be used.
456 */
457 PspDefaultQuotaBlock.ReferenceCount = 1;
458 PspDefaultQuotaBlock.ProcessCount = 1;
459
460 /* Assign that block to initial process */
461 PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock;
462 }
463
464 /**
465 * @brief
466 * Inherits the quota block to another newborn
467 * (child) process. If there's no parent
468 * process, the default quota block is
469 * assigned.
470 *
471 * @param[in] Process
472 * The child process which quota block
473 * is to be given.
474 *
475 * @param[in] ParentProcess
476 * The parent process.
477 *
478 * @return
479 * Nothing.
480 */
481 VOID
482 NTAPI
PspInheritQuota(_In_ PEPROCESS Process,_In_opt_ PEPROCESS ParentProcess)483 PspInheritQuota(
484 _In_ PEPROCESS Process,
485 _In_opt_ PEPROCESS ParentProcess)
486 {
487 PEPROCESS_QUOTA_BLOCK QuotaBlock;
488
489 if (ParentProcess != NULL)
490 {
491 ASSERT(ParentProcess->QuotaBlock != NULL);
492 QuotaBlock = ParentProcess->QuotaBlock;
493 }
494 else
495 {
496 QuotaBlock = &PspDefaultQuotaBlock;
497 }
498
499 InterlockedIncrementSizeT(&QuotaBlock->ProcessCount);
500 InterlockedIncrementSizeT(&QuotaBlock->ReferenceCount);
501
502 Process->QuotaBlock = QuotaBlock;
503 }
504
505 /**
506 * @brief
507 * Inserts the new quota block into
508 * the quota list.
509 *
510 * @param[in] QuotaBlock
511 * The new quota block.
512 *
513 * @return
514 * Nothing.
515 */
516 VOID
517 NTAPI
PspInsertQuotaBlock(_In_ PEPROCESS_QUOTA_BLOCK QuotaBlock)518 PspInsertQuotaBlock(
519 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock)
520 {
521 KIRQL OldIrql;
522
523 KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
524 InsertTailList(&PspQuotaBlockList, &QuotaBlock->QuotaList);
525 KeReleaseSpinLock(&PspQuotaLock, OldIrql);
526 }
527
528 /**
529 * @brief
530 * De-references a quota block when quotas
531 * have been returned back because of an
532 * object de-allocation or when a process
533 * gets destroyed. If the last instance
534 * that held up the block gets de-referenced
535 * the function will perform a cleanup against
536 * that block and it'll free the quota block
537 * from memory.
538 *
539 * @param[in] Process
540 * A pointer to a process that de-references the
541 * quota block.
542 *
543 * @param[in] QuotaBlock
544 * A pointer to a quota block that is to be
545 * de-referenced. This block can come from a
546 * process that references it or an object.
547 *
548 * @return
549 * Nothing.
550 */
551 VOID
552 NTAPI
PspDereferenceQuotaBlock(_In_opt_ PEPROCESS Process,_In_ PEPROCESS_QUOTA_BLOCK QuotaBlock)553 PspDereferenceQuotaBlock(
554 _In_opt_ PEPROCESS Process,
555 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock)
556 {
557 ULONG PsQuotaTypeIndex;
558 KIRQL OldIrql;
559
560 /* Make sure the quota block is not trash */
561 ASSERT(QuotaBlock);
562
563 /* Iterate over the process quota types if we have a process */
564 if (Process)
565 {
566 for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsQuotaTypes; PsQuotaTypeIndex++)
567 {
568 /*
569 * We need to make sure that the quota usage
570 * uniquely associated with the process is 0
571 * on that moment the process gets destroyed.
572 */
573 ASSERT(Process->QuotaUsage[PsQuotaTypeIndex] == 0);
574 }
575
576 /* As the process is now gone, decrement the process count */
577 InterlockedDecrementUL(&QuotaBlock->ProcessCount);
578 }
579
580 /* If no one is using this block, begin to destroy it */
581 if (QuotaBlock != &PspDefaultQuotaBlock &&
582 InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0)
583 {
584 /* Acquire the quota lock */
585 KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
586
587 /* Return all the quotas back to Mm and remove the quota from list */
588 PspReturnQuotasOnDestroy(QuotaBlock);
589 RemoveEntryList(&QuotaBlock->QuotaList);
590
591 /* Release the lock and free the block */
592 KeReleaseSpinLock(&PspQuotaLock, OldIrql);
593 ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK);
594 }
595 }
596
597 /**
598 * @brief
599 * Returns the shared (paged and non paged)
600 * pool quotas. The function is used exclusively
601 * by the Object Manager to manage quota returns
602 * handling of objects.
603 *
604 * @param[in] QuotaBlock
605 * The quota block which quotas are to
606 * be returned.
607 *
608 * @param[in] AmountToReturnPaged
609 * The amount of paged quotas quotas to
610 * be returned.
611 *
612 * @param[in] AmountToReturnNonPaged
613 * The amount of non paged quotas to
614 * be returned.
615 *
616 * @return
617 * Nothing.
618 */
619 VOID
620 NTAPI
PsReturnSharedPoolQuota(_In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,_In_ SIZE_T AmountToReturnPaged,_In_ SIZE_T AmountToReturnNonPaged)621 PsReturnSharedPoolQuota(
622 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock,
623 _In_ SIZE_T AmountToReturnPaged,
624 _In_ SIZE_T AmountToReturnNonPaged)
625 {
626 /* Sanity check */
627 ASSERT(QuotaBlock);
628
629 /* Return the pool quotas if there're any */
630 if (AmountToReturnPaged != 0)
631 {
632 PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsPagedPool, AmountToReturnPaged);
633 }
634
635 if (AmountToReturnNonPaged != 0)
636 {
637 PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsNonPagedPool, AmountToReturnNonPaged);
638 }
639
640 DPRINT("PsReturnSharedPoolQuota(): Amount returned back (paged %lu -- non paged %lu)\n", AmountToReturnPaged, AmountToReturnNonPaged);
641
642 /* Dereference the quota block */
643 PspDereferenceQuotaBlock(NULL, QuotaBlock);
644 }
645
646 /**
647 * @brief
648 * Charges the shared (paged and non paged)
649 * pool quotas. The function is used exclusively
650 * by the Object Manager to manage quota charges
651 * handling of objects.
652 *
653 * @param[in] Process
654 * The process which quotas are to
655 * be charged within its quota block.
656 *
657 * @param[in] AmountToChargePaged
658 * The amount of paged quotas quotas to
659 * be charged.
660 *
661 * @param[in] AmountToChargeNonPaged
662 * The amount of non paged quotas to
663 * be charged.
664 *
665 * @return
666 * Returns the charged quota block, which it'll
667 * be used by the Object Manager to attach
668 * the charged quotas information to the object.
669 * If the function fails to charge quotas, NULL
670 * is returned to the caller.
671 */
672 PEPROCESS_QUOTA_BLOCK
673 NTAPI
PsChargeSharedPoolQuota(_In_ PEPROCESS Process,_In_ SIZE_T AmountToChargePaged,_In_ SIZE_T AmountToChargeNonPaged)674 PsChargeSharedPoolQuota(
675 _In_ PEPROCESS Process,
676 _In_ SIZE_T AmountToChargePaged,
677 _In_ SIZE_T AmountToChargeNonPaged)
678 {
679 NTSTATUS Status;
680
681 /* Sanity checks */
682 ASSERT(Process);
683 ASSERT(Process->QuotaBlock);
684
685 /* Do we have some paged pool quota to charge? */
686 if (AmountToChargePaged != 0)
687 {
688 /* We do, charge! */
689 Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged);
690 if (!NT_SUCCESS(Status))
691 {
692 DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx)\n", Status);
693 return NULL;
694 }
695 }
696
697 /* Do we have some non paged pool quota to charge? */
698 if (AmountToChargeNonPaged != 0)
699 {
700 /* We do, charge! */
701 Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsNonPagedPool, AmountToChargeNonPaged);
702 if (!NT_SUCCESS(Status))
703 {
704 DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx). Attempting to return some paged pool back...\n", Status);
705 PspReturnProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged);
706 return NULL;
707 }
708 }
709
710 /* We have charged the quotas of an object, increment the reference */
711 InterlockedIncrementSizeT(&Process->QuotaBlock->ReferenceCount);
712
713 DPRINT("PsChargeSharedPoolQuota(): Amount charged (paged %lu -- non paged %lu)\n", AmountToChargePaged, AmountToChargeNonPaged);
714 return Process->QuotaBlock;
715 }
716
717 /**
718 * @brief
719 * Charges the process page file quota.
720 * The function is used internally by
721 * the kernel.
722 *
723 * @param[in] Process
724 * The process which page file quota is
725 * to be charged.
726 *
727 * @param[in] Amount
728 * The amount of page file quota to charge.
729 *
730 * @return
731 * Returns STATUS_SUCCESS if quota charging has
732 * been done with success, otherwise a NTSTATUS
733 * code of STATUS_PAGEFILE_QUOTA_EXCEEDED is
734 * returned.
735 */
736 NTSTATUS
737 NTAPI
PsChargeProcessPageFileQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)738 PsChargeProcessPageFileQuota(
739 _In_ PEPROCESS Process,
740 _In_ SIZE_T Amount)
741 {
742 /* Don't do anything for the system process */
743 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
744
745 return PspChargeProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount);
746 }
747
748 /**
749 * @brief
750 * Charges the pool quota of a given process.
751 * The kind of pool quota to charge is determined
752 * by the PoolType parameter.
753 *
754 * @param[in] Process
755 * The process which quota is to be
756 * charged.
757 *
758 * @param[in] PoolType
759 * The pool type to choose to charge quotas
760 * (e.g. PagedPool or NonPagedPool).
761 *
762 * @param[in] Amount
763 * The amount of quotas to charge into a process.
764 *
765 * @return
766 * Nothing.
767 *
768 * @remarks
769 * The function raises an exception if STATUS_QUOTA_EXCEEDED
770 * status code is returned. Callers are responsible on their
771 * own to handle the raised exception.
772 */
773 VOID
774 NTAPI
PsChargePoolQuota(_In_ PEPROCESS Process,_In_ POOL_TYPE PoolType,_In_ SIZE_T Amount)775 PsChargePoolQuota(
776 _In_ PEPROCESS Process,
777 _In_ POOL_TYPE PoolType,
778 _In_ SIZE_T Amount)
779 {
780 NTSTATUS Status;
781 ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
782
783 /* Don't do anything for the system process */
784 if (Process == PsInitialSystemProcess) return;
785
786 /* Charge the usage */
787 Status = PsChargeProcessPoolQuota(Process, PoolType, Amount);
788 if (!NT_SUCCESS(Status)) ExRaiseStatus(Status);
789 }
790
791 /**
792 * @brief
793 * Charges the non paged pool quota
794 * of a given process.
795 *
796 * @param[in] Process
797 * The process which non paged quota
798 * is to be charged.
799 *
800 * @param[in] Amount
801 * The amount of quotas to charge into a process.
802 *
803 * @return
804 * Returns STATUS_SUCCESS if quota charing has
805 * suceeded, STATUS_QUOTA_EXCEEDED is returned
806 * otherwise to indicate the caller attempted
807 * to charge quotas over the limits.
808 */
809 NTSTATUS
810 NTAPI
PsChargeProcessNonPagedPoolQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)811 PsChargeProcessNonPagedPoolQuota(
812 _In_ PEPROCESS Process,
813 _In_ SIZE_T Amount)
814 {
815 /* Call the general function */
816 return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount);
817 }
818
819 /**
820 * @brief
821 * Charges the paged pool quota of a
822 * given process.
823 *
824 * @param[in] Process
825 * The process which paged quota
826 * is to be charged.
827 *
828 * @param[in] Amount
829 * The amount of quotas to charge into a process.
830 *
831 * @return
832 * Returns STATUS_SUCCESS if quota charing has
833 * suceeded, STATUS_QUOTA_EXCEEDED is returned
834 * otherwise to indicate the caller attempted
835 * to charge quotas over the limits.
836 */
837 NTSTATUS
838 NTAPI
PsChargeProcessPagedPoolQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)839 PsChargeProcessPagedPoolQuota(
840 _In_ PEPROCESS Process,
841 _In_ SIZE_T Amount)
842 {
843 /* Call the general function */
844 return PsChargeProcessPoolQuota(Process, PagedPool, Amount);
845 }
846
847 /**
848 * @brief
849 * Charges the process' quota pool.
850 * The type of quota to be charged
851 * depends upon the PoolType parameter.
852 *
853 * @param[in] Process
854 * The process which quota is to
855 * be charged.
856 *
857 * @param[in] PoolType
858 * The type of quota pool to charge (e.g.
859 * PagedPool or NonPagedPool).
860 *
861 * @param[in] Amount
862 * The amount of quotas to charge into a process.
863 *
864 * @return
865 * Returns STATUS_SUCCESS if quota charing has
866 * suceeded, STATUS_QUOTA_EXCEEDED is returned
867 * otherwise to indicate the caller attempted
868 * to charge quotas over the limits.
869 */
870 NTSTATUS
871 NTAPI
PsChargeProcessPoolQuota(_In_ PEPROCESS Process,_In_ POOL_TYPE PoolType,_In_ SIZE_T Amount)872 PsChargeProcessPoolQuota(
873 _In_ PEPROCESS Process,
874 _In_ POOL_TYPE PoolType,
875 _In_ SIZE_T Amount)
876 {
877 /* Don't do anything for the system process */
878 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
879
880 return PspChargeProcessQuotaSpecifiedPool(Process,
881 Process->QuotaBlock,
882 (PoolType & PAGED_POOL_MASK),
883 Amount);
884 }
885
886 /**
887 * @brief
888 * Returns the pool quota that the
889 * process was taking up.
890 *
891 * @param[in] Process
892 * The process which quota is to
893 * be returned.
894 *
895 * @param[in] PoolType
896 * The type of quota pool to return (e.g.
897 * PagedPool or NonPagedPool).
898 *
899 * @param[in] Amount
900 * The amount of quotas to return from a process.
901 *
902 * @return
903 * Nothing.
904 */
905 VOID
906 NTAPI
PsReturnPoolQuota(_In_ PEPROCESS Process,_In_ POOL_TYPE PoolType,_In_ SIZE_T Amount)907 PsReturnPoolQuota(
908 _In_ PEPROCESS Process,
909 _In_ POOL_TYPE PoolType,
910 _In_ SIZE_T Amount)
911 {
912 /* Don't do anything for the system process */
913 if (Process == PsInitialSystemProcess) return;
914
915 PspReturnProcessQuotaSpecifiedPool(Process,
916 Process->QuotaBlock,
917 (PoolType & PAGED_POOL_MASK),
918 Amount);
919 }
920
921 /**
922 * @brief
923 * Returns the non paged quota pool
924 * that the process was taking up.
925 *
926 * @param[in] Process
927 * The process which non paged quota
928 * is to be returned.
929 *
930 * @param[in] Amount
931 * The amount of quotas to return from a process.
932 *
933 * @return
934 * Nothing.
935 */
936 VOID
937 NTAPI
PsReturnProcessNonPagedPoolQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)938 PsReturnProcessNonPagedPoolQuota(
939 _In_ PEPROCESS Process,
940 _In_ SIZE_T Amount)
941 {
942 /* Don't do anything for the system process */
943 if (Process == PsInitialSystemProcess) return;
944
945 PsReturnPoolQuota(Process, NonPagedPool, Amount);
946 }
947
948 /**
949 * @brief
950 * Returns the paged pool quota that
951 * the process was taking up.
952 *
953 * @param[in] Process
954 * The process which paged pool
955 * quota is to be returned.
956 *
957 * @param[in] Amount
958 * The amount of quotas to return from a process.
959 *
960 * @return
961 * Nothing.
962 */
963 VOID
964 NTAPI
PsReturnProcessPagedPoolQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)965 PsReturnProcessPagedPoolQuota(
966 _In_ PEPROCESS Process,
967 _In_ SIZE_T Amount)
968 {
969 /* Don't do anything for the system process */
970 if (Process == PsInitialSystemProcess) return;
971
972 PsReturnPoolQuota(Process, PagedPool, Amount);
973 }
974
975 /**
976 * @brief
977 * Returns the page file quota that the
978 * process was taking up. The function
979 * is used exclusively by the kernel.
980 *
981 * @param[in] Process
982 * The process which pagefile quota is
983 * to be returned.
984 *
985 * @param[in] Amount
986 * The amount of quotas to return from a process.
987 *
988 * @return
989 * Returns STATUS_SUCCESS.
990 */
991 NTSTATUS
992 NTAPI
PsReturnProcessPageFileQuota(_In_ PEPROCESS Process,_In_ SIZE_T Amount)993 PsReturnProcessPageFileQuota(
994 _In_ PEPROCESS Process,
995 _In_ SIZE_T Amount)
996 {
997 /* Don't do anything for the system process */
998 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
999
1000 PspReturnProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount);
1001 return STATUS_SUCCESS;
1002 }
1003
1004 /**
1005 * @brief
1006 * This function adjusts the working set limits
1007 * of a process and sets up new quota limits
1008 * when necessary. The function is used
1009 * when the caller requests to set up
1010 * new working set sizes.
1011 *
1012 * @param[in] Process
1013 * The process which quota limits or working
1014 * set sizes are to be changed.
1015 *
1016 * @param[in] Unused
1017 * This parameter is unused.
1018 *
1019 * @param[in] QuotaLimits
1020 * An arbitrary pointer that points to a quota
1021 * limits structure, needed to determine on
1022 * setting up new working set sizes.
1023 *
1024 * @param[in] QuotaLimitsLength
1025 * The length of QuotaLimits buffer, which size
1026 * is expressed in bytes.
1027 *
1028 * @param[in] PreviousMode
1029 * The processor level access mode.
1030 *
1031 * @return
1032 * Returns STATUS_SUCCESS if the function has completed
1033 * successfully. STATUS_INVALID_PARAMETER is returned if
1034 * the caller has given a quota limits structure with
1035 * invalid data. STATUS_INFO_LENGTH_MISMATCH is returned
1036 * if the length of QuotaLimits pointed by QuotaLimitsLength
1037 * is not right. STATUS_PRIVILEGE_NOT_HELD is returned if
1038 * the calling thread of the process doesn't hold the necessary
1039 * right privilege to increase quotas. STATUS_NO_MEMORY is
1040 * returned if a memory pool allocation has failed. A failure
1041 * NTSTATUS code is returned otherwise.
1042 */
1043 NTSTATUS
1044 NTAPI
PspSetQuotaLimits(_In_ PEPROCESS Process,_In_ ULONG Unused,_In_ PVOID QuotaLimits,_In_ ULONG QuotaLimitsLength,_In_ KPROCESSOR_MODE PreviousMode)1045 PspSetQuotaLimits(
1046 _In_ PEPROCESS Process,
1047 _In_ ULONG Unused,
1048 _In_ PVOID QuotaLimits,
1049 _In_ ULONG QuotaLimitsLength,
1050 _In_ KPROCESSOR_MODE PreviousMode)
1051 {
1052 QUOTA_LIMITS_EX CapturedQuotaLimits;
1053 PEPROCESS_QUOTA_BLOCK QuotaBlock, OldQuotaBlock;
1054 BOOLEAN IncreaseOkay;
1055 KAPC_STATE SavedApcState;
1056 NTSTATUS Status;
1057
1058 UNREFERENCED_PARAMETER(Unused);
1059
1060 _SEH2_TRY
1061 {
1062 ProbeForRead(QuotaLimits, QuotaLimitsLength, sizeof(ULONG));
1063
1064 /* Check if we have the basic or extended structure */
1065 if (QuotaLimitsLength == sizeof(QUOTA_LIMITS))
1066 {
1067 /* Copy the basic structure, zero init the remaining fields */
1068 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS));
1069 CapturedQuotaLimits.WorkingSetLimit = 0;
1070 CapturedQuotaLimits.Reserved2 = 0;
1071 CapturedQuotaLimits.Reserved3 = 0;
1072 CapturedQuotaLimits.Reserved4 = 0;
1073 CapturedQuotaLimits.CpuRateLimit.RateData = 0;
1074 CapturedQuotaLimits.Flags = 0;
1075 }
1076 else if (QuotaLimitsLength == sizeof(QUOTA_LIMITS_EX))
1077 {
1078 /* Copy the full structure */
1079 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS_EX));
1080
1081 /* Verify that the caller passed valid flags */
1082 if ((CapturedQuotaLimits.Flags & ~VALID_QUOTA_FLAGS) ||
1083 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_ENABLE) &&
1084 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_DISABLE)) ||
1085 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_ENABLE) &&
1086 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_DISABLE)))
1087 {
1088 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits.Flags);
1089 _SEH2_YIELD(return STATUS_INVALID_PARAMETER);
1090 }
1091
1092 /* Verify that the caller didn't pass reserved values */
1093 if ((CapturedQuotaLimits.WorkingSetLimit != 0) ||
1094 (CapturedQuotaLimits.Reserved2 != 0) ||
1095 (CapturedQuotaLimits.Reserved3 != 0) ||
1096 (CapturedQuotaLimits.Reserved4 != 0) ||
1097 (CapturedQuotaLimits.CpuRateLimit.RateData != 0))
1098 {
1099 DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n",
1100 CapturedQuotaLimits.WorkingSetLimit,
1101 CapturedQuotaLimits.Reserved2,
1102 CapturedQuotaLimits.Reserved3,
1103 CapturedQuotaLimits.Reserved4,
1104 CapturedQuotaLimits.CpuRateLimit.RateData);
1105 _SEH2_YIELD(return STATUS_INVALID_PARAMETER);
1106 }
1107 }
1108 else
1109 {
1110 DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength);
1111 _SEH2_YIELD(return STATUS_INFO_LENGTH_MISMATCH);
1112 }
1113 }
1114 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1115 {
1116 DPRINT1("Exception while copying data\n");
1117 _SEH2_YIELD(return _SEH2_GetExceptionCode());
1118 }
1119 _SEH2_END;
1120
1121 /* Check the caller changes the working set size limits */
1122 if ((CapturedQuotaLimits.MinimumWorkingSetSize != 0) &&
1123 (CapturedQuotaLimits.MaximumWorkingSetSize != 0))
1124 {
1125 /* Check for special case: trimming the WS */
1126 if ((CapturedQuotaLimits.MinimumWorkingSetSize == SIZE_T_MAX) &&
1127 (CapturedQuotaLimits.MaximumWorkingSetSize == SIZE_T_MAX))
1128 {
1129 /* No increase allowed */
1130 IncreaseOkay = FALSE;
1131 }
1132 else
1133 {
1134 /* Check if the caller has the required privilege */
1135 IncreaseOkay = SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege,
1136 PreviousMode);
1137 }
1138
1139 /* Attach to the target process and disable APCs */
1140 KeStackAttachProcess(&Process->Pcb, &SavedApcState);
1141 KeEnterGuardedRegion();
1142
1143 /* Call Mm to adjust the process' working set size */
1144 Status = MmAdjustWorkingSetSize(CapturedQuotaLimits.MinimumWorkingSetSize,
1145 CapturedQuotaLimits.MaximumWorkingSetSize,
1146 0,
1147 IncreaseOkay);
1148
1149 /* Bring back APCs and detach from the process */
1150 KeLeaveGuardedRegion();
1151 KeUnstackDetachProcess(&SavedApcState);
1152 }
1153 else if (Process->QuotaBlock == &PspDefaultQuotaBlock)
1154 {
1155 /* Check if the caller has the required privilege */
1156 if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, PreviousMode))
1157 {
1158 return STATUS_PRIVILEGE_NOT_HELD;
1159 }
1160
1161 /* Allocate a new quota block */
1162 QuotaBlock = ExAllocatePoolWithTag(NonPagedPool,
1163 sizeof(EPROCESS_QUOTA_BLOCK),
1164 TAG_QUOTA_BLOCK);
1165 if (QuotaBlock == NULL)
1166 {
1167 ObDereferenceObject(Process);
1168 return STATUS_NO_MEMORY;
1169 }
1170
1171 /* Initialize the quota block */
1172 QuotaBlock->ReferenceCount = 1;
1173 QuotaBlock->ProcessCount = 1;
1174 QuotaBlock->QuotaEntry[PsNonPagedPool].Peak = Process->QuotaPeak[PsNonPagedPool];
1175 QuotaBlock->QuotaEntry[PsPagedPool].Peak = Process->QuotaPeak[PsPagedPool];
1176 QuotaBlock->QuotaEntry[PsPageFile].Peak = Process->QuotaPeak[PsPageFile];
1177 QuotaBlock->QuotaEntry[PsNonPagedPool].Limit = PspDefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit;
1178 QuotaBlock->QuotaEntry[PsPagedPool].Limit = PspDefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit;
1179 QuotaBlock->QuotaEntry[PsPageFile].Limit = PspDefaultQuotaBlock.QuotaEntry[PsPageFile].Limit;
1180
1181 /* Try to exchange the quota block, if that failed, just drop it */
1182 OldQuotaBlock = InterlockedCompareExchangePointer((PVOID*)&Process->QuotaBlock,
1183 QuotaBlock,
1184 &PspDefaultQuotaBlock);
1185 if (OldQuotaBlock == &PspDefaultQuotaBlock)
1186 {
1187 /* Success, insert the new quota block */
1188 PspInsertQuotaBlock(QuotaBlock);
1189 }
1190 else
1191 {
1192 /* Failed, free the quota block and ignore it */
1193 ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK);
1194 }
1195
1196 Status = STATUS_SUCCESS;
1197 }
1198 else
1199 {
1200 Status = STATUS_SUCCESS;
1201 }
1202
1203 return Status;
1204 }
1205
1206 /* EOF */
1207