Windows2000/private/ntos/ps/psjob.c
2020-09-30 17:12:32 +02:00

2797 lines
90 KiB
C

/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
psjob.c
Abstract:
This module implements bulk of the job object support
Author:
Mark Lucovsky (markl) 22-May-1997
Revision History:
--*/
#include "psp.h"
#include "winerror.h"
#pragma alloc_text(PAGE, PspJobDelete)
#pragma alloc_text(PAGE, PspJobClose)
#pragma alloc_text(PAGE, NtCreateJobObject)
#pragma alloc_text(PAGE, NtOpenJobObject)
#pragma alloc_text(PAGE, NtAssignProcessToJobObject)
#pragma alloc_text(PAGE, PspAddProcessToJob)
#pragma alloc_text(PAGE, PspRemoveProcessFromJob)
#pragma alloc_text(PAGE, PspExitProcessFromJob)
#pragma alloc_text(PAGE, NtQueryInformationJobObject)
#pragma alloc_text(PAGE, NtSetInformationJobObject)
#pragma alloc_text(PAGE, PspApplyJobLimitsToProcessSet)
#pragma alloc_text(PAGE, PspApplyJobLimitsToProcess)
#pragma alloc_text(PAGE, NtTerminateJobObject)
#pragma alloc_text(PAGE, PspTerminateAllProcessesInJob)
#pragma alloc_text(PAGE, PspFoldProcessAccountingIntoJob)
#pragma alloc_text(PAGE, PspCaptureTokenFilter)
// move to io.h
extern POBJECT_TYPE IoCompletionObjectType;
NTSTATUS
NTAPI
NtCreateJobObject (
OUT PHANDLE JobHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
)
{
PEJOB Job;
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
PAGED_CODE();
// Establish an exception handler, probe the output handle address, and
// attempt to create a job object. If the probe fails, then return the
// exception code as the service status. Otherwise return the status value
// returned by the object insertion routine.
try {
// Get previous processor mode and probe output handle address if
// necessary.
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
ProbeForWriteHandle(JobHandle);
}
// Allocate job object.
Status = ObCreateObject(
PreviousMode,
PsJobType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(EJOB),
0,
0,
(PVOID *)&Job
);
// If the job object was successfully allocated, then initialize it
// and attempt to insert the job object in the current
// process' handle table.
if (NT_SUCCESS(Status)) {
RtlZeroMemory(Job,sizeof(EJOB));
InitializeListHead(&Job->ProcessListHead);
KeInitializeEvent(&Job->Event,NotificationEvent,FALSE);
// Job Object gets the SessionId of the Process creating the Job
// We will use this sessionid to restrict the processes that can
// be added to a job.
Job->SessionId = PsGetCurrentProcess()->SessionId;
// Initialize the scheduling class for the Job
Job->SchedulingClass = PSP_DEFAULT_SCHEDULING_CLASSES;
// Insert Job on Job List
ExAcquireFastMutex(&PspJobListLock);
InsertTailList(&PspJobList,&Job->JobLinks);
ExReleaseFastMutex(&PspJobListLock);
Status = ExInitializeResource(&Job->JobLock);
if ( !NT_SUCCESS(Status) ) {
// Note that ExInitializeResource really can't fail and
// is hard coded to return success.
ObDereferenceObject(Job);
}
else {
ExInitializeFastMutex(&Job->MemoryLimitsLock);
Status = ObInsertObject(
(PVOID)Job,
NULL,
DesiredAccess,
0,
(PVOID *)NULL,
&Handle
);
}
// If the job object was successfully inserted in the current
// process' handle table, then attempt to write the job object
// handle value. If the write attempt fails, then do not report
// an error. When the caller attempts to access the handle value,
// an access violation will occur.
if (NT_SUCCESS(Status)) {
try {
*JobHandle = Handle;
}
except(ExSystemExceptionFilter()) {
}
}
}
// If an exception occurs during the probe of the output handle address,
// then always handle the exception and return the exception code as the
// status value.
}
except(ExSystemExceptionFilter()) {
return GetExceptionCode();
}
// Return service status.
return Status;
}
NTSTATUS
NTAPI
NtOpenJobObject(
OUT PHANDLE JobHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
)
{
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
PAGED_CODE();
// Establish an exception handler, probe the output handle address, and
// attempt to open the job object. If the probe fails, then return the
// exception code as the service status. Otherwise return the status value
// returned by the object open routine.
try {
// Get previous processor mode and probe output handle address
// if necessary.
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
ProbeForWriteHandle(JobHandle);
}
// Open handle to the event object with the specified desired access.
Status = ObOpenObjectByName(
ObjectAttributes,
PsJobType,
PreviousMode,
NULL,
DesiredAccess,
NULL,
&Handle
);
// If the open was successful, then attempt to write the job object
// handle value. If the write attempt fails, then do not report an
// error. When the caller attempts to access the handle value, an
// access violation will occur.
if (NT_SUCCESS(Status)) {
try {
*JobHandle = Handle;
}
except(ExSystemExceptionFilter()) {
}
}
}
except(ExSystemExceptionFilter()) {
// If an exception occurs during the probe of the output job handle,
// then always handle the exception and return the exception code as the
// status value.
Status = GetExceptionCode();
}
return Status;
}
NTSTATUS
NTAPI
NtAssignProcessToJobObject(
IN HANDLE JobHandle,
IN HANDLE ProcessHandle
)
{
PEJOB Job;
PEPROCESS Process;
NTSTATUS Status;
KPROCESSOR_MODE PreviousMode;
BOOLEAN IsAdmin ;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
// Reference the process object, lock the process, test for already been assigned
Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_SET_QUOTA | PROCESS_TERMINATE, PsProcessType, PreviousMode, (PVOID *)&Process, NULL);
if ( !NT_SUCCESS(Status) ) {
return Status;
}
// Quick Check for prior assignment
if ( Process->Job ) {
ObDereferenceObject(Process);
return STATUS_ACCESS_DENIED;
}
// Now reference the job object. Then we need to lock the process and check again
Status = ObReferenceObjectByHandle(
JobHandle,
JOB_OBJECT_ASSIGN_PROCESS,
PsJobType,
PreviousMode,
(PVOID *)&Job,
NULL
);
if ( !NT_SUCCESS(Status) ) {
ObDereferenceObject(Process);
return Status;
}
// We only allow a process that is running in the Job creator's hydra session
// to be assigned to the job.
if ( Process->SessionId != Job->SessionId ) {
ObDereferenceObject(Process);
ObDereferenceObject(Job);
return STATUS_ACCESS_DENIED;
}
Status = PsLockProcess(Process,PreviousMode,PsLockPollOnTimeout);
if ( !NT_SUCCESS(Status) || Process->Job ) {
if ( !NT_SUCCESS(Status) ) {
Status = STATUS_PROCESS_IS_TERMINATING;
}
else {
Status = STATUS_ACCESS_DENIED;
PsUnlockProcess(Process);
}
ObDereferenceObject(Process);
ObDereferenceObject(Job);
return Status ;
}
// Security Rules: If the job has no-admin set, and it is running
// as admin, that's not allowed
if ( Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN )
{
PsLockProcessSecurityFields();
IsAdmin = SeTokenIsAdmin( Process->Token );
PsFreeProcessSecurityFields();
if ( IsAdmin )
{
Status = STATUS_ACCESS_DENIED ;
PsUnlockProcess( Process );
ObDereferenceObject( Process );
ObDereferenceObject( Job );
return Status ;
}
}
// If the job has a token filter established,
// use it to filter the
// ref the job for the process
ObReferenceObject(Job);
Process->Job = Job;
PsUnlockProcess(Process);
Status = PspAddProcessToJob(Job,Process);
if ( !NT_SUCCESS(Status) ) {
Job->TotalTerminatedProcesses++;
PspTerminateProcess(Process,ERROR_NOT_ENOUGH_QUOTA,PsLockPollOnTimeout);
}
// If the job has UI restrictions and this is a GUI process, call ntuser
if ( ( Job->UIRestrictionsClass != JOB_OBJECT_UILIMIT_NONE ) &&
( Process->Win32Process != NULL ) ) {
WIN32_JOBCALLOUT_PARAMETERS Parms;
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
Parms.Job = Job;
Parms.CalloutType = PsW32JobCalloutAddProcess;
Parms.Data = Process->Win32Process;
MmDispatchWin32Callout( PspW32JobCallout,NULL, (PVOID)&Parms, &(Job->SessionId));
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
}
if ( Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_ONLY_TOKEN )
{
Status = PspSetPrimaryToken( ProcessHandle, NULL, Job->Token );
if ( !NT_SUCCESS( Status ) )
{
// What?
}
}
ObDereferenceObject(Process);
ObDereferenceObject(Job);
return Status;
}
NTSTATUS
PspAddProcessToJob(
PEJOB Job,
PEPROCESS Process
)
{
NTSTATUS Status;
SIZE_T MinWs,MaxWs;
PAGED_CODE();
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
InsertTailList(&Job->ProcessListHead,&Process->JobLinks);
// Update relevant ADD accounting info.
Job->TotalProcesses++;
Job->ActiveProcesses++;
// Test for active process count exceeding limit
Status = STATUS_SUCCESS;
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_ACTIVE_PROCESS &&
Job->ActiveProcesses > Job->ActiveProcessLimit ) {
PS_SET_CLEAR_BITS (&Process->JobStatus,
PS_JOB_STATUS_NOT_REALLY_ACTIVE | PS_JOB_STATUS_ACCOUNTING_FOLDED,
PS_JOB_STATUS_LAST_REPORT_MEMORY);
Job->ActiveProcesses--;
if ( Job->CompletionPort ) {
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
NULL,
STATUS_SUCCESS,
JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT,
TRUE
);
}
Status = STATUS_QUOTA_EXCEEDED;
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME && KeReadStateEvent(&Job->Event) ) {
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NOT_REALLY_ACTIVE | PS_JOB_STATUS_ACCOUNTING_FOLDED);
Job->ActiveProcesses--;
Status = STATUS_QUOTA_EXCEEDED;
}
if ( Status == STATUS_SUCCESS ) {
PspApplyJobLimitsToProcess(Job,Process);
if ( Process->Job->CompletionPort
&& Process->UniqueProcessId
&& !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE)
&& !(Process->JobStatus & PS_JOB_STATUS_NEW_PROCESS_REPORTED)) {
PS_SET_CLEAR_BITS (&Process->JobStatus,
PS_JOB_STATUS_NEW_PROCESS_REPORTED,
PS_JOB_STATUS_LAST_REPORT_MEMORY);
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
(PVOID)Process->UniqueProcessId,
STATUS_SUCCESS,
JOB_OBJECT_MSG_NEW_PROCESS,
FALSE
);
}
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET ) {
MinWs = Job->MinimumWorkingSetSize;
MaxWs = Job->MaximumWorkingSetSize;
}
else {
MinWs = 0;
MaxWs = 0;
}
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
if ( Status == STATUS_SUCCESS ) {
if ( MinWs != 0 && MaxWs != 0 ) {
KeEnterCriticalRegion();
ExAcquireFastMutexUnsafe(&PspWorkingSetChangeHead.Lock);
KeAttachProcess (&Process->Pcb);
MmAdjustWorkingSetSize(MinWs,MaxWs,FALSE);
// call MM to Enable hard workingset
MmEnforceWorkingSetLimit(&Process->Vm, TRUE);
KeDetachProcess();
ExReleaseFastMutexUnsafe(&PspWorkingSetChangeHead.Lock);
KeLeaveCriticalRegion();
}
else {
MmEnforceWorkingSetLimit(&Process->Vm, FALSE);
}
if ( !MmAssignProcessToJob(Process) ) {
Status = STATUS_QUOTA_EXCEEDED;
}
}
return Status;
}
// Only callable from process delete routine !
// This means that if the above fails, failure is termination of the process !
VOID
PspRemoveProcessFromJob(
PEJOB Job,
PEPROCESS Process
)
{
PAGED_CODE();
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
RemoveEntryList(&Process->JobLinks);
// Update REMOVE accounting info
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
Job->ActiveProcesses--;
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NOT_REALLY_ACTIVE);
}
PspFoldProcessAccountingIntoJob(Job,Process);
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
}
VOID
PspExitProcessFromJob(
PEJOB Job,
PEPROCESS Process
)
{
PAGED_CODE();
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
// Update REMOVE accounting info
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
Job->ActiveProcesses--;
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NOT_REALLY_ACTIVE);
}
PspFoldProcessAccountingIntoJob(Job,Process);
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
}
VOID
PspJobDelete(
IN PVOID Object
)
{
PEJOB Job;
WIN32_JOBCALLOUT_PARAMETERS Parms;
PAGED_CODE();
Job = (PEJOB) Object;
// call ntuser to delete its job structure
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
Parms.Job = Job;
Parms.CalloutType = PsW32JobCalloutTerminate;
Parms.Data = NULL;
MmDispatchWin32Callout( PspW32JobCallout,NULL, (PVOID)&Parms, &(Job->SessionId));
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
Job->LimitFlags = 0;
if ( Job->CompletionPort ) {
ObDereferenceObject(Job->CompletionPort);
Job->CompletionPort = NULL;
}
// Remove Job on Job List
ExAcquireFastMutex(&PspJobListLock);
RemoveEntryList(&Job->JobLinks);
ExReleaseFastMutex(&PspJobListLock);
// Free Security clutter:
if ( Job->Token ) {
ObDereferenceObject( Job->Token );
Job->Token = NULL ;
}
if ( Job->Filter ) {
if ( Job->Filter->CapturedSids ) {
ExFreePool( Job->Filter->CapturedSids );
}
if ( Job->Filter->CapturedPrivileges ) {
ExFreePool( Job->Filter->CapturedPrivileges );
}
if ( Job->Filter->CapturedGroups ) {
ExFreePool( Job->Filter->CapturedGroups );
}
ExFreePool( Job->Filter );
}
ExDeleteResource(&Job->JobLock);
}
VOID
PspJobClose (
IN PEPROCESS Process,
IN PVOID Object,
IN ACCESS_MASK GrantedAccess,
IN ULONG ProcessHandleCount,
IN ULONG SystemHandleCount
)
/*++
Routine Description:
Called by the object manager when a handle is closed to the object.
Arguments:
Process - Process doing the close
Object - Job object being closed
GrantedAccess - Access ranted for this handle
ProcessHandleCount - Unused and unmaintained by OB
SystemHandleCount - Current handle count for this object
Return Value:
None.
--*/
{
PEJOB Job = Object;
PVOID Port;
PAGED_CODE ();
// If this isn't the last handle then do nothing.
if (SystemHandleCount > 1) {
return;
}
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
// Release the completion port
Port = Job->CompletionPort;
Job->CompletionPort = NULL;
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
if (Port != NULL) {
ObDereferenceObject(Port);
}
}
ULONG PspJobInfoLengths[] = {
sizeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), // JobObjectBasicAccountingInformation
sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION), // JobObjectBasicLimitInformation
sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST), // JobObjectBasicProcessIdList
sizeof(JOBOBJECT_BASIC_UI_RESTRICTIONS), // JobObjectBasicUIRestrictions
sizeof(JOBOBJECT_SECURITY_LIMIT_INFORMATION), // JobObjectSecurityLimitInformation
sizeof(JOBOBJECT_END_OF_JOB_TIME_INFORMATION), // JobObjectEndOfJobTimeInformation
sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT), // JobObjectAssociateCompletionPortInformation
sizeof(JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION), // JobObjectBasicAndIoAccountingInformation
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), // JobObjectExtendedLimitInformation
0
};
#if defined(_ALPHA_)
// Alpha's run with aligned stacks, so we can require quad alignment
ULONG PspJobInfoAlign[] = {
sizeof(LARGE_INTEGER), // JobObjectBasicAccountingInformation
sizeof(LARGE_INTEGER), // JobObjectBasicLimitInformation
sizeof(ULONG), // JobObjectBasicProcessIdList
sizeof(ULONG), // JobObjectBasicUIRestrictions
sizeof(ULONG), // JobObjectSecurityLimitInformation
sizeof(ULONG), // JobObjectEndOfJobTimeInformation
sizeof(PVOID), // JobObjectAssociateCompletionPortInformation
sizeof(LARGE_INTEGER), // JobObjectBasicAndIoAccountingInformation
sizeof(LARGE_INTEGER), // JobObjectExtendedLimitInformation
0
};
#else
ULONG PspJobInfoAlign[] = {
sizeof(ULONG), // JobObjectBasicAccountingInformation
sizeof(ULONG), // JobObjectBasicLimitInformation
sizeof(ULONG), // JobObjectBasicProcessIdList
sizeof(ULONG), // JobObjectBasicUIRestrictions
sizeof(ULONG), // JobObjectSecurityLimitInformation
sizeof(ULONG), // JobObjectEndOfJobTimeInformation
sizeof(PVOID), // JobObjectAssociateCompletionPortInformation
sizeof(ULONG), // JobObjectBasicAndIoAccountingInformation
sizeof(ULONG), // JobObjectExtendedLimitInformation
0
};
#endif // _ALPHA_
NTSTATUS
NtQueryInformationJobObject(
IN HANDLE JobHandle,
IN JOBOBJECTINFOCLASS JobObjectInformationClass,
OUT PVOID JobObjectInformation,
IN ULONG JobObjectInformationLength,
OUT PULONG ReturnLength OPTIONAL
)
{
PEJOB Job;
KPROCESSOR_MODE PreviousMode;
JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION AccountingInfo;
JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInfo;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimitInfo;
JOBOBJECT_BASIC_UI_RESTRICTIONS BasicUIRestrictions;
JOBOBJECT_SECURITY_LIMIT_INFORMATION SecurityLimitInfo ;
NTSTATUS st;
ULONG RequiredLength, RequiredAlign, ActualReturnLength;
PVOID ReturnData;
PEPROCESS Process;
PLIST_ENTRY Next;
LARGE_INTEGER UserTime, KernelTime;
PULONG_PTR NextProcessIdSlot;
ULONG WorkingLength;
PJOBOBJECT_BASIC_PROCESS_ID_LIST IdList;
PUCHAR CurrentOffset ;
PTOKEN_GROUPS WorkingGroup ;
PTOKEN_PRIVILEGES WorkingPrivs ;
ULONG RemainingSidBuffer ;
PSID TargetSidBuffer ;
PSID RemainingSid ;
BOOLEAN AlreadyCopied ;
PAGED_CODE();
// Get previous processor mode and probe output argument if necessary.
if ( JobObjectInformationClass >= MaxJobObjectInfoClass || JobObjectInformationClass <= 0) {
return STATUS_INVALID_INFO_CLASS;
}
RequiredLength = PspJobInfoLengths[JobObjectInformationClass-1];
RequiredAlign = PspJobInfoAlign[JobObjectInformationClass-1];
ActualReturnLength = RequiredLength;
if ( JobObjectInformationLength != RequiredLength ) {
// BasicProcessIdList is variable length, so make sure header is
// ok. Can not enforce an exact match ! Security Limits can be
// as well, due to the token groups and privs
if ( ( JobObjectInformationClass == JobObjectBasicProcessIdList ) ||
( JobObjectInformationClass == JobObjectSecurityLimitInformation ) ) {
if ( JobObjectInformationLength < RequiredLength ) {
return STATUS_INFO_LENGTH_MISMATCH;
}
else {
RequiredLength = JobObjectInformationLength;
}
}
else {
return STATUS_INFO_LENGTH_MISMATCH;
}
}
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
try {
ProbeForWrite(
JobObjectInformation,
JobObjectInformationLength,
RequiredAlign
);
if (ARGUMENT_PRESENT(ReturnLength)) {
ProbeForWriteUlong(ReturnLength);
}
}
except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
}
// reference the job
if ( ARGUMENT_PRESENT(JobHandle) ) {
st = ObReferenceObjectByHandle(
JobHandle,
JOB_OBJECT_QUERY,
PsJobType,
PreviousMode,
(PVOID *)&Job,
NULL
);
if ( !NT_SUCCESS(st) ) {
return st;
}
}
else {
// if the current process has a job, NULL means the job of the
// current process. Query is always allowed for this case
Process = PsGetCurrentProcess();
if ( Process->Job ) {
Job = Process->Job;
ObReferenceObject(Job);
}
else {
return STATUS_ACCESS_DENIED;
}
}
AlreadyCopied = FALSE ;
KeEnterCriticalRegion();
ExAcquireResourceShared(&Job->JobLock, TRUE);
// Check argument validity.
switch ( JobObjectInformationClass ) {
case JobObjectBasicAccountingInformation:
case JobObjectBasicAndIoAccountingInformation:
// These two cases are identical, EXCEPT that with AndIo, IO information
// is returned as well, but the first part of the local is identical to
// basic, and the shorter return'd data length chops what we return.
RtlZeroMemory(&AccountingInfo.IoInfo,sizeof(AccountingInfo.IoInfo));
AccountingInfo.BasicInfo.TotalUserTime = Job->TotalUserTime;
AccountingInfo.BasicInfo.TotalKernelTime = Job->TotalKernelTime;
AccountingInfo.BasicInfo.ThisPeriodTotalUserTime = Job->ThisPeriodTotalUserTime;
AccountingInfo.BasicInfo.ThisPeriodTotalKernelTime = Job->ThisPeriodTotalKernelTime;
AccountingInfo.BasicInfo.TotalPageFaultCount = Job->TotalPageFaultCount;
AccountingInfo.BasicInfo.TotalProcesses = Job->TotalProcesses;
AccountingInfo.BasicInfo.ActiveProcesses = Job->ActiveProcesses;
AccountingInfo.BasicInfo.TotalTerminatedProcesses = Job->TotalTerminatedProcesses;
AccountingInfo.IoInfo.ReadOperationCount = Job->ReadOperationCount;
AccountingInfo.IoInfo.WriteOperationCount = Job->WriteOperationCount;
AccountingInfo.IoInfo.OtherOperationCount = Job->OtherOperationCount;
AccountingInfo.IoInfo.ReadTransferCount = Job->ReadTransferCount;
AccountingInfo.IoInfo.WriteTransferCount = Job->WriteTransferCount;
AccountingInfo.IoInfo.OtherTransferCount = Job->OtherTransferCount;
// Add in the time and page faults for each process
Next = Job->ProcessListHead.Flink;
while ( Next != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(Next,EPROCESS,JobLinks));
if ( !(Process->JobStatus & PS_JOB_STATUS_ACCOUNTING_FOLDED) ) {
UserTime.QuadPart = UInt32x32To64(Process->Pcb.UserTime,KeMaximumIncrement);
KernelTime.QuadPart = UInt32x32To64(Process->Pcb.KernelTime,KeMaximumIncrement);
AccountingInfo.BasicInfo.TotalUserTime.QuadPart += UserTime.QuadPart;
AccountingInfo.BasicInfo.TotalKernelTime.QuadPart += KernelTime.QuadPart;
AccountingInfo.BasicInfo.ThisPeriodTotalUserTime.QuadPart += UserTime.QuadPart;
AccountingInfo.BasicInfo.ThisPeriodTotalKernelTime.QuadPart += KernelTime.QuadPart;
AccountingInfo.BasicInfo.TotalPageFaultCount += Process->Vm.PageFaultCount;
AccountingInfo.IoInfo.ReadOperationCount += Process->ReadOperationCount.QuadPart;
AccountingInfo.IoInfo.WriteOperationCount += Process->WriteOperationCount.QuadPart;
AccountingInfo.IoInfo.OtherOperationCount += Process->OtherOperationCount.QuadPart;
AccountingInfo.IoInfo.ReadTransferCount += Process->ReadTransferCount.QuadPart;
AccountingInfo.IoInfo.WriteTransferCount += Process->WriteTransferCount.QuadPart;
AccountingInfo.IoInfo.OtherTransferCount += Process->OtherTransferCount.QuadPart;
}
Next = Next->Flink;
}
ReturnData = &AccountingInfo;
st = STATUS_SUCCESS;
break;
case JobObjectExtendedLimitInformation:
case JobObjectBasicLimitInformation:
// Get the Basic Information
ExtendedLimitInfo.BasicLimitInformation.LimitFlags = Job->LimitFlags;
ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize = Job->MinimumWorkingSetSize;
ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize = Job->MaximumWorkingSetSize;
ExtendedLimitInfo.BasicLimitInformation.ActiveProcessLimit = Job->ActiveProcessLimit;
ExtendedLimitInfo.BasicLimitInformation.PriorityClass = (ULONG)Job->PriorityClass;
ExtendedLimitInfo.BasicLimitInformation.SchedulingClass = Job->SchedulingClass;
ExtendedLimitInfo.BasicLimitInformation.Affinity = (ULONG_PTR)Job->Affinity;
ExtendedLimitInfo.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart = Job->PerProcessUserTimeLimit.QuadPart;
ExtendedLimitInfo.BasicLimitInformation.PerJobUserTimeLimit.QuadPart = Job->PerJobUserTimeLimit.QuadPart;
ReturnData = &ExtendedLimitInfo.BasicLimitInformation;
if ( JobObjectInformationClass == JobObjectExtendedLimitInformation ) {
// Get Extended Information
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
ExtendedLimitInfo.ProcessMemoryLimit = Job->ProcessMemoryLimit << PAGE_SHIFT;
ExtendedLimitInfo.JobMemoryLimit = Job->JobMemoryLimit << PAGE_SHIFT;
ExtendedLimitInfo.PeakJobMemoryUsed = Job->PeakJobMemoryUsed << PAGE_SHIFT;
ExtendedLimitInfo.PeakProcessMemoryUsed = Job->PeakProcessMemoryUsed << PAGE_SHIFT;
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
// Zero un-used I/O counters
RtlZeroMemory(&ExtendedLimitInfo.IoInfo,sizeof(ExtendedLimitInfo.IoInfo));
ReturnData = &ExtendedLimitInfo;
}
st = STATUS_SUCCESS;
break;
case JobObjectBasicUIRestrictions:
BasicUIRestrictions.UIRestrictionsClass = Job->UIRestrictionsClass;
ReturnData = &BasicUIRestrictions;
st = STATUS_SUCCESS;
break;
case JobObjectBasicProcessIdList:
IdList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)JobObjectInformation;
NextProcessIdSlot = &IdList->ProcessIdList[0];
WorkingLength = FIELD_OFFSET(JOBOBJECT_BASIC_PROCESS_ID_LIST,ProcessIdList);
AlreadyCopied = TRUE ;
try {
// Acounted for in the workinglength = 2*sizeof(ULONG)
IdList->NumberOfAssignedProcesses = Job->ActiveProcesses;
IdList->NumberOfProcessIdsInList = 0;
Next = Job->ProcessListHead.Flink;
while ( Next != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(Next,EPROCESS,JobLinks));
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
if ( !Process->UniqueProcessId ) {
IdList->NumberOfAssignedProcesses--;
}
else {
if ( (RequiredLength - WorkingLength) >= sizeof(ULONG_PTR) ) {
*NextProcessIdSlot++ = (ULONG_PTR)Process->UniqueProcessId;
WorkingLength += sizeof(ULONG_PTR);
IdList->NumberOfProcessIdsInList++;
}
else {
st = STATUS_BUFFER_OVERFLOW;
ActualReturnLength = WorkingLength;
break;
}
}
}
Next = Next->Flink;
}
ActualReturnLength = WorkingLength;
}
except ( ExSystemExceptionFilter() ) {
st = GetExceptionCode();
ActualReturnLength = 0;
}
break;
case JobObjectSecurityLimitInformation:
RtlZeroMemory( &SecurityLimitInfo, sizeof( SecurityLimitInfo ) );
SecurityLimitInfo.SecurityLimitFlags = Job->SecurityLimitFlags ;
ReturnData = &SecurityLimitInfo;
st = STATUS_SUCCESS;
// If a filter is present, then we have an ugly marshalling to do.
if ( Job->Filter )
{
// Compute size needed:
WorkingLength = Job->Filter->CapturedSidsLength +
Job->Filter->CapturedGroupsLength +
Job->Filter->CapturedPrivilegesLength ;
WorkingLength = 0 ;
// For each field, if it is present, include the extra stuff
if ( Job->Filter->CapturedSidsLength )
{
WorkingLength += Job->Filter->CapturedSidsLength +
sizeof( ULONG );
}
if ( Job->Filter->CapturedGroupsLength )
{
WorkingLength += Job->Filter->CapturedGroupsLength +
sizeof( ULONG );
}
if ( Job->Filter->CapturedPrivilegesLength )
{
WorkingLength += Job->Filter->CapturedPrivilegesLength +
sizeof( ULONG );
}
RequiredLength -= sizeof( SecurityLimitInfo );
if ( WorkingLength > RequiredLength )
{
st = STATUS_BUFFER_OVERFLOW ;
ActualReturnLength = WorkingLength + sizeof( SecurityLimitInfo );
break;
}
CurrentOffset = (PUCHAR) (JobObjectInformation) + sizeof( SecurityLimitInfo );
try {
if ( Job->Filter->CapturedSidsLength )
{
WorkingGroup = (PTOKEN_GROUPS) CurrentOffset ;
CurrentOffset += sizeof( ULONG );
SecurityLimitInfo.RestrictedSids = WorkingGroup ;
WorkingGroup->GroupCount = Job->Filter->CapturedSidCount ;
TargetSidBuffer = (PSID) (CurrentOffset +
sizeof( SID_AND_ATTRIBUTES ) *
Job->Filter->CapturedSidCount );
st = RtlCopySidAndAttributesArray(
Job->Filter->CapturedSidCount,
Job->Filter->CapturedSids,
WorkingLength,
WorkingGroup->Groups,
TargetSidBuffer,
&RemainingSid,
&RemainingSidBuffer );
CurrentOffset += Job->Filter->CapturedSidsLength ;
}
if ( !NT_SUCCESS( st ) )
{
leave ;
}
if ( Job->Filter->CapturedGroupsLength )
{
WorkingGroup = (PTOKEN_GROUPS) CurrentOffset ;
CurrentOffset += sizeof( ULONG );
SecurityLimitInfo.SidsToDisable = WorkingGroup ;
WorkingGroup->GroupCount = Job->Filter->CapturedGroupCount ;
TargetSidBuffer = (PSID) (CurrentOffset +
sizeof( SID_AND_ATTRIBUTES ) *
Job->Filter->CapturedGroupCount );
st = RtlCopySidAndAttributesArray(
Job->Filter->CapturedGroupCount,
Job->Filter->CapturedGroups,
WorkingLength,
WorkingGroup->Groups,
TargetSidBuffer,
&RemainingSid,
&RemainingSidBuffer );
CurrentOffset += Job->Filter->CapturedGroupsLength ;
}
if ( !NT_SUCCESS( st ) )
{
leave ;
}
if ( Job->Filter->CapturedPrivilegesLength )
{
WorkingPrivs = (PTOKEN_PRIVILEGES) CurrentOffset;
CurrentOffset += sizeof( ULONG );
SecurityLimitInfo.PrivilegesToDelete = WorkingPrivs ;
WorkingPrivs->PrivilegeCount = Job->Filter->CapturedPrivilegeCount ;
RtlCopyMemory( WorkingPrivs->Privileges,
Job->Filter->CapturedPrivileges,
Job->Filter->CapturedPrivilegesLength );
}
AlreadyCopied = TRUE ;
RtlCopyMemory( JobObjectInformation,
&SecurityLimitInfo,
sizeof( SecurityLimitInfo ) );
}
except (EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
ActualReturnLength = 0 ;
break;
}
}
break;
default:
st = STATUS_INVALID_INFO_CLASS;
}
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
// Finish Up
ObDereferenceObject(Job);
if ( NT_SUCCESS(st) ) {
// Either of these may cause an access violation. The
// exception handler will return access violation as
// status code. No further cleanup needs to be done.
try {
if ( !AlreadyCopied ) {
RtlCopyMemory(JobObjectInformation,ReturnData,RequiredLength);
}
if (ARGUMENT_PRESENT(ReturnLength) ) {
*ReturnLength = ActualReturnLength;
}
}
except(EXCEPTION_EXECUTE_HANDLER) {
return STATUS_SUCCESS;
}
}
return st;
}
NTSTATUS
NtSetInformationJobObject(
IN HANDLE JobHandle,
IN JOBOBJECTINFOCLASS JobObjectInformationClass,
IN PVOID JobObjectInformation,
IN ULONG JobObjectInformationLength
)
{
PEJOB Job;
EJOB LocalJob;
KPROCESSOR_MODE PreviousMode;
NTSTATUS st;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimitInfo;
JOBOBJECT_BASIC_UI_RESTRICTIONS BasicUIRestrictions;
JOBOBJECT_SECURITY_LIMIT_INFORMATION SecurityLimitInfo ;
JOBOBJECT_END_OF_JOB_TIME_INFORMATION EndOfJobInfo;
JOBOBJECT_ASSOCIATE_COMPLETION_PORT AssociateInfo;
ULONG RequiredAccess ;
ULONG RequiredLength, RequiredAlign;
PEPROCESS Process;
BOOLEAN HasPrivilege;
BOOLEAN IsChild ;
PLIST_ENTRY Next;
PPS_JOB_TOKEN_FILTER Filter ;
PVOID IoCompletion;
PACCESS_TOKEN LocalToken ;
ULONG ValidFlags;
ULONG LimitFlags;
BOOLEAN ProcessWorkingSetHead = FALSE;
PJOB_WORKING_SET_CHANGE_RECORD WsChangeRecord;
PAGED_CODE();
// Get previous processor mode and probe output argument if necessary.
if ( JobObjectInformationClass >= MaxJobObjectInfoClass || JobObjectInformationClass <= 0) {
return STATUS_INVALID_INFO_CLASS;
}
RequiredLength = PspJobInfoLengths[JobObjectInformationClass-1];
RequiredAlign = PspJobInfoAlign[JobObjectInformationClass-1];
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
try {
ProbeForRead(
JobObjectInformation,
JobObjectInformationLength,
RequiredAlign
);
}
except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
}
if ( JobObjectInformationLength != RequiredLength ) {
return STATUS_INFO_LENGTH_MISMATCH;
}
// reference the job
if ( JobObjectInformationClass == JobObjectSecurityLimitInformation )
{
RequiredAccess = JOB_OBJECT_SET_SECURITY_ATTRIBUTES ;
}
else
{
RequiredAccess = JOB_OBJECT_SET_ATTRIBUTES ;
}
st = ObReferenceObjectByHandle(
JobHandle,
RequiredAccess,
PsJobType,
PreviousMode,
(PVOID *)&Job,
NULL
);
if ( !NT_SUCCESS(st) ) {
return st;
}
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
// Check argument validity.
switch ( JobObjectInformationClass ) {
case JobObjectExtendedLimitInformation:
case JobObjectBasicLimitInformation:
try {
RtlCopyMemory(&ExtendedLimitInfo,JobObjectInformation,RequiredLength);
}
except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
// sanity check LimitFlags
if ( JobObjectInformationClass == JobObjectBasicLimitInformation) {
ValidFlags = JOB_OBJECT_BASIC_LIMIT_VALID_FLAGS;
}
else {
ValidFlags = JOB_OBJECT_EXTENDED_LIMIT_VALID_FLAGS;
}
if ( ExtendedLimitInfo.BasicLimitInformation.LimitFlags & ~ValidFlags ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LimitFlags = ExtendedLimitInfo.BasicLimitInformation.LimitFlags;
// Deal with each of the various limit flags
LocalJob.LimitFlags = Job->LimitFlags;
// ACTIVE PROCESS LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_ACTIVE_PROCESS ) {
// Active Process Limit is NOT retroactive. New processes are denied,
// but existing ones are not killed just because the limit is
// reduced.
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
LocalJob.ActiveProcessLimit = ExtendedLimitInfo.BasicLimitInformation.ActiveProcessLimit;
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
LocalJob.ActiveProcessLimit = 0;
}
// PRIORITY CLASS LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_PRIORITY_CLASS ) {
if ( ExtendedLimitInfo.BasicLimitInformation.PriorityClass > PROCESS_PRIORITY_CLASS_ABOVE_NORMAL ) {
st = STATUS_INVALID_PARAMETER;
}
else {
if ( ExtendedLimitInfo.BasicLimitInformation.PriorityClass == PROCESS_PRIORITY_CLASS_HIGH ||
ExtendedLimitInfo.BasicLimitInformation.PriorityClass == PROCESS_PRIORITY_CLASS_REALTIME ) {
// Increasing the base priority of a process is a
// privileged operation. Check for the privilege
// here.
HasPrivilege = SeCheckPrivilegedObject(
SeIncreaseBasePriorityPrivilege,
JobHandle,
JOB_OBJECT_SET_ATTRIBUTES,
PreviousMode
);
if (!HasPrivilege) {
st = STATUS_PRIVILEGE_NOT_HELD;
}
}
if ( NT_SUCCESS(st) ) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
LocalJob.PriorityClass = (UCHAR)ExtendedLimitInfo.BasicLimitInformation.PriorityClass;
}
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_PRIORITY_CLASS;
LocalJob.PriorityClass = 0;
}
// SCHEDULING CLASS LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_SCHEDULING_CLASS ) {
if ( ExtendedLimitInfo.BasicLimitInformation.SchedulingClass >= PSP_NUMBER_OF_SCHEDULING_CLASSES) {
st = STATUS_INVALID_PARAMETER;
}
else {
if ( ExtendedLimitInfo.BasicLimitInformation.SchedulingClass > PSP_DEFAULT_SCHEDULING_CLASSES ) {
// Increasing above the default scheduling class
// is a
// privileged operation. Check for the privilege
// here.
HasPrivilege = SeCheckPrivilegedObject(
SeIncreaseBasePriorityPrivilege,
JobHandle,
JOB_OBJECT_SET_ATTRIBUTES,
PreviousMode
);
if (!HasPrivilege) {
st = STATUS_PRIVILEGE_NOT_HELD;
}
}
if ( NT_SUCCESS(st) ) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_SCHEDULING_CLASS;
LocalJob.SchedulingClass = ExtendedLimitInfo.BasicLimitInformation.SchedulingClass;
}
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_SCHEDULING_CLASS;
LocalJob.SchedulingClass = PSP_DEFAULT_SCHEDULING_CLASSES ;
}
// AFFINITY LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_AFFINITY ) {
if ( !ExtendedLimitInfo.BasicLimitInformation.Affinity ||
(ExtendedLimitInfo.BasicLimitInformation.Affinity != (ExtendedLimitInfo.BasicLimitInformation.Affinity & KeActiveProcessors)) ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_AFFINITY;
LocalJob.Affinity = (KAFFINITY)ExtendedLimitInfo.BasicLimitInformation.Affinity;
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_AFFINITY;
LocalJob.Affinity = 0;
}
// PROCESS TIME LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_PROCESS_TIME ) {
if ( !ExtendedLimitInfo.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_TIME;
LocalJob.PerProcessUserTimeLimit.QuadPart = ExtendedLimitInfo.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart;
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_PROCESS_TIME;
LocalJob.PerProcessUserTimeLimit.QuadPart = 0;
}
// JOB TIME LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME ) {
if ( !ExtendedLimitInfo.BasicLimitInformation.PerJobUserTimeLimit.QuadPart ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_JOB_TIME;
LocalJob.PerJobUserTimeLimit.QuadPart = ExtendedLimitInfo.BasicLimitInformation.PerJobUserTimeLimit.QuadPart;
}
}
else {
if ( LimitFlags & JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME ) {
// If we are supposed to preserve existing job time limits, then
// preserve them !
LocalJob.LimitFlags |= (Job->LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME);
LocalJob.PerJobUserTimeLimit.QuadPart = Job->PerJobUserTimeLimit.QuadPart;
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_JOB_TIME;
LocalJob.PerJobUserTimeLimit.QuadPart = 0;
}
}
// WORKING SET LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET ) {
// the only issue with this check is that when we enforce through the
// processes, we may find a process that can not handle the new working set
// limit because it will make the process's working set not fluid
if ( (ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize == 0 &&
ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize == 0) ||
(ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize == (SIZE_T)-1 &&
ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize == (SIZE_T)-1) ||
(ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize >
ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize) ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_WORKINGSET;
LocalJob.MinimumWorkingSetSize = ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize;
LocalJob.MaximumWorkingSetSize = ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize;
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_WORKINGSET;
LocalJob.MinimumWorkingSetSize = 0;
LocalJob.MaximumWorkingSetSize = 0;
}
if ( JobObjectInformationClass == JobObjectExtendedLimitInformation) {
// PROCESS MEMORY LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY ) {
if ( ExtendedLimitInfo.ProcessMemoryLimit < PAGE_SIZE ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY;
LocalJob.ProcessMemoryLimit = ExtendedLimitInfo.ProcessMemoryLimit >> PAGE_SHIFT;
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_PROCESS_MEMORY;
LocalJob.ProcessMemoryLimit = 0;
}
// JOB WIDE MEMORY LIMIT
if ( LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY ) {
if ( ExtendedLimitInfo.JobMemoryLimit < PAGE_SIZE ) {
st = STATUS_INVALID_PARAMETER;
}
else {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
LocalJob.JobMemoryLimit = ExtendedLimitInfo.JobMemoryLimit >> PAGE_SHIFT;
}
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_JOB_MEMORY;
LocalJob.JobMemoryLimit = 0;
}
// JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
if ( LimitFlags & JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION ) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
}
// JOB_OBJECT_LIMIT_BREAKAWAY_OK
if ( LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK ) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_BREAKAWAY_OK;
}
// JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK
if ( LimitFlags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK ) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
}
else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
}
}
if ( NT_SUCCESS(st) ) {
// Copy LocalJob to Job
Job->LimitFlags = LocalJob.LimitFlags;
Job->MinimumWorkingSetSize = LocalJob.MinimumWorkingSetSize;
Job->MaximumWorkingSetSize = LocalJob.MaximumWorkingSetSize;
Job->ActiveProcessLimit = LocalJob.ActiveProcessLimit;
Job->Affinity = LocalJob.Affinity;
Job->PriorityClass = LocalJob.PriorityClass;
Job->SchedulingClass = LocalJob.SchedulingClass;
Job->PerProcessUserTimeLimit.QuadPart = LocalJob.PerProcessUserTimeLimit.QuadPart;
Job->PerJobUserTimeLimit.QuadPart = LocalJob.PerJobUserTimeLimit.QuadPart;
if ( JobObjectInformationClass == JobObjectExtendedLimitInformation) {
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
Job->ProcessMemoryLimit = LocalJob.ProcessMemoryLimit;
Job->JobMemoryLimit = LocalJob.JobMemoryLimit;
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
}
if ( LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME ) {
// Take any signalled processes and fold their accounting
// intothe job. This way a process that exited clean but still
// is open won't impact the next period
Next = Job->ProcessListHead.Flink;
while ( Next != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(Next,EPROCESS,JobLinks));
// see if process has been signalled.
// This indicates that the process has exited. We can't do
// this in the exit path becuase of the lock order problem
// between the process lock and the job lock since in exit
// we hold the process lock for a long time and can't drop
// it until thread termination
if ( KeReadStateProcess(&Process->Pcb) ) {
PspFoldProcessAccountingIntoJob(Job,Process);
}
else {
LARGE_INTEGER ProcessTime;
// running processes have their current runtime
// added to the programmed limit. This way, you
// can set a limit on a job with processes in the
// job and not have previous runtimes count against
// the limit
if ( !(Process->JobStatus & PS_JOB_STATUS_ACCOUNTING_FOLDED) ) {
ProcessTime.QuadPart = UInt32x32To64(Process->Pcb.UserTime,KeMaximumIncrement);
Job->PerJobUserTimeLimit.QuadPart += ProcessTime.QuadPart;
}
}
Next = Next->Flink;
}
// clear period times and reset the job
Job->ThisPeriodTotalUserTime.QuadPart = 0;
Job->ThisPeriodTotalKernelTime.QuadPart = 0;
KeClearEvent(&Job->Event);
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET ) {
ExAcquireFastMutexUnsafe(&PspWorkingSetChangeHead.Lock);
PspWorkingSetChangeHead.MinimumWorkingSetSize = Job->MinimumWorkingSetSize;
PspWorkingSetChangeHead.MaximumWorkingSetSize = Job->MaximumWorkingSetSize;
ProcessWorkingSetHead = TRUE;
}
PspApplyJobLimitsToProcessSet(Job);
}
}
}
break;
case JobObjectBasicUIRestrictions:
try {
RtlCopyMemory(&BasicUIRestrictions, JobObjectInformation, RequiredLength);
}
except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
// sanity check UIRestrictionsClass
if ( BasicUIRestrictions.UIRestrictionsClass & ~JOB_OBJECT_UI_VALID_FLAGS ) {
st = STATUS_INVALID_PARAMETER;
}
else {
// Check for switching between UI restrictions
if ( Job->UIRestrictionsClass ^ BasicUIRestrictions.UIRestrictionsClass ) {
// notify ntuser that the UI restrictions have changed
WIN32_JOBCALLOUT_PARAMETERS Parms;
Parms.Job = Job;
Parms.CalloutType = PsW32JobCalloutSetInformation;
Parms.Data = ULongToPtr(BasicUIRestrictions.UIRestrictionsClass);
MmDispatchWin32Callout( PspW32JobCallout,NULL, (PVOID)&Parms, &(Job->SessionId) );
}
// save the UI restrictions into the job object
Job->UIRestrictionsClass = BasicUIRestrictions.UIRestrictionsClass;
}
}
break;
// SECURITY LIMITS
case JobObjectSecurityLimitInformation:
try {
RtlCopyMemory( &SecurityLimitInfo,
JobObjectInformation,
RequiredLength );
}
except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
if ( SecurityLimitInfo.SecurityLimitFlags &
(~JOB_OBJECT_SECURITY_VALID_FLAGS))
{
st = STATUS_INVALID_PARAMETER ;
}
else
{
// Deal with specific options. Basic rules: Once a
// flag is on, it is always on (so even with a handle to
// the job, a process could not lift the security
// restrictions).
if ( SecurityLimitInfo.SecurityLimitFlags &
JOB_OBJECT_SECURITY_NO_ADMIN )
{
Job->SecurityLimitFlags |= JOB_OBJECT_SECURITY_NO_ADMIN ;
if ( Job->Token )
{
if ( SeTokenIsAdmin( Job->Token ) )
{
Job->SecurityLimitFlags &= (~JOB_OBJECT_SECURITY_NO_ADMIN);
st = STATUS_INVALID_PARAMETER ;
}
}
}
if ( SecurityLimitInfo.SecurityLimitFlags &
JOB_OBJECT_SECURITY_RESTRICTED_TOKEN )
{
if ( Job->SecurityLimitFlags &
( JOB_OBJECT_SECURITY_ONLY_TOKEN | JOB_OBJECT_SECURITY_FILTER_TOKENS ) )
{
st = STATUS_INVALID_PARAMETER ;
}
else
{
Job->SecurityLimitFlags |= JOB_OBJECT_SECURITY_RESTRICTED_TOKEN ;
}
}
// The forcible token is a little more interesting. It
// cannot be reset, so if there is a pointer there already,
// fail the call. If a filter is already in place, this is
// not allowed, either. If no-admin is set, it is checked
// at the end, once the token has been ref'd.
if ( SecurityLimitInfo.SecurityLimitFlags &
JOB_OBJECT_SECURITY_ONLY_TOKEN )
{
if ( Job->Token ||
(Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_FILTER_TOKENS) )
{
st = STATUS_INVALID_PARAMETER ;
}
else
{
st = ObReferenceObjectByHandle(
SecurityLimitInfo.JobToken,
TOKEN_ASSIGN_PRIMARY |
TOKEN_IMPERSONATE |
TOKEN_DUPLICATE ,
SeTokenObjectType(),
PreviousMode,
&LocalToken,
NULL );
if ( NT_SUCCESS( st ) )
{
st = SeIsChildTokenByPointer( LocalToken,
&IsChild );
if ( !NT_SUCCESS( st ) )
{
ObDereferenceObject( LocalToken );
}
}
if ( NT_SUCCESS( st ) )
{
// If the token supplied is not a restricted token
// based on the caller's ID, then they must have
// assign primary privilege in order to associate
// the token with the job.
if ( !IsChild )
{
HasPrivilege = SeCheckPrivilegedObject(
SeAssignPrimaryTokenPrivilege,
JobHandle,
JOB_OBJECT_SET_SECURITY_ATTRIBUTES,
PreviousMode
);
if ( !HasPrivilege )
{
st = STATUS_PRIVILEGE_NOT_HELD;
}
}
if ( NT_SUCCESS( st ) )
{
// Grab a reference to the token into the job
// object
Job->Token = LocalToken ;
// Not surprisingly, specifying no-admin and
// supplying an admin token is a no-no.
if ( (Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN) &&
SeTokenIsAdmin( Job->Token ) )
{
st = STATUS_INVALID_PARAMETER ;
ObDereferenceObject( Job->Token );
Job->Token = NULL ;
}
else
{
Job->SecurityLimitFlags |= JOB_OBJECT_SECURITY_ONLY_TOKEN ;
}
}
else
{
// This is the token was a child or otherwise ok,
// but assign primary was not held, so the
// request was rejected.
ObDereferenceObject( LocalToken );
}
}
}
}
if ( SecurityLimitInfo.SecurityLimitFlags &
JOB_OBJECT_SECURITY_FILTER_TOKENS )
{
if ( Job->SecurityLimitFlags &
( JOB_OBJECT_SECURITY_ONLY_TOKEN |
JOB_OBJECT_SECURITY_FILTER_TOKENS ) )
{
st = STATUS_INVALID_PARAMETER ;
}
else
{
// capture the token restrictions
st = PspCaptureTokenFilter(
PreviousMode,
&SecurityLimitInfo,
&Filter
);
if ( NT_SUCCESS( st ) )
{
Job->SecurityLimitFlags |= JOB_OBJECT_SECURITY_FILTER_TOKENS ;
Job->Filter = Filter ;
}
}
}
}
}
break;
case JobObjectEndOfJobTimeInformation:
try {
RtlCopyMemory(&EndOfJobInfo,JobObjectInformation,RequiredLength);
}
except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
// sanity check LimitFlags
if ( EndOfJobInfo.EndOfJobTimeAction > JOB_OBJECT_POST_AT_END_OF_JOB ) {
st = STATUS_INVALID_PARAMETER;
}
else {
Job->EndOfJobTimeAction = EndOfJobInfo.EndOfJobTimeAction;
}
}
break;
case JobObjectAssociateCompletionPortInformation:
try {
RtlCopyMemory(&AssociateInfo,JobObjectInformation,RequiredLength);
}
except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
if ( !Job->CompletionPort && AssociateInfo.CompletionPort ) {
st = ObReferenceObjectByHandle(
AssociateInfo.CompletionPort,
IO_COMPLETION_MODIFY_STATE,
IoCompletionObjectType,
PreviousMode,
&IoCompletion,
NULL
);
if (NT_SUCCESS(st)) {
Job->CompletionKey = AssociateInfo.CompletionKey;
Job->CompletionPort = IoCompletion;
// Now whip through ALL existing processes in the job
// and send notification messages
Next = Job->ProcessListHead.Flink;
while ( Next != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(Next,EPROCESS,JobLinks));
// If the process is really considered part of the job, has
// been assigned its id, and has not yet checked in, do it now
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE)
&& Process->UniqueProcessId
&& !(Process->JobStatus & PS_JOB_STATUS_NEW_PROCESS_REPORTED)) {
PS_SET_CLEAR_BITS (&Process->JobStatus,
PS_JOB_STATUS_NEW_PROCESS_REPORTED,
PS_JOB_STATUS_LAST_REPORT_MEMORY);
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
(PVOID)Process->UniqueProcessId,
STATUS_SUCCESS,
JOB_OBJECT_MSG_NEW_PROCESS,
FALSE
);
}
Next = Next->Flink;
}
}
}
else {
st = STATUS_INVALID_PARAMETER;
}
}
break;
default:
st = STATUS_INVALID_INFO_CLASS;
}
ExReleaseResource(&Job->JobLock);
// Working Set Changes are processed outside of the job lock.
// calling MmAdjust CAN NOT cause MM to call PsChangeJobMemoryUsage !
if ( ProcessWorkingSetHead ) {
if ( !IsListEmpty(&PspWorkingSetChangeHead.Links) ) {
while ( !IsListEmpty(&PspWorkingSetChangeHead.Links) ) {
Next = RemoveHeadList(&PspWorkingSetChangeHead.Links);
WsChangeRecord = CONTAINING_RECORD(Next,JOB_WORKING_SET_CHANGE_RECORD,Links);
KeAttachProcess(&WsChangeRecord->Process->Pcb);
MmAdjustWorkingSetSize(
PspWorkingSetChangeHead.MinimumWorkingSetSize,
PspWorkingSetChangeHead.MaximumWorkingSetSize,
FALSE
);
// call MM to Enable hard workingset
MmEnforceWorkingSetLimit(&WsChangeRecord->Process->Vm, TRUE);
KeDetachProcess();
ObDereferenceObject(WsChangeRecord->Process);
ExFreePool(WsChangeRecord);
}
}
ExReleaseFastMutexUnsafe(&PspWorkingSetChangeHead.Lock);
}
KeLeaveCriticalRegion();
// Finish Up
ObDereferenceObject(Job);
return st;
}
VOID
PspApplyJobLimitsToProcessSet(
PEJOB Job
)
{
PLIST_ENTRY Next;
PEPROCESS Process;
PJOB_WORKING_SET_CHANGE_RECORD WsChangeRecord;
PAGED_CODE();
// The job object is held exclusive by the caller
Next = Job->ProcessListHead.Flink;
while ( Next != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(Next,EPROCESS,JobLinks));
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET ) {
WsChangeRecord = ExAllocatePoolWithTag(PagedPool, sizeof( *WsChangeRecord ), 'rCsP');
if ( WsChangeRecord ) {
WsChangeRecord->Process = Process;
ObReferenceObject(Process);
// Avoid double delete since process could be in delete routine during the above ref
if ( ObGetObjectPointerCount(Process) > 1 ) {
InsertTailList(&PspWorkingSetChangeHead.Links,&WsChangeRecord->Links);
}
else {
// process is possibly in delete routine waiting to come
// out of job. DON'T Dereference !
ExFreePool(WsChangeRecord);
}
}
}
PspApplyJobLimitsToProcess(Job,Process);
}
Next = Next->Flink;
}
}
VOID PspApplyJobLimitsToProcess(PEJOB Job, PEPROCESS Process)
{
NTSTATUS Status;
PLIST_ENTRY Next;
PETHREAD Thread;
PAGED_CODE();
// The job object is held exclusive by the caller
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_PRIORITY_CLASS ) {
Process->PriorityClass = Job->PriorityClass;
PsSetProcessPriorityByClass(Process, Process->Vm.MemoryPriority == MEMORY_PRIORITY_FOREGROUND ? PsProcessPriorityForeground : PsProcessPriorityBackground);
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_AFFINITY ) {
// the following allows this api to properly if
// called while the exiting process is blocked holding the
// createdeletelock. This can happen during debugger/server
// lpc transactions that occur in pspexitthread
Status = PsLockProcess(Process,KeGetPreviousMode(),PsLockPollOnTimeout);
if ( Status == STATUS_SUCCESS ) {
Process->Pcb.Affinity = Job->Affinity;
Next = Process->ThreadListHead.Flink;
while ( Next != &Process->ThreadListHead) {
Thread = (PETHREAD)(CONTAINING_RECORD(Next,ETHREAD,ThreadListEntry));
KeSetAffinityThread(&Thread->Tcb,Job->Affinity);
Next = Next->Flink;
}
PsUnlockProcess(Process);
}
}
if ( !(Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) ) {
// call MM to disable hard workingset
MmEnforceWorkingSetLimit(&Process->Vm, FALSE);
}
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY ) {
Process->CommitChargeLimit = Job->ProcessMemoryLimit;
}
else {
Process->CommitChargeLimit = 0;
}
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
// If the process is NOT IDLE Priority Class, and long fixed quantums
// are in use, use the scheduling class stored in the job object for this process
if ( Process->PriorityClass != PROCESS_PRIORITY_CLASS_IDLE ) {
if ( PspUseJobSchedulingClasses ) {
Process->Pcb.ThreadQuantum = PspJobSchedulingClasses[Job->SchedulingClass];
}
// if the scheduling class is PSP_NUMBER_OF_SCHEDULING_CLASSES-1, then
// give this process non-preemptive scheduling
if ( Job->SchedulingClass == PSP_NUMBER_OF_SCHEDULING_CLASSES-1 ) {
KeSetDisableQuantumProcess(&Process->Pcb,TRUE);
}
else {
KeSetDisableQuantumProcess(&Process->Pcb,FALSE);
}
}
}
NTSTATUS NtTerminateJobObject(IN HANDLE JobHandle, IN NTSTATUS ExitStatus)
{
PEJOB Job;
NTSTATUS st;
KPROCESSOR_MODE PreviousMode;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
st = ObReferenceObjectByHandle(JobHandle, JOB_OBJECT_TERMINATE, PsJobType, PreviousMode, (PVOID *)&Job, NULL);
if ( !NT_SUCCESS(st) ) {
return st;
}
KeEnterCriticalRegion();
ExAcquireResourceExclusive(&Job->JobLock, TRUE);
PspTerminateAllProcessesInJob(Job,ExitStatus,PsLockPollOnTimeout);
ExReleaseResource(&Job->JobLock);
KeLeaveCriticalRegion();
ObDereferenceObject(Job);
return st;
}
VOID
PsEnforceExecutionTimeLimits(
VOID
)
{
PLIST_ENTRY NextJob;
PLIST_ENTRY NextProcess;
LARGE_INTEGER RunningJobTime;
LARGE_INTEGER ProcessTime;
PEJOB Job;
PEPROCESS Process;
NTSTATUS st;
ExAcquireFastMutex(&PspJobListLock);
// Look at each job. If time limits are set for the job, then enforce them
NextJob = PspJobList.Flink;
while ( NextJob != &PspJobList ) {
Job = (PEJOB)(CONTAINING_RECORD(NextJob,EJOB,JobLinks));
if ( Job->LimitFlags & (JOB_OBJECT_LIMIT_PROCESS_TIME | JOB_OBJECT_LIMIT_JOB_TIME) ) {
// Job looks like a candidate for time enforcing. Need to get the
// job lock to be sure, but we don't want to hang waiting for the
// job lock, so skip the job until next time around if we need to
if ( ExAcquireResourceExclusive(&Job->JobLock, FALSE) ) {
if ( Job->LimitFlags & (JOB_OBJECT_LIMIT_PROCESS_TIME | JOB_OBJECT_LIMIT_JOB_TIME) ) {
// Job is setup for time limits
RunningJobTime.QuadPart = Job->ThisPeriodTotalUserTime.QuadPart;
NextProcess = Job->ProcessListHead.Flink;
while ( NextProcess != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(NextProcess,EPROCESS,JobLinks));
ProcessTime.QuadPart = UInt32x32To64(Process->Pcb.UserTime,KeMaximumIncrement);
if ( !(Process->JobStatus & PS_JOB_STATUS_ACCOUNTING_FOLDED) ) {
RunningJobTime.QuadPart += ProcessTime.QuadPart;
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_PROCESS_TIME ) {
if ( ProcessTime.QuadPart > Job->PerProcessUserTimeLimit.QuadPart ) {
// Process Time Limit has been exceeded.
// Reference the process. Assert that it is not in its
// delete routine. If all is OK, then nuke and dereferece
// the process
ObReferenceObject(Process);
// Avoid double delete since process could be in delete routine during the above ref
if ( ObGetObjectPointerCount(Process) > 1 ) {
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
if ( PspTerminateProcess(Process,ERROR_NOT_ENOUGH_QUOTA,PsLockReturnTimeout) == STATUS_SUCCESS ) {
Job->TotalTerminatedProcesses++;
PS_SET_CLEAR_BITS (&Process->JobStatus,
PS_JOB_STATUS_NOT_REALLY_ACTIVE,
PS_JOB_STATUS_LAST_REPORT_MEMORY);
Job->ActiveProcesses--;
if ( Job->CompletionPort ) {
IoSetIoCompletion(Job->CompletionPort, Job->CompletionKey, (PVOID)Process->UniqueProcessId, STATUS_SUCCESS, JOB_OBJECT_MSG_END_OF_PROCESS_TIME, FALSE);
}
PspFoldProcessAccountingIntoJob(Job,Process);
}
}
ObDereferenceObject(Process);
}
}
}
NextProcess = NextProcess->Flink;
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME ) {
if ( RunningJobTime.QuadPart > Job->PerJobUserTimeLimit.QuadPart ) {
// Job Time Limit has been exceeded.
// Perform the appropriate action
switch ( Job->EndOfJobTimeAction ) {
case JOB_OBJECT_TERMINATE_AT_END_OF_JOB:
if ( PspTerminateAllProcessesInJob(Job,ERROR_NOT_ENOUGH_QUOTA,PsLockReturnTimeout) ) {
if ( Job->ActiveProcesses == 0 ) {
KeSetEvent(&Job->Event,0,FALSE);
if ( Job->CompletionPort ) {
PS_CLEAR_BITS (&Process->JobStatus, PS_JOB_STATUS_LAST_REPORT_MEMORY);
IoSetIoCompletion(Job->CompletionPort, Job->CompletionKey, NULL, STATUS_SUCCESS, JOB_OBJECT_MSG_END_OF_JOB_TIME, FALSE);
}
}
}
break;
case JOB_OBJECT_POST_AT_END_OF_JOB:
if ( Job->CompletionPort ) {
PS_CLEAR_BITS (&Process->JobStatus, PS_JOB_STATUS_LAST_REPORT_MEMORY);
st = IoSetIoCompletion(Job->CompletionPort, Job->CompletionKey, NULL, STATUS_SUCCESS, JOB_OBJECT_MSG_END_OF_JOB_TIME, FALSE);
if ( NT_SUCCESS(st) ) {
// Clear job level time limit
Job->LimitFlags &= ~JOB_OBJECT_LIMIT_JOB_TIME;
Job->PerJobUserTimeLimit.QuadPart = 0;
}
}
else {
if ( PspTerminateAllProcessesInJob(Job,ERROR_NOT_ENOUGH_QUOTA,PsLockReturnTimeout) ) {
if ( Job->ActiveProcesses == 0 ) {
KeSetEvent(&Job->Event,0,FALSE);
}
}
}
break;
}
}
}
}
ExReleaseResource(&Job->JobLock);
}
}
NextJob = NextJob->Flink;
}
ExReleaseFastMutex(&PspJobListLock);
}
BOOLEAN PspTerminateAllProcessesInJob(PEJOB Job, NTSTATUS Status, PSLOCKPROCESSMODE LockMode)
{
PLIST_ENTRY NextProcess;
PEPROCESS Process;
BOOLEAN TerminatedAProcess;
PAGED_CODE();
TerminatedAProcess = FALSE;
NextProcess = Job->ProcessListHead.Flink;
while ( NextProcess != &Job->ProcessListHead) {
Process = (PEPROCESS)(CONTAINING_RECORD(NextProcess,EPROCESS,JobLinks));
// Reference the process. Assert that it is not in its
// delete routine. If all is OK, then nuke and dereferece the process
ObReferenceObject(Process);
// Avoid double delete since process could be in delete routine during the above ref
if ( ObGetObjectPointerCount(Process) > 1 ) {
if ( !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) ) {
if ( PspTerminateProcess(Process,Status,LockMode) == STATUS_SUCCESS ) {
// If the lockmode isn't poll, it means we ran out of
// job time, so increment the terminated process count
// for each nuked process
if ( LockMode != PsLockPollOnTimeout ) {
Job->TotalTerminatedProcesses++;
}
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NOT_REALLY_ACTIVE);
Job->ActiveProcesses--;
PspFoldProcessAccountingIntoJob(Job,Process);
TerminatedAProcess = TRUE;
}
}
ObDereferenceObject(Process);
}
NextProcess = NextProcess->Flink;
}
return TerminatedAProcess;
}
VOID
PspFoldProcessAccountingIntoJob(
PEJOB Job,
PEPROCESS Process
)
{
LARGE_INTEGER UserTime, KernelTime;
if ( !(Process->JobStatus & PS_JOB_STATUS_ACCOUNTING_FOLDED) ) {
UserTime.QuadPart = UInt32x32To64(Process->Pcb.UserTime,KeMaximumIncrement);
KernelTime.QuadPart = UInt32x32To64(Process->Pcb.KernelTime,KeMaximumIncrement);
Job->TotalUserTime.QuadPart += UserTime.QuadPart;
Job->TotalKernelTime.QuadPart += KernelTime.QuadPart;
Job->ThisPeriodTotalUserTime.QuadPart += UserTime.QuadPart;
Job->ThisPeriodTotalKernelTime.QuadPart += KernelTime.QuadPart;
Job->ReadOperationCount += Process->ReadOperationCount.QuadPart;
Job->WriteOperationCount += Process->WriteOperationCount.QuadPart;
Job->OtherOperationCount += Process->OtherOperationCount.QuadPart;
Job->ReadTransferCount += Process->ReadTransferCount.QuadPart;
Job->WriteTransferCount += Process->WriteTransferCount.QuadPart;
Job->OtherTransferCount += Process->OtherTransferCount.QuadPart;
Job->TotalPageFaultCount += Process->Vm.PageFaultCount;
if ( Process->CommitChargePeak > Job->PeakProcessMemoryUsed ) {
Job->PeakProcessMemoryUsed = Process->CommitChargePeak;
}
PS_SET_CLEAR_BITS (&Process->JobStatus,
PS_JOB_STATUS_ACCOUNTING_FOLDED,
PS_JOB_STATUS_LAST_REPORT_MEMORY);
if ( Job->CompletionPort && Job->ActiveProcesses == 0) {
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
NULL,
STATUS_SUCCESS,
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO,
FALSE
);
}
}
}
NTSTATUS
PspCaptureTokenFilter(
KPROCESSOR_MODE PreviousMode,
PJOBOBJECT_SECURITY_LIMIT_INFORMATION SecurityLimitInfo,
PPS_JOB_TOKEN_FILTER * TokenFilter
)
{
NTSTATUS Status ;
PPS_JOB_TOKEN_FILTER Filter ;
Filter = ExAllocatePoolWithTag( NonPagedPool, sizeof( PS_JOB_TOKEN_FILTER ), 'fTsP' );
if ( !Filter )
{
*TokenFilter = NULL ;
return STATUS_INSUFFICIENT_RESOURCES ;
}
RtlZeroMemory( Filter, sizeof( PS_JOB_TOKEN_FILTER ) );
try {
Status = STATUS_SUCCESS ;
// Capture Sids to remove
if (ARGUMENT_PRESENT( SecurityLimitInfo->SidsToDisable)) {
ProbeForRead( SecurityLimitInfo->SidsToDisable, sizeof(TOKEN_GROUPS), sizeof(ULONG) );
Filter->CapturedGroupCount = SecurityLimitInfo->SidsToDisable->GroupCount;
Status = SeCaptureSidAndAttributesArray(
SecurityLimitInfo->SidsToDisable->Groups,
Filter->CapturedGroupCount,
PreviousMode,
NULL, 0,
NonPagedPool,
TRUE,
&Filter->CapturedGroups,
&Filter->CapturedGroupsLength
);
}
// Capture PrivilegesToDelete
if (NT_SUCCESS(Status) && ARGUMENT_PRESENT(SecurityLimitInfo->PrivilegesToDelete)) {
ProbeForRead( SecurityLimitInfo->PrivilegesToDelete, sizeof(TOKEN_PRIVILEGES), sizeof(ULONG) );
Filter->CapturedPrivilegeCount = SecurityLimitInfo->PrivilegesToDelete->PrivilegeCount;
Status = SeCaptureLuidAndAttributesArray(
SecurityLimitInfo->PrivilegesToDelete->Privileges,
Filter->CapturedPrivilegeCount,
PreviousMode,
NULL, 0,
NonPagedPool,
TRUE,
&Filter->CapturedPrivileges,
&Filter->CapturedPrivilegesLength
);
}
// Capture Restricted Sids
if (NT_SUCCESS(Status) && ARGUMENT_PRESENT(SecurityLimitInfo->RestrictedSids)) {
ProbeForRead( SecurityLimitInfo->RestrictedSids, sizeof(TOKEN_GROUPS), sizeof(ULONG) );
Filter->CapturedSidCount = SecurityLimitInfo->RestrictedSids->GroupCount;
Status = SeCaptureSidAndAttributesArray(
SecurityLimitInfo->RestrictedSids->Groups,
Filter->CapturedSidCount,
PreviousMode,
NULL, 0,
NonPagedPool,
TRUE,
&Filter->CapturedSids,
&Filter->CapturedSidsLength
);
}
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
} // end_try
if ( !NT_SUCCESS( Status ) )
{
if ( Filter->CapturedSids )
{
ExFreePool( Filter->CapturedSids );
}
if ( Filter->CapturedPrivileges )
{
ExFreePool( Filter->CapturedPrivileges );
}
if ( Filter->CapturedGroups )
{
ExFreePool( Filter->CapturedGroups );
}
ExFreePool( Filter );
Filter = NULL ;
}
*TokenFilter = Filter ;
return Status ;
}
BOOLEAN
PsChangeJobMemoryUsage(
SSIZE_T Amount
)
{
PEPROCESS Process;
PEJOB Job;
SIZE_T CurrentJobMemoryUsed;
BOOLEAN ReturnValue;
ReturnValue = TRUE;
Process = PsGetCurrentProcess();
Job = Process->Job;
if ( Job ) {
// This routine can be called while hoolding the process lock (during
// teb deletion... So instead of using the job lock, we must use the
// memory limits lock. The lock order is always (job lock followed by
// process lock. The memory limits lock never nests or calls other
// code while held. It can be grapped while holding the job lock, or the process
// lock.
KeEnterCriticalRegion();
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
CurrentJobMemoryUsed = Job->CurrentJobMemoryUsed + Amount;
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY &&
CurrentJobMemoryUsed > Job->JobMemoryLimit ) {
CurrentJobMemoryUsed = Job->CurrentJobMemoryUsed;
ReturnValue = FALSE;
// Tell the job port that commit has been exceeded, and process id x
// was the one that hit it.
if ( Job->CompletionPort
&& Process->UniqueProcessId
&& (Process->JobStatus & PS_JOB_STATUS_NEW_PROCESS_REPORTED)
&& (Process->JobStatus & PS_JOB_STATUS_LAST_REPORT_MEMORY) == 0) {
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_LAST_REPORT_MEMORY);
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
(PVOID)Process->UniqueProcessId,
STATUS_SUCCESS,
JOB_OBJECT_MSG_JOB_MEMORY_LIMIT,
TRUE
);
}
}
if ( ReturnValue ) {
// update current and peak counters
Job->CurrentJobMemoryUsed = CurrentJobMemoryUsed;
if ( CurrentJobMemoryUsed > Job->PeakJobMemoryUsed ) {
Job->PeakJobMemoryUsed = CurrentJobMemoryUsed;
}
if ( Process->CommitCharge + Amount > Job->PeakProcessMemoryUsed ) {
Job->PeakProcessMemoryUsed = Process->CommitCharge + Amount;
}
}
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
KeLeaveCriticalRegion();
}
return ReturnValue;
}
VOID
PsReportProcessMemoryLimitViolation(
VOID
)
{
PEPROCESS Process;
PEJOB Job;
Process = PsGetCurrentProcess();
Job = Process->Job;
if ( Job && (Job->LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) ) {
KeEnterCriticalRegion();
ExAcquireFastMutexUnsafe(&Job->MemoryLimitsLock);
// Tell the job port that commit has been exceeded, and process id x
// was the one that hit it.
if ( Job->CompletionPort
&& Process->UniqueProcessId
&& (Process->JobStatus & PS_JOB_STATUS_NEW_PROCESS_REPORTED)
&& (Process->JobStatus & PS_JOB_STATUS_LAST_REPORT_MEMORY) == 0) {
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_LAST_REPORT_MEMORY);
IoSetIoCompletion(
Job->CompletionPort,
Job->CompletionKey,
(PVOID)Process->UniqueProcessId,
STATUS_SUCCESS,
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT,
TRUE
);
}
ExReleaseFastMutexUnsafe(&Job->MemoryLimitsLock);
KeLeaveCriticalRegion();
}
}