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 INIT_FUNCTION 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