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