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