xref: /reactos/ntoskrnl/ps/quota.c (revision 8a978a17)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/ps/quota.c
5  * PURPOSE:         Process Pool Quotas
6  *
7  * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
8  *                  Mike Nordell
9  */
10 
11 /* INCLUDES **************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock;
18 static LIST_ENTRY PspQuotaBlockList = {&PspQuotaBlockList, &PspQuotaBlockList};
19 static KSPIN_LOCK PspQuotaLock;
20 
21 #define TAG_QUOTA_BLOCK 'bQsP'
22 #define VALID_QUOTA_FLAGS (QUOTA_LIMITS_HARDWS_MIN_ENABLE | \
23                            QUOTA_LIMITS_HARDWS_MIN_DISABLE | \
24                            QUOTA_LIMITS_HARDWS_MAX_ENABLE | \
25                            QUOTA_LIMITS_HARDWS_MAX_DISABLE)
26 
27 /* PRIVATE FUNCTIONS *******************************************************/
28 
29 /*
30  * Private helper to charge the specified process quota.
31  * ReturnsSTATUS_QUOTA_EXCEEDED on quota limit check failure.
32  * Updates QuotaPeak as needed for specified PoolIndex.
33  * TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
34  *       to replace UCHAR for 'PoolIndex'.
35  * Notes: Conceptually translation unit local/private.
36  */
37 NTSTATUS
38 NTAPI
39 PspChargeProcessQuotaSpecifiedPool(IN PEPROCESS Process,
40                                    IN UCHAR     PoolIndex,
41                                    IN SIZE_T    Amount)
42 {
43     ASSERT(Process);
44     ASSERT(Process != PsInitialSystemProcess);
45     ASSERT(PoolIndex <= 2);
46     ASSERT(Process->QuotaBlock);
47 
48     /* Note: Race warning. TODO: Needs to add/use lock for this */
49     if (Process->QuotaUsage[PoolIndex] + Amount >
50         Process->QuotaBlock->QuotaEntry[PoolIndex].Limit)
51     {
52         DPRINT1("Quota exceeded, but ROS will let it slide...\n");
53         return STATUS_SUCCESS;
54         //return STATUS_QUOTA_EXCEEDED; /* caller raises the exception */
55     }
56 
57     InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex], Amount);
58 
59     /* Note: Race warning. TODO: Needs to add/use lock for this */
60     if (Process->QuotaPeak[PoolIndex] < Process->QuotaUsage[PoolIndex])
61     {
62         Process->QuotaPeak[PoolIndex] = Process->QuotaUsage[PoolIndex];
63     }
64 
65     return STATUS_SUCCESS;
66 }
67 
68 /*
69  * Private helper to remove quota charge from the specified process quota.
70  * TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
71  *       to replace UCHAR for 'PoolIndex'.
72  * Notes: Conceptually translation unit local/private.
73  */
74 VOID
75 NTAPI
76 PspReturnProcessQuotaSpecifiedPool(IN PEPROCESS Process,
77                                    IN UCHAR     PoolIndex,
78                                    IN SIZE_T    Amount)
79 {
80     ASSERT(Process);
81     ASSERT(Process != PsInitialSystemProcess);
82     ASSERT(PoolIndex <= 2);
83     ASSERT(!(Amount & 0x80000000)); /* we need to be able to negate it */
84     if (Process->QuotaUsage[PoolIndex] < Amount)
85     {
86         DPRINT1("WARNING: Process->QuotaUsage sanity check failed.\n");
87     }
88     else
89     {
90         InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex],
91                                -(LONG)Amount);
92     }
93 }
94 
95 /* FUNCTIONS ***************************************************************/
96 
97 CODE_SEG("INIT")
98 VOID
99 NTAPI
100 PsInitializeQuotaSystem(VOID)
101 {
102     RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock));
103     PspDefaultQuotaBlock.QuotaEntry[PagedPool].Limit = (SIZE_T)-1;
104     PspDefaultQuotaBlock.QuotaEntry[NonPagedPool].Limit = (SIZE_T)-1;
105     PspDefaultQuotaBlock.QuotaEntry[2].Limit = (SIZE_T)-1; /* Page file */
106     PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock;
107 }
108 
109 VOID
110 NTAPI
111 PspInheritQuota(PEPROCESS Process, PEPROCESS ParentProcess)
112 {
113     if (ParentProcess != NULL)
114     {
115         PEPROCESS_QUOTA_BLOCK QuotaBlock = ParentProcess->QuotaBlock;
116 
117         ASSERT(QuotaBlock != NULL);
118 
119         (void)InterlockedIncrementUL(&QuotaBlock->ReferenceCount);
120 
121         Process->QuotaBlock = QuotaBlock;
122     }
123     else
124         Process->QuotaBlock = &PspDefaultQuotaBlock;
125 }
126 
127 VOID
128 NTAPI
129 PspInsertQuotaBlock(
130     PEPROCESS_QUOTA_BLOCK QuotaBlock)
131 {
132     KIRQL OldIrql;
133 
134     KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
135     InsertTailList(&PspQuotaBlockList, &QuotaBlock->QuotaList);
136     KeReleaseSpinLock(&PspQuotaLock, OldIrql);
137 }
138 
139 VOID
140 NTAPI
141 PspDestroyQuotaBlock(PEPROCESS Process)
142 {
143     PEPROCESS_QUOTA_BLOCK QuotaBlock = Process->QuotaBlock;
144     KIRQL OldIrql;
145 
146     if (QuotaBlock != &PspDefaultQuotaBlock &&
147         InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0)
148     {
149         KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
150         RemoveEntryList(&QuotaBlock->QuotaList);
151         KeReleaseSpinLock(&PspQuotaLock, OldIrql);
152         ExFreePool(QuotaBlock);
153     }
154 }
155 
156 /*
157  * @implemented
158  */
159 NTSTATUS
160 NTAPI
161 PsChargeProcessPageFileQuota(IN PEPROCESS Process,
162                              IN SIZE_T Amount)
163 {
164     /* Don't do anything for the system process */
165     if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
166 
167     return PspChargeProcessQuotaSpecifiedPool(Process, 2, Amount);
168 }
169 
170 /*
171  * @implemented
172  */
173 VOID
174 NTAPI
175 PsChargePoolQuota(IN PEPROCESS Process,
176                   IN POOL_TYPE PoolType,
177                   IN SIZE_T Amount)
178 {
179     NTSTATUS Status;
180     ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
181 
182     /* Don't do anything for the system process */
183     if (Process == PsInitialSystemProcess) return;
184 
185     /* Charge the usage */
186     Status = PsChargeProcessPoolQuota(Process, PoolType, Amount);
187     if (!NT_SUCCESS(Status)) ExRaiseStatus(Status);
188 }
189 
190 /*
191  * @implemented
192  */
193 NTSTATUS
194 NTAPI
195 PsChargeProcessNonPagedPoolQuota(IN PEPROCESS Process,
196                                  IN SIZE_T Amount)
197 {
198     /* Call the general function */
199     return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount);
200 }
201 
202 /*
203  * @implemented
204  */
205 NTSTATUS
206 NTAPI
207 PsChargeProcessPagedPoolQuota(IN PEPROCESS Process,
208                               IN SIZE_T Amount)
209 {
210     /* Call the general function */
211     return PsChargeProcessPoolQuota(Process, PagedPool, Amount);
212 }
213 
214 /*
215  * @implemented
216  */
217 NTSTATUS
218 NTAPI
219 PsChargeProcessPoolQuota(IN PEPROCESS Process,
220                          IN POOL_TYPE PoolType,
221                          IN SIZE_T Amount)
222 {
223     /* Don't do anything for the system process */
224     if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
225 
226     return PspChargeProcessQuotaSpecifiedPool(Process,
227                                               (PoolType & PAGED_POOL_MASK),
228                                               Amount);
229 }
230 
231 /*
232  * @implemented
233  */
234 VOID
235 NTAPI
236 PsReturnPoolQuota(IN PEPROCESS Process,
237                   IN POOL_TYPE PoolType,
238                   IN SIZE_T Amount)
239 {
240     /* Don't do anything for the system process */
241     if (Process == PsInitialSystemProcess) return;
242 
243     PspReturnProcessQuotaSpecifiedPool(Process,
244                                        (PoolType & PAGED_POOL_MASK),
245                                        Amount);
246 }
247 
248 /*
249  * @implemented
250  */
251 VOID
252 NTAPI
253 PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process,
254                                  IN SIZE_T Amount)
255 {
256     /* Don't do anything for the system process */
257     if (Process == PsInitialSystemProcess) return;
258 
259     PsReturnPoolQuota(Process, NonPagedPool, Amount);
260 }
261 
262 /*
263  * @implemented
264  */
265 VOID
266 NTAPI
267 PsReturnProcessPagedPoolQuota(IN PEPROCESS Process,
268                               IN SIZE_T Amount)
269 {
270     /* Don't do anything for the system process */
271     if (Process == PsInitialSystemProcess) return;
272 
273     PsReturnPoolQuota(Process, PagedPool, Amount);
274 }
275 
276 /*
277  * @implemented
278  */
279 NTSTATUS
280 NTAPI
281 PsReturnProcessPageFileQuota(IN PEPROCESS Process,
282                              IN SIZE_T Amount)
283 {
284     /* Don't do anything for the system process */
285     if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
286 
287     PspReturnProcessQuotaSpecifiedPool(Process, 2, Amount);
288     return STATUS_SUCCESS;
289 }
290 
291 NTSTATUS
292 NTAPI
293 PspSetQuotaLimits(
294     _In_ PEPROCESS Process,
295     _In_ ULONG Unused,
296     _In_ PVOID QuotaLimits,
297     _In_ ULONG QuotaLimitsLength,
298     _In_ KPROCESSOR_MODE PreviousMode)
299 {
300     QUOTA_LIMITS_EX CapturedQuotaLimits;
301     PEPROCESS_QUOTA_BLOCK QuotaBlock, OldQuotaBlock;
302     BOOLEAN IncreaseOkay;
303     KAPC_STATE SavedApcState;
304     NTSTATUS Status;
305 
306     UNREFERENCED_PARAMETER(Unused);
307 
308     _SEH2_TRY
309     {
310         ProbeForRead(QuotaLimits, QuotaLimitsLength, sizeof(ULONG));
311 
312         /* Check if we have the basic or extended structure */
313         if (QuotaLimitsLength == sizeof(QUOTA_LIMITS))
314         {
315             /* Copy the basic structure, zero init the remaining fields */
316             RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS));
317             CapturedQuotaLimits.WorkingSetLimit = 0;
318             CapturedQuotaLimits.Reserved2 = 0;
319             CapturedQuotaLimits.Reserved3 = 0;
320             CapturedQuotaLimits.Reserved4 = 0;
321             CapturedQuotaLimits.CpuRateLimit.RateData = 0;
322             CapturedQuotaLimits.Flags = 0;
323         }
324         else if (QuotaLimitsLength == sizeof(QUOTA_LIMITS_EX))
325         {
326             /* Copy the full structure */
327             RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS_EX));
328 
329             /* Verify that the caller passed valid flags */
330             if ((CapturedQuotaLimits.Flags & ~VALID_QUOTA_FLAGS) ||
331                 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_ENABLE) &&
332                  (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_DISABLE)) ||
333                 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_ENABLE) &&
334                  (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_DISABLE)))
335             {
336                 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits.Flags);
337                 _SEH2_YIELD(return STATUS_INVALID_PARAMETER);
338             }
339 
340             /* Verify that the caller didn't pass reserved values */
341             if ((CapturedQuotaLimits.WorkingSetLimit != 0) ||
342                 (CapturedQuotaLimits.Reserved2 != 0) ||
343                 (CapturedQuotaLimits.Reserved3 != 0) ||
344                 (CapturedQuotaLimits.Reserved4 != 0) ||
345                 (CapturedQuotaLimits.CpuRateLimit.RateData != 0))
346             {
347                 DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n",
348                         CapturedQuotaLimits.WorkingSetLimit,
349                         CapturedQuotaLimits.Reserved2,
350                         CapturedQuotaLimits.Reserved3,
351                         CapturedQuotaLimits.Reserved4,
352                         CapturedQuotaLimits.CpuRateLimit.RateData);
353                 _SEH2_YIELD(return STATUS_INVALID_PARAMETER);
354             }
355         }
356         else
357         {
358             DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength);
359             _SEH2_YIELD(return STATUS_INFO_LENGTH_MISMATCH);
360         }
361     }
362     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
363     {
364         DPRINT1("Exception while copying data\n");
365         _SEH2_YIELD(return _SEH2_GetExceptionCode());
366     }
367     _SEH2_END;
368 
369     /* Check the caller changes the working set size limits */
370     if ((CapturedQuotaLimits.MinimumWorkingSetSize != 0) &&
371         (CapturedQuotaLimits.MaximumWorkingSetSize != 0))
372     {
373         /* Check for special case: trimming the WS */
374         if ((CapturedQuotaLimits.MinimumWorkingSetSize == SIZE_T_MAX) &&
375             (CapturedQuotaLimits.MaximumWorkingSetSize == SIZE_T_MAX))
376         {
377             /* No increase allowed */
378             IncreaseOkay = FALSE;
379         }
380         else
381         {
382             /* Check if the caller has the required privilege */
383             IncreaseOkay = SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege,
384                                                   PreviousMode);
385         }
386 
387         /* Attach to the target process and disable APCs */
388         KeStackAttachProcess(&Process->Pcb, &SavedApcState);
389         KeEnterGuardedRegion();
390 
391         /* Call Mm to adjust the process' working set size */
392         Status = MmAdjustWorkingSetSize(CapturedQuotaLimits.MinimumWorkingSetSize,
393                                         CapturedQuotaLimits.MaximumWorkingSetSize,
394                                         0,
395                                         IncreaseOkay);
396 
397         /* Bring back APCs and detach from the process */
398         KeLeaveGuardedRegion();
399         KeUnstackDetachProcess(&SavedApcState);
400     }
401     else if (Process->QuotaBlock == &PspDefaultQuotaBlock)
402     {
403         /* Check if the caller has the required privilege */
404         if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, PreviousMode))
405         {
406             return STATUS_PRIVILEGE_NOT_HELD;
407         }
408 
409         /* Allocate a new quota block */
410         QuotaBlock = ExAllocatePoolWithTag(NonPagedPool,
411                                            sizeof(EPROCESS_QUOTA_BLOCK),
412                                            TAG_QUOTA_BLOCK);
413         if (QuotaBlock == NULL)
414         {
415             ObDereferenceObject(Process);
416             return STATUS_NO_MEMORY;
417         }
418 
419         /* Initialize the quota block */
420         QuotaBlock->ReferenceCount = 1;
421         QuotaBlock->ProcessCount = 1;
422         QuotaBlock->QuotaEntry[0].Peak = Process->QuotaPeak[0];
423         QuotaBlock->QuotaEntry[1].Peak = Process->QuotaPeak[1];
424         QuotaBlock->QuotaEntry[2].Peak = Process->QuotaPeak[2];
425         QuotaBlock->QuotaEntry[0].Limit = PspDefaultQuotaBlock.QuotaEntry[0].Limit;
426         QuotaBlock->QuotaEntry[1].Limit = PspDefaultQuotaBlock.QuotaEntry[1].Limit;
427         QuotaBlock->QuotaEntry[2].Limit = PspDefaultQuotaBlock.QuotaEntry[2].Limit;
428 
429         /* Try to exchange the quota block, if that failed, just drop it */
430         OldQuotaBlock = InterlockedCompareExchangePointer((PVOID*)&Process->QuotaBlock,
431                                                           QuotaBlock,
432                                                           &PspDefaultQuotaBlock);
433         if (OldQuotaBlock == &PspDefaultQuotaBlock)
434         {
435             /* Success, insert the new quota block */
436             PspInsertQuotaBlock(QuotaBlock);
437         }
438         else
439         {
440             /* Failed, free the quota block and ignore it */
441             ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK);
442         }
443 
444         Status = STATUS_SUCCESS;
445     }
446     else
447     {
448         Status = STATUS_SUCCESS;
449     }
450 
451     return Status;
452 }
453 
454 
455 /* EOF */
456