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