xref: /reactos/ntoskrnl/ps/quota.c (revision 1734f297)
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  */
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  */
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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