windows-nt/Source/XPSP1/NT/base/ntos/ps/psjob.c
2020-09-26 16:20:57 +08:00

3440 lines
110 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(INIT, PspInitializeJobStructures)
#pragma alloc_text(INIT, PspInitializeJobStructuresPhase1)
#pragma alloc_text(PAGE, NtCreateJobObject)
#pragma alloc_text(PAGE, NtOpenJobObject)
#pragma alloc_text(PAGE, NtAssignProcessToJobObject)
#pragma alloc_text(PAGE, NtQueryInformationJobObject)
#pragma alloc_text(PAGE, NtSetInformationJobObject)
#pragma alloc_text(PAGE, NtTerminateJobObject)
#pragma alloc_text(PAGE, NtIsProcessInJob)
#pragma alloc_text(PAGE, NtCreateJobSet)
#pragma alloc_text(PAGE, PspJobDelete)
#pragma alloc_text(PAGE, PspJobClose)
#pragma alloc_text(PAGE, PspAddProcessToJob)
#pragma alloc_text(PAGE, PspRemoveProcessFromJob)
#pragma alloc_text(PAGE, PspExitProcessFromJob)
#pragma alloc_text(PAGE, PspApplyJobLimitsToProcessSet)
#pragma alloc_text(PAGE, PspApplyJobLimitsToProcess)
#pragma alloc_text(PAGE, PspTerminateAllProcessesInJob)
#pragma alloc_text(PAGE, PspFoldProcessAccountingIntoJob)
#pragma alloc_text(PAGE, PspCaptureTokenFilter)
#pragma alloc_text(PAGE, PsReportProcessMemoryLimitViolation)
#pragma alloc_text(PAGE, PspJobTimeLimitsWork)
#pragma alloc_text(PAGE, PsEnforceExecutionTimeLimits)
#pragma alloc_text(PAGE, PspShutdownJobLimits)
#pragma alloc_text(PAGE, PspGetJobFromSet)
#pragma alloc_text(PAGE, PsChangeJobMemoryUsage)
#pragma alloc_text(PAGE, PspWin32SessionCallout)
//
// move to io.h
extern POBJECT_TYPE IoCompletionObjectType;
KDPC PspJobTimeLimitsDpc;
KTIMER PspJobTimeLimitsTimer;
WORK_QUEUE_ITEM PspJobTimeLimitsWorkItem;
FAST_MUTEX PspJobTimeLimitsLock;
BOOLEAN PspJobTimeLimitsShuttingDown;
#define PSP_ONE_SECOND (10 * (1000*1000))
#define PSP_JOB_TIME_LIMITS_TIME -7
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif
LARGE_INTEGER PspJobTimeLimitsInterval = {0};
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif
NTSTATUS
NTAPI
NtCreateJobObject (
OUT PHANDLE JobHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
)
{
PEJOB Job;
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
PETHREAD CurrentThread;
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.
//
CurrentThread = PsGetCurrentThread ();
PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
try {
//
// Probe output handle address if
// necessary.
//
if (PreviousMode != KernelMode) {
ProbeForWriteHandle (JobHandle);
}
*JobHandle = NULL;
} except (ExSystemExceptionFilter ()) {
return GetExceptionCode();
}
//
// Allocate job object.
//
Status = ObCreateObject (PreviousMode,
PsJobType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof (EJOB),
0,
0,
&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);
InitializeListHead (&Job->JobSetLinks);
KeInitializeEvent (&Job->Event, NotificationEvent, FALSE);
ExInitializeFastMutex (&Job->MemoryLimitsLock);
//
// 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 = MmGetSessionId (PsGetCurrentProcessByThread (CurrentThread));
//
// Initialize the scheduling class for the Job
//
Job->SchedulingClass = PSP_DEFAULT_SCHEDULING_CLASSES;
ExInitializeResourceLite (&Job->JobLock);
ExAcquireFastMutex (&PspJobListLock);
InsertTailList (&PspJobList, &Job->JobLinks);
ExReleaseFastMutex (&PspJobListLock);
Status = ObInsertObject (Job,
NULL,
DesiredAccess,
0,
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 (NT_SUCCESS (Status)) {
try {
*JobHandle = Handle;
} except (ExSystemExceptionFilter ()) {
Status = 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.
//
PreviousMode = KeGetPreviousMode ();
if (PreviousMode != KernelMode) {
try {
//
// Probe output handle address
// if necessary.
//
ProbeForWriteHandle (JobHandle);
} except (EXCEPTION_EXECUTE_HANDLER) {
//
// 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.
//
return GetExceptionCode ();
}
}
//
// 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 just 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 ()) {
return GetExceptionCode ();
}
}
return Status;
}
NTSTATUS
NTAPI
NtAssignProcessToJobObject(
IN HANDLE JobHandle,
IN HANDLE ProcessHandle
)
{
PEJOB Job;
PEPROCESS Process;
PETHREAD CurrentThread;
NTSTATUS Status, Status1;
KPROCESSOR_MODE PreviousMode;
BOOLEAN IsAdmin;
PACCESS_TOKEN JobToken, NewToken = NULL;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
//
// Now reference the job object. Then we need to lock the process and check again
//
Status = ObReferenceObjectByHandle (JobHandle,
JOB_OBJECT_ASSIGN_PROCESS,
PsJobType,
PreviousMode,
&Job,
NULL);
if (!NT_SUCCESS (Status)) {
return Status;
}
JobToken = Job->Token;
//
// Reference the process object, lock the process, test for already been assigned
//
Status = ObReferenceObjectByHandle (ProcessHandle,
PROCESS_SET_QUOTA | PROCESS_TERMINATE |
((JobToken != NULL)?PROCESS_SET_INFORMATION:0),
PsProcessType,
PreviousMode,
&Process,
NULL);
if (!NT_SUCCESS (Status)) {
ObDereferenceObject (Job);
return Status;
}
//
// Quick Check for prior assignment
//
if (Process->Job) {
Status = STATUS_ACCESS_DENIED;
goto deref_and_return_status;
}
//
// We only allow a process that is running in the Job creator's hydra session
// to be assigned to the job.
//
if (MmGetSessionId (Process) != Job->SessionId) {
Status = STATUS_ACCESS_DENIED;
goto deref_and_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) {
PACCESS_TOKEN Token;
Token = PsReferencePrimaryToken (Process);
IsAdmin = SeTokenIsAdmin (Token);
PsDereferencePrimaryTokenEx (Process, Token);
if (IsAdmin) {
Status = STATUS_ACCESS_DENIED;
goto deref_and_return_status;
}
}
//
// Duplicate the primary token so we can assign it to the process.
//
if (JobToken != NULL) {
Status = SeSubProcessToken (JobToken,
&NewToken,
FALSE);
if (!NT_SUCCESS (Status)) {
goto deref_and_return_status;
}
}
if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
Status = STATUS_PROCESS_IS_TERMINATING;
if (JobToken != NULL) {
ObDereferenceObject (NewToken);
}
goto deref_and_return_status;
}
//
// ref the job for the process
//
ObReferenceObject (Job);
if (InterlockedCompareExchangePointer (&Process->Job, Job, NULL) != NULL) {
ExReleaseRundownProtection (&Process->RundownProtect);
ObDereferenceObject (Process);
ObDereferenceObjectEx (Job, 2);
if (JobToken != NULL) {
ObDereferenceObject (NewToken);
}
return STATUS_ACCESS_DENIED;
}
//
// If the job has a token filter established,
// use it to filter the
//
ExReleaseRundownProtection (&Process->RundownProtect);
Status = PspAddProcessToJob (Job, Process);
if (!NT_SUCCESS (Status)) {
Status1 = PspTerminateProcess (Process, ERROR_NOT_ENOUGH_QUOTA);
if (NT_SUCCESS (Status1)) {
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite (&Job->JobLock, TRUE);
Job->TotalTerminatedProcesses++;
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
}
}
//
// 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;
Parms.Job = Job;
Parms.CalloutType = PsW32JobCalloutAddProcess;
Parms.Data = Process->Win32Process;
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
PspWin32SessionCallout(PspW32JobCallout, &Parms, Job->SessionId);
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
}
if (JobToken != NULL) {
Status1 = PspSetPrimaryToken (NULL, Process, NULL, NewToken, TRUE);
ObDereferenceObject (NewToken);
//
// Only bad callers should fail here.
//
ASSERT (NT_SUCCESS (Status1));
}
deref_and_return_status:
ObDereferenceObject (Process);
ObDereferenceObject (Job);
return Status;
}
NTSTATUS
PspAddProcessToJob(
PEJOB Job,
PEPROCESS Process
)
{
NTSTATUS Status;
PETHREAD CurrentThread;
SIZE_T MinWs,MaxWs;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
Status = STATUS_SUCCESS;
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite (&Job->JobLock, TRUE);
InsertTailList (&Job->ProcessListHead, &Process->JobLinks);
//
// Update relevant ADD accounting info.
//
Job->TotalProcesses++;
Job->ActiveProcesses++;
//
// Test for active process count exceeding limit
//
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 != NULL) {
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 the last handle to the job has been closed and the kill on close option is set
// we don't let new processes enter the job. This is to make cleanup solid.
//
if (PS_TEST_ALL_BITS_SET (Job->JobFlags, PS_JOB_FLAGS_CLOSE_DONE|JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)) {
Job->ActiveProcesses--;
Status = STATUS_INVALID_PARAMETER;
}
if (Status == STATUS_SUCCESS) {
PspApplyJobLimitsToProcess (Job, Process);
if (Job->CompletionPort != NULL &&
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;
}
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
if (Status == STATUS_SUCCESS) {
if (MinWs != 0 && MaxWs != 0) {
KAPC_STATE ApcState;
KeStackAttachProcess (&Process->Pcb, &ApcState);
ExAcquireFastMutex (&PspWorkingSetChangeHead.Lock);
MmAdjustWorkingSetSize (MinWs,MaxWs,FALSE,TRUE);
//
// call MM to Enable hard workingset
//
MmEnforceWorkingSetLimit (&Process->Vm, TRUE);
ExReleaseFastMutex (&PspWorkingSetChangeHead.Lock);
KeUnstackDetachProcess (&ApcState);
} 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
)
{
PETHREAD CurrentThread;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite (&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);
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
}
VOID
PspExitProcessFromJob(
PEJOB Job,
PEPROCESS Process
)
{
PETHREAD CurrentThread;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite(&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);
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
}
VOID
PspJobDelete(
IN PVOID Object
)
{
PEJOB Job, tJob;
WIN32_JOBCALLOUT_PARAMETERS Parms;
PPS_JOB_TOKEN_FILTER Filter;
PETHREAD CurrentThread;
PAGED_CODE();
Job = (PEJOB) Object;
//
// call ntuser to delete its job structure
//
Parms.Job = Job;
Parms.CalloutType = PsW32JobCalloutTerminate;
Parms.Data = NULL;
CurrentThread = PsGetCurrentThread ();
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
PspWin32SessionCallout(PspW32JobCallout, &Parms, Job->SessionId);
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
Job->LimitFlags = 0;
if (Job->CompletionPort != NULL) {
ObDereferenceObject(Job->CompletionPort);
Job->CompletionPort = NULL;
}
//
// Remove Job on Job List and job set
//
tJob = NULL;
ExAcquireFastMutex (&PspJobListLock);
RemoveEntryList (&Job->JobLinks);
//
// If we are part of a jobset then we must be the pinning job. We must pass on the pin to the next
// job in the chain.
//
if (!IsListEmpty (&Job->JobSetLinks)) {
tJob = CONTAINING_RECORD (Job->JobSetLinks.Flink, EJOB, JobSetLinks);
RemoveEntryList (&Job->JobSetLinks);
}
ExReleaseFastMutex (&PspJobListLock);
//
// Removing the pin from the job set can cause a cascade of deletes that would cause a stack overflow
// as we recursed at this point. We break recursion by forcing the defered delete path here.
//
if (tJob != NULL) {
ObDereferenceObjectDeferDelete (tJob);
}
//
// Free Security clutter:
//
if (Job->Token != NULL) {
ObDereferenceObject (Job->Token);
Job->Token = NULL;
}
Filter = Job->Filter;
if (Filter != NULL) {
if (Filter->CapturedSids != NULL) {
ExFreePool (Filter->CapturedSids);
}
if (Filter->CapturedPrivileges != NULL) {
ExFreePool (Filter->CapturedPrivileges);
}
if (Filter->CapturedGroups != NULL) {
ExFreePool (Filter->CapturedGroups);
}
ExFreePool (Filter);
}
ExDeleteResourceLite (&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;
PETHREAD CurrentThread;
PAGED_CODE ();
UNREFERENCED_PARAMETER (Process);
UNREFERENCED_PARAMETER (GrantedAccess);
UNREFERENCED_PARAMETER (ProcessHandleCount);
//
// If this isn't the last handle then do nothing.
//
if (SystemHandleCount > 1) {
return;
}
CurrentThread = PsGetCurrentThread ();
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite (&Job->JobLock, TRUE);
//
// Mark the job has having its last handle closed.
// This is used to prevent new processes entering a job
// marked as terminate on close and also prevents a completion
// port being set on a torn down job. Completion ports
// are removed on last handle close.
//
PS_SET_BITS (&Job->JobFlags, PS_JOB_FLAGS_CLOSE_DONE);
if (Job->LimitFlags&JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) {
PspTerminateAllProcessesInJob (Job, STATUS_SUCCESS, FALSE);
}
ExAcquireFastMutex (&Job->MemoryLimitsLock);
//
// Release the completion port
//
Port = Job->CompletionPort;
Job->CompletionPort = NULL;
ExReleaseFastMutex (&Job->MemoryLimitsLock);
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
if (Port != NULL) {
ObDereferenceObject (Port);
}
}
#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("PAGECONST")
#endif
const 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
sizeof(JOBOBJECT_JOBSET_INFORMATION), // JobObjectJobSetInformation
0
};
const 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
TYPE_ALIGNMENT (JOBOBJECT_JOBSET_INFORMATION), // JobObjectJobSetInformation
0
};
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_EXTENDED_LIMIT_INFORMATION ExtendedLimitInfo;
JOBOBJECT_BASIC_UI_RESTRICTIONS BasicUIRestrictions;
JOBOBJECT_SECURITY_LIMIT_INFORMATION SecurityLimitInfo;
JOBOBJECT_JOBSET_INFORMATION JobSetInformation;
JOBOBJECT_END_OF_JOB_TIME_INFORMATION EndOfJobInfo;
NTSTATUS st=STATUS_SUCCESS;
ULONG RequiredLength, RequiredAlign, ActualReturnLength;
PVOID ReturnData=NULL;
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;
PPS_JOB_TOKEN_FILTER Filter;
PETHREAD CurrentThread;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
//
// 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 = KeGetPreviousModeByThread (&CurrentThread->Tcb);
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 = PsGetCurrentProcessByThread(CurrentThread);
if (Process->Job != NULL) {
Job = Process->Job;
ObReferenceObject(Job);
} else {
return STATUS_ACCESS_DENIED;
}
}
AlreadyCopied = FALSE ;
//
// 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));
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceSharedLite (&Job->JobLock, TRUE);
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;
}
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
ReturnData = &AccountingInfo;
st = STATUS_SUCCESS;
break;
case JobObjectExtendedLimitInformation:
case JobObjectBasicLimitInformation:
//
// Get the Basic Information
//
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceSharedLite(&Job->JobLock, TRUE);
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;
if ( JobObjectInformationClass == JobObjectExtendedLimitInformation ) {
//
// Get Extended Information
//
ExAcquireFastMutex (&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;
ExReleaseFastMutex (&Job->MemoryLimitsLock);
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
//
// Zero un-used I/O counters
//
RtlZeroMemory(&ExtendedLimitInfo.IoInfo,sizeof(ExtendedLimitInfo.IoInfo));
ReturnData = &ExtendedLimitInfo;
} else {
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
ReturnData = &ExtendedLimitInfo.BasicLimitInformation;
}
st = STATUS_SUCCESS;
break;
case JobObjectBasicUIRestrictions:
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceSharedLite(&Job->JobLock, TRUE);
BasicUIRestrictions.UIRestrictionsClass = Job->UIRestrictionsClass;
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
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;
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceSharedLite(&Job->JobLock, 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;
}
ExReleaseResourceLite(&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
break;
case JobObjectSecurityLimitInformation:
RtlZeroMemory (&SecurityLimitInfo, sizeof (SecurityLimitInfo));
ReturnData = &SecurityLimitInfo;
st = STATUS_SUCCESS;
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceSharedLite(&Job->JobLock, TRUE);
SecurityLimitInfo.SecurityLimitFlags = Job->SecurityLimitFlags;
//
// If a filter is present, then we have an ugly marshalling to do.
//
Filter = Job->Filter;
if (Filter != NULL) {
WorkingLength = 0;
//
// For each field, if it is present, include the extra stuff
//
if (Filter->CapturedSidsLength > 0) {
WorkingLength += Filter->CapturedSidsLength + sizeof (ULONG);
}
if (Filter->CapturedGroupsLength > 0) {
WorkingLength += Filter->CapturedGroupsLength + sizeof (ULONG);
}
if (Filter->CapturedPrivilegesLength > 0) {
WorkingLength += Filter->CapturedPrivilegesLength + sizeof (ULONG);
}
RequiredLength -= sizeof (SecurityLimitInfo);
if (WorkingLength > RequiredLength) {
st = STATUS_BUFFER_OVERFLOW ;
ActualReturnLength = WorkingLength + sizeof (SecurityLimitInfo);
goto unlock;
}
CurrentOffset = (PUCHAR) (JobObjectInformation) + sizeof (SecurityLimitInfo);
try {
if (Filter->CapturedSidsLength > 0) {
WorkingGroup = (PTOKEN_GROUPS) CurrentOffset;
CurrentOffset += sizeof (ULONG);
SecurityLimitInfo.RestrictedSids = WorkingGroup;
WorkingGroup->GroupCount = Filter->CapturedSidCount;
TargetSidBuffer = (PSID) (CurrentOffset +
sizeof (SID_AND_ATTRIBUTES) *
Filter->CapturedSidCount);
st = RtlCopySidAndAttributesArray (Filter->CapturedSidCount,
Filter->CapturedSids,
WorkingLength,
WorkingGroup->Groups,
TargetSidBuffer,
&RemainingSid,
&RemainingSidBuffer);
CurrentOffset += Filter->CapturedSidsLength;
}
if (!NT_SUCCESS (st)) {
leave;
}
if (Filter->CapturedGroupsLength > 0) {
WorkingGroup = (PTOKEN_GROUPS) CurrentOffset;
CurrentOffset += sizeof (ULONG);
SecurityLimitInfo.SidsToDisable = WorkingGroup;
WorkingGroup->GroupCount = Filter->CapturedGroupCount;
TargetSidBuffer = (PSID) (CurrentOffset +
sizeof (SID_AND_ATTRIBUTES) *
Filter->CapturedGroupCount);
st = RtlCopySidAndAttributesArray (Filter->CapturedGroupCount,
Filter->CapturedGroups,
WorkingLength,
WorkingGroup->Groups,
TargetSidBuffer,
&RemainingSid,
&RemainingSidBuffer);
CurrentOffset += Filter->CapturedGroupsLength;
}
if (!NT_SUCCESS (st)) {
leave;
}
if (Filter->CapturedPrivilegesLength > 0) {
WorkingPrivs = (PTOKEN_PRIVILEGES) CurrentOffset;
CurrentOffset += sizeof (ULONG);
SecurityLimitInfo.PrivilegesToDelete = WorkingPrivs;
WorkingPrivs->PrivilegeCount = Filter->CapturedPrivilegeCount;
RtlCopyMemory (WorkingPrivs->Privileges,
Filter->CapturedPrivileges,
Filter->CapturedPrivilegesLength);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode ();
ActualReturnLength = 0 ;
}
}
unlock:
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
AlreadyCopied = TRUE ;
if (NT_SUCCESS (st)) {
try {
RtlCopyMemory (JobObjectInformation,
&SecurityLimitInfo,
sizeof (SecurityLimitInfo));
} except (EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode ();
ActualReturnLength = 0 ;
break;
}
}
break;
case JobObjectJobSetInformation:
ExAcquireFastMutex (&PspJobListLock);
JobSetInformation.MemberLevel = Job->MemberLevel;
ExReleaseFastMutex (&PspJobListLock);
ReturnData = &JobSetInformation;
st = STATUS_SUCCESS;
break;
case JobObjectEndOfJobTimeInformation:
EndOfJobInfo.EndOfJobTimeAction = Job->EndOfJobTimeAction;
ReturnData = &EndOfJobInfo;
st = STATUS_SUCCESS;
break;
default:
st = STATUS_INVALID_INFO_CLASS;
}
//
// 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 GetExceptionCode ();
}
}
return st;
}
NTSTATUS
NtSetInformationJobObject(
IN HANDLE JobHandle,
IN JOBOBJECTINFOCLASS JobObjectInformationClass,
IN PVOID JobObjectInformation,
IN ULONG JobObjectInformationLength
)
{
PEJOB Job;
EJOB LocalJob={0};
KPROCESSOR_MODE PreviousMode;
NTSTATUS st;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimitInfo={0};
JOBOBJECT_BASIC_UI_RESTRICTIONS BasicUIRestrictions={0};
JOBOBJECT_SECURITY_LIMIT_INFORMATION SecurityLimitInfo={0};
JOBOBJECT_END_OF_JOB_TIME_INFORMATION EndOfJobInfo={0};
JOBOBJECT_ASSOCIATE_COMPLETION_PORT AssociateInfo={0};
ULONG RequiredAccess;
ULONG RequiredLength, RequiredAlign;
PEPROCESS Process;
PETHREAD CurrentThread;
BOOLEAN HasPrivilege;
BOOLEAN IsChild=FALSE;
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];
CurrentThread = PsGetCurrentThread ();
PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
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;
}
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
//
// 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;
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
//
// 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 {
if (ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize <= PsMinimumWorkingSet ||
SeSinglePrivilegeCheck (SeIncreaseBasePriorityPrivilege,
PreviousMode)) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_WORKINGSET;
LocalJob.MinimumWorkingSetSize = ExtendedLimitInfo.BasicLimitInformation.MinimumWorkingSetSize;
LocalJob.MaximumWorkingSetSize = ExtendedLimitInfo.BasicLimitInformation.MaximumWorkingSetSize;
} else {
st = STATUS_PRIVILEGE_NOT_HELD;
}
}
} 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;
}
//
// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
//
if (LimitFlags & JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) {
LocalJob.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
} else {
LocalJob.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
}
}
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) {
ExAcquireFastMutex (&Job->MemoryLimitsLock);
Job->ProcessMemoryLimit = LocalJob.ProcessMemoryLimit;
Job->JobMemoryLimit = LocalJob.JobMemoryLimit;
ExReleaseFastMutex (&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 ) {
ExAcquireFastMutex (&PspWorkingSetChangeHead.Lock);
PspWorkingSetChangeHead.MinimumWorkingSetSize = Job->MinimumWorkingSetSize;
PspWorkingSetChangeHead.MaximumWorkingSetSize = Job->MaximumWorkingSetSize;
ProcessWorkingSetHead = TRUE;
}
PspApplyJobLimitsToProcessSet(Job);
}
ExReleaseResourceLite(&Job->JobLock);
}
}
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 {
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
//
// 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);
PspWin32SessionCallout(PspW32JobCallout, &Parms, Job->SessionId);
}
//
// save the UI restrictions into the job object
//
Job->UIRestrictionsClass = BasicUIRestrictions.UIRestrictionsClass;
ExReleaseResourceLite(&Job->JobLock);
}
}
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 {
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
//
// 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 ) ) {
if (SeTokenType (LocalToken) != TokenPrimary) {
st = STATUS_BAD_TOKEN_TYPE;
} else {
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)) {
//
// Not surprisingly, specifying no-admin and
// supplying an admin token is a no-no.
//
if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN) &&
SeTokenIsAdmin (LocalToken)) {
st = STATUS_INVALID_PARAMETER;
ObDereferenceObject (LocalToken);
} else {
//
// Grab a reference to the token into the job
// object
//
KeMemoryBarrier ();
Job->Token = LocalToken;
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)) {
KeMemoryBarrier ();
Job->SecurityLimitFlags |= JOB_OBJECT_SECURITY_FILTER_TOKENS;
Job->Filter = Filter;
}
}
}
ExReleaseResourceLite(&Job->JobLock);
}
}
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 {
ExAcquireResourceExclusiveLite (&Job->JobLock, TRUE);
Job->EndOfJobTimeAction = EndOfJobInfo.EndOfJobTimeAction;
ExReleaseResourceLite (&Job->JobLock);
}
}
break;
case JobObjectAssociateCompletionPortInformation:
try {
RtlCopyMemory(&AssociateInfo,JobObjectInformation,RequiredLength);
} except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
if ( NT_SUCCESS(st) ) {
if (Job->CompletionPort || AssociateInfo.CompletionPort == NULL) {
st = STATUS_INVALID_PARAMETER;
} else {
st = ObReferenceObjectByHandle (AssociateInfo.CompletionPort,
IO_COMPLETION_MODIFY_STATE,
IoCompletionObjectType,
PreviousMode,
&IoCompletion,
NULL);
if (NT_SUCCESS(st)) {
ExAcquireResourceExclusiveLite(&Job->JobLock, TRUE);
//
// If the job already has a completion port or if the job has been rundown
// then reject the request.
//
if (Job->CompletionPort != NULL || (Job->JobFlags&PS_JOB_FLAGS_CLOSE_DONE) != 0) {
ExReleaseResourceLite(&Job->JobLock);
ObDereferenceObject (IoCompletion);
st = STATUS_INVALID_PARAMETER;
} else {
Job->CompletionKey = AssociateInfo.CompletionKey;
KeMemoryBarrier ();
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;
}
ExReleaseResourceLite(&Job->JobLock);
}
}
}
}
break;
default:
st = STATUS_INVALID_INFO_CLASS;
}
//
// Working Set Changes are processed outside of the job lock.
//
// calling MmAdjust CAN NOT cause MM to call PsChangeJobMemoryUsage !
//
if (ProcessWorkingSetHead) {
LIST_ENTRY FreeList;
KAPC_STATE ApcState;
InitializeListHead (&FreeList);
while (!IsListEmpty (&PspWorkingSetChangeHead.Links)) {
Next = RemoveHeadList(&PspWorkingSetChangeHead.Links);
InsertTailList (&FreeList, Next);
WsChangeRecord = CONTAINING_RECORD(Next,JOB_WORKING_SET_CHANGE_RECORD,Links);
KeStackAttachProcess(&WsChangeRecord->Process->Pcb, &ApcState);
MmAdjustWorkingSetSize (PspWorkingSetChangeHead.MinimumWorkingSetSize,
PspWorkingSetChangeHead.MaximumWorkingSetSize,
FALSE,
TRUE);
//
// call MM to Enable hard workingset
//
MmEnforceWorkingSetLimit(&WsChangeRecord->Process->Vm, TRUE);
KeUnstackDetachProcess(&ApcState);
}
ExReleaseFastMutex (&PspWorkingSetChangeHead.Lock);
while (!IsListEmpty (&FreeList)) {
Next = RemoveHeadList(&FreeList);
WsChangeRecord = CONTAINING_RECORD(Next,JOB_WORKING_SET_CHANGE_RECORD,Links);
ObDereferenceObject (WsChangeRecord->Process);
ExFreePool (WsChangeRecord);
}
}
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
//
// Finish Up
//
ObDereferenceObject(Job);
return st;
}
VOID
PspApplyJobLimitsToProcessSet(
PEJOB Job
)
{
PEPROCESS Process;
PJOB_WORKING_SET_CHANGE_RECORD WsChangeRecord;
PAGED_CODE();
//
// The job object is held exclusive by the caller
//
for (Process = PspGetNextJobProcess (Job, NULL);
Process != NULL;
Process = PspGetNextJobProcess (Job, Process)) {
if (!(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE)) {
if (Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) {
WsChangeRecord = ExAllocatePoolWithTag (PagedPool,
sizeof(*WsChangeRecord),
'rCsP');
if (WsChangeRecord != NULL) {
WsChangeRecord->Process = Process;
ObReferenceObject (Process);
InsertTailList(&PspWorkingSetChangeHead.Links,&WsChangeRecord->Links);
}
}
PspApplyJobLimitsToProcess(Job,Process);
}
}
}
VOID
PspApplyJobLimitsToProcess(
PEJOB Job,
PEPROCESS Process
)
{
PETHREAD CurrentThread;
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.Flags.MemoryPriority == MEMORY_PRIORITY_FOREGROUND ?
PsProcessPriorityForeground : PsProcessPriorityBackground);
}
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_AFFINITY ) {
CurrentThread = PsGetCurrentThread ();
PspLockProcessExclusive (Process, CurrentThread);
KeSetAffinityProcess (&Process->Pcb, Job->Affinity);
PspUnlockProcessExclusive (Process, CurrentThread);
}
if ( !(Job->LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) ) {
//
// call MM to disable hard workingset
//
MmEnforceWorkingSetLimit(&Process->Vm, FALSE);
}
ExAcquireFastMutex (&Job->MemoryLimitsLock);
if ( Job->LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY ) {
Process->CommitChargeLimit = Job->ProcessMemoryLimit;
} else {
Process->CommitChargeLimit = 0;
}
ExReleaseFastMutex (&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;
PETHREAD CurrentThread;
PAGED_CODE();
CurrentThread = PsGetCurrentThread ();
PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
st = ObReferenceObjectByHandle (JobHandle,
JOB_OBJECT_TERMINATE,
PsJobType,
PreviousMode,
&Job,
NULL);
if (!NT_SUCCESS(st)) {
return st;
}
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
ExAcquireResourceExclusiveLite (&Job->JobLock, TRUE);
PspTerminateAllProcessesInJob (Job,ExitStatus,FALSE);
ExReleaseResourceLite (&Job->JobLock);
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
ObDereferenceObject(Job);
return st;
}
VOID
PsEnforceExecutionTimeLimits(
VOID
)
{
PLIST_ENTRY NextJob;
LARGE_INTEGER RunningJobTime;
LARGE_INTEGER ProcessTime;
PEJOB Job;
PEPROCESS Process;
NTSTATUS st;
PAGED_CODE();
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 (ExAcquireResourceExclusiveLite (&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;
for (Process = PspGetNextJobProcess (Job, NULL);
Process != NULL;
Process = PspGetNextJobProcess (Job, Process)) {
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
//
if (!(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE)) {
if (NT_SUCCESS (PspTerminateProcess (Process,ERROR_NOT_ENOUGH_QUOTA))) {
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 != NULL) {
IoSetIoCompletion (Job->CompletionPort,
Job->CompletionKey,
(PVOID)Process->UniqueProcessId,
STATUS_SUCCESS,
JOB_OBJECT_MSG_END_OF_PROCESS_TIME,
FALSE);
}
PspFoldProcessAccountingIntoJob(Job,Process);
}
}
}
}
}
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, TRUE) ) {
if (Job->ActiveProcesses == 0) {
KeSetEvent (&Job->Event,0,FALSE);
if (Job->CompletionPort) {
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) {
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,TRUE) ) {
if (Job->ActiveProcesses == 0) {
KeSetEvent(&Job->Event,0,FALSE);
}
}
}
break;
}
}
}
}
ExReleaseResourceLite(&Job->JobLock);
}
}
NextJob = NextJob->Flink;
}
ExReleaseFastMutex (&PspJobListLock);
}
BOOLEAN
PspTerminateAllProcessesInJob(
PEJOB Job,
NTSTATUS Status,
BOOLEAN IncCounter
)
{
PEPROCESS Process;
BOOLEAN TerminatedAProcess;
PAGED_CODE();
TerminatedAProcess = FALSE;
for (Process = PspGetNextJobProcess (Job, NULL);
Process != NULL;
Process = PspGetNextJobProcess (Job, Process)) {
if (!(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE)) {
if (NT_SUCCESS (PspTerminateProcess(Process,Status))) {
if (IncCounter) {
Job->TotalTerminatedProcesses++;
}
PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NOT_REALLY_ACTIVE);
Job->ActiveProcesses--;
PspFoldProcessAccountingIntoJob(Job,Process);
TerminatedAProcess = TRUE;
}
}
}
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)) {
ProbeForReadSmallStructure (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)) {
ProbeForReadSmallStructure (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)) {
ProbeForReadSmallStructure (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 holding 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.
//
ExAcquireFastMutex (&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) {
Job->CurrentJobMemoryUsed = CurrentJobMemoryUsed;
//
// Update current and peak counters if this is an addition.
//
if (Amount > 0) {
if (CurrentJobMemoryUsed > Job->PeakJobMemoryUsed) {
Job->PeakJobMemoryUsed = CurrentJobMemoryUsed;
}
if (Process->CommitCharge + Amount > Job->PeakProcessMemoryUsed) {
Job->PeakProcessMemoryUsed = Process->CommitCharge + Amount;
}
}
}
ExReleaseFastMutex (&Job->MemoryLimitsLock);
}
return ReturnValue;
}
VOID
PsReportProcessMemoryLimitViolation(
VOID
)
{
PEPROCESS Process;
PEJOB Job;
PAGED_CODE();
Process = PsGetCurrentProcess();
Job = Process->Job;
if ( Job && (Job->LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) ) {
ExAcquireFastMutex (&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
);
}
ExReleaseFastMutex (&Job->MemoryLimitsLock);
}
}
VOID
PspJobTimeLimitsWork(
IN PVOID Context
)
{
PAGED_CODE();
UNREFERENCED_PARAMETER (Context);
PsEnforceExecutionTimeLimits();
//
// Reset timer
//
ExAcquireFastMutex (&PspJobTimeLimitsLock);
if (!PspJobTimeLimitsShuttingDown) {
KeSetTimer (&PspJobTimeLimitsTimer,
PspJobTimeLimitsInterval,
&PspJobTimeLimitsDpc);
}
ExReleaseFastMutex (&PspJobTimeLimitsLock);
}
VOID
PspJobTimeLimitsDpcRoutine(
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
UNREFERENCED_PARAMETER (Dpc);
UNREFERENCED_PARAMETER (DeferredContext);
UNREFERENCED_PARAMETER (SystemArgument1);
UNREFERENCED_PARAMETER (SystemArgument2);
ExQueueWorkItem(&PspJobTimeLimitsWorkItem, DelayedWorkQueue);
}
VOID
PspInitializeJobStructures(
)
{
//
// Initialize job list head and mutex
//
InitializeListHead (&PspJobList);
ExInitializeFastMutex (&PspJobListLock);
//
// Initialize job time limits timer, etc
//
ExInitializeFastMutex (&PspJobTimeLimitsLock);
PspJobTimeLimitsShuttingDown = FALSE;
KeInitializeDpc (&PspJobTimeLimitsDpc,
PspJobTimeLimitsDpcRoutine,
NULL);
ExInitializeWorkItem (&PspJobTimeLimitsWorkItem, PspJobTimeLimitsWork, NULL);
KeInitializeTimer (&PspJobTimeLimitsTimer);
PspJobTimeLimitsInterval.QuadPart = Int32x32To64(PSP_ONE_SECOND,
PSP_JOB_TIME_LIMITS_TIME);
}
VOID
PspInitializeJobStructuresPhase1(
)
{
//
// Wait until Phase1 executive initialization completes (ie: the worker
// queues must be initialized) before setting off our DPC timer (which
// queues work items!).
//
KeSetTimer (&PspJobTimeLimitsTimer,
PspJobTimeLimitsInterval,
&PspJobTimeLimitsDpc);
}
VOID
PspShutdownJobLimits(
VOID
)
{
// Cancel the job time limits enforcement worker
ExAcquireFastMutex (&PspJobTimeLimitsLock);
PspJobTimeLimitsShuttingDown = TRUE;
KeCancelTimer (&PspJobTimeLimitsTimer);
ExReleaseFastMutex (&PspJobTimeLimitsLock);
}
NTSTATUS
NtIsProcessInJob (
IN HANDLE ProcessHandle,
IN HANDLE JobHandle
)
/*++
Routine Description:
This finds out if a process is in a specific or any job
Arguments:
ProcessHandle - Handle to process to be checked
JobHandle - Handle of job to check process against, May be NULL to do general query.
Return Value:
NTSTATUS - Status of call
--*/
{
KPROCESSOR_MODE PreviousMode;
PEPROCESS Process;
PEJOB Job;
NTSTATUS Status;
PreviousMode = KeGetPreviousMode ();
Status = ObReferenceObjectByHandle (ProcessHandle,
PROCESS_QUERY_INFORMATION,
PsProcessType,
PreviousMode,
&Process,
NULL);
if (!NT_SUCCESS (Status)) {
return Status;
}
if (JobHandle == NULL) {
Job = Process->Job;
} else {
Status = ObReferenceObjectByHandle (JobHandle,
JOB_OBJECT_QUERY,
PsJobType,
PreviousMode,
&Job,
NULL);
if (!NT_SUCCESS (Status)) {
ObDereferenceObject (Process);
return Status;
}
}
if (Process->Job == NULL || Process->Job != Job) {
Status = STATUS_PROCESS_NOT_IN_JOB;
} else {
Status = STATUS_PROCESS_IN_JOB;
}
if (JobHandle != NULL) {
ObDereferenceObject (Job);
}
ObDereferenceObject (Process);
return Status;
}
NTSTATUS
PspGetJobFromSet (
IN PEJOB ParentJob,
IN ULONG JobMemberLevel,
OUT PEJOB *pJob)
/*++
Routine Description:
The function selects the job a process will run in. Either the same job as the parent or a job in the same
job set as the parent but with a JobMemberLevel >= to the parents level/
Arguments:
ParentJob - Job the parent is in.
JobMemberLevel - Member level requested for this process. Zero for use parents job.
Pjob - Returned job to place process in.
Return Value:
NTSTATUS - Status of call
--*/
{
PLIST_ENTRY Entry;
PEJOB Job;
NTSTATUS Status;
//
// This is the normal case. We are not asking to be moved jobs or we are askign for our current level
//
if (JobMemberLevel == 0) {
ObReferenceObject (ParentJob);
*pJob = ParentJob;
return STATUS_SUCCESS;
}
ExAcquireFastMutex (&PspJobListLock);
Status = STATUS_ACCESS_DENIED;
if (ParentJob->MemberLevel != 0 && ParentJob->MemberLevel <= JobMemberLevel) {
for (Entry = ParentJob->JobSetLinks.Flink;
Entry != &ParentJob->JobSetLinks;
Entry = Entry->Flink) {
Job = CONTAINING_RECORD (Entry, EJOB, JobSetLinks);
if (Job->MemberLevel == JobMemberLevel &&
ObReferenceObjectSafe (Job)) {
*pJob = Job;
Status = STATUS_SUCCESS;
break;
}
}
}
ExReleaseFastMutex (&PspJobListLock);
return Status;
}
NTSTATUS
NtCreateJobSet (
IN ULONG NumJob,
IN PJOB_SET_ARRAY UserJobSet,
IN ULONG Flags)
/*++
Routine Description:
This function creates a job set from multiple job objects.
Arguments:
NumJob - Number of jobs in JobSet
UserJobSet - Pointer to array of jobs to combine
Flags - Flags mask for future expansion
Return Value:
NTSTATUS - Status of call
--*/
{
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
ULONG_PTR BufLen;
PJOB_SET_ARRAY JobSet;
ULONG JobsProcessed;
PEJOB Job;
ULONG MinMemberLevel;
PEJOB HeadJob;
PLIST_ENTRY ListEntry;
//
// Flags must be zero and number of jobs >= 2 and not overflow when the length is caculated
//
if (Flags != 0) {
return STATUS_INVALID_PARAMETER;
}
if (NumJob <= 1 || NumJob > MAXULONG_PTR / sizeof (JobSet[0])) {
return STATUS_INVALID_PARAMETER;
}
BufLen = NumJob * sizeof (JobSet[0]);
JobSet = ExAllocatePoolWithQuotaTag (PagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, BufLen, 'bjsP');
if (JobSet == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
PreviousMode = KeGetPreviousMode ();
try {
if (PreviousMode == UserMode) {
ProbeForRead (UserJobSet, BufLen, TYPE_ALIGNMENT (JOB_SET_ARRAY));
}
RtlCopyMemory (JobSet, UserJobSet, BufLen);
} except (ExSystemExceptionFilter ()) {
ExFreePool (JobSet);
return GetExceptionCode ();
}
MinMemberLevel = 0;
Status = STATUS_SUCCESS;
for (JobsProcessed = 0; JobsProcessed < NumJob; JobsProcessed++) {
if (JobSet[JobsProcessed].MemberLevel <= MinMemberLevel || JobSet[JobsProcessed].Flags != 0) {
Status = STATUS_INVALID_PARAMETER;
break;
}
MinMemberLevel = JobSet[JobsProcessed].MemberLevel;
Status = ObReferenceObjectByHandle (JobSet[JobsProcessed].JobHandle,
JOB_OBJECT_QUERY,
PsJobType,
PreviousMode,
&Job,
NULL);
if (!NT_SUCCESS (Status)) {
break;
}
JobSet[JobsProcessed].JobHandle = Job;
}
if (!NT_SUCCESS (Status)) {
while (JobsProcessed-- > 0) {
Job = JobSet[JobsProcessed].JobHandle;
ObDereferenceObject (Job);
}
ExFreePool (JobSet);
return Status;
}
ExAcquireFastMutex (&PspJobListLock);
HeadJob = NULL;
for (JobsProcessed = 0; JobsProcessed < NumJob; JobsProcessed++) {
Job = JobSet[JobsProcessed].JobHandle;
//
// If we are already in a job set then reject this call.
//
if (Job->MemberLevel != 0) {
Status = STATUS_INVALID_PARAMETER;
break;
}
if (HeadJob != NULL) {
if (HeadJob == Job) {
Status = STATUS_INVALID_PARAMETER;
break;
}
InsertTailList (&HeadJob->JobSetLinks, &Job->JobSetLinks);
} else {
HeadJob = Job;
}
Job->MemberLevel = JobSet[JobsProcessed].MemberLevel;
}
if (!NT_SUCCESS (Status)) {
if (HeadJob) {
while (!IsListEmpty (&HeadJob->JobSetLinks)) {
ListEntry = RemoveHeadList (&HeadJob->JobSetLinks);
Job = CONTAINING_RECORD (ListEntry, EJOB, JobSetLinks);
Job->MemberLevel = 0;
InitializeListHead (&Job->JobSetLinks);
}
HeadJob->MemberLevel = 0;
}
}
ExReleaseFastMutex (&PspJobListLock);
//
// Dereference all the objects in the error path. If we suceeded then pin all but the first object by
// leaving the reference there.
//
if (!NT_SUCCESS (Status)) {
for (JobsProcessed = 0; JobsProcessed < NumJob; JobsProcessed++) {
Job = JobSet[JobsProcessed].JobHandle;
ObDereferenceObject (Job);
}
} else {
Job = JobSet[0].JobHandle;
ObDereferenceObject (Job);
}
ExFreePool (JobSet);
return Status;
}
NTSTATUS
PspWin32SessionCallout(
IN PKWIN32_JOB_CALLOUT CalloutRoutine,
IN PKWIN32_JOBCALLOUT_PARAMETERS Parameters,
IN ULONG SessionId
)
/*++
Routine Description:
This routine calls the specified callout routine in session space, for the
specified session.
Parameters:
CalloutRoutine - Callout routine in session space.
Parameters - Parameters to pass the callout routine.
SessionId - Specifies the ID of the session in which the specified
callout routine is to be called.
Return Value:
Status code that indicates whether or not the function was successful.
Notes:
Returns STATUS_NOT_FOUND if the specified session was not found.
--*/
{
NTSTATUS Status;
PVOID OpaqueSession;
KAPC_STATE ApcState;
PEPROCESS Process;
PAGED_CODE();
//
// Make sure we have all the information we need to deliver notification.
//
if (CalloutRoutine == NULL) {
return STATUS_INVALID_PARAMETER;
}
//
// Make sure the callout routine in session space.
//
ASSERT(MmIsSessionAddress((PVOID)CalloutRoutine));
Process = PsGetCurrentProcess();
if ((Process->Flags & PS_PROCESS_FLAGS_IN_SESSION) &&
(SessionId == MmGetSessionId (Process))) {
//
// If the call is from a user mode process, and we are asked to call the
// current session, call directly.
//
(CalloutRoutine)(Parameters);
Status = STATUS_SUCCESS;
} else {
//
// Reference the session object for the specified session.
//
OpaqueSession = MmGetSessionById (SessionId);
if (OpaqueSession == NULL) {
return STATUS_NOT_FOUND;
}
//
// Attach to the specified session.
//
Status = MmAttachSession(OpaqueSession, &ApcState);
if (!NT_SUCCESS(Status)) {
KdPrintEx((DPFLTR_SYSTEM_ID, DPFLTR_WARNING_LEVEL,
"PspWin32SessionCallout: "
"could not attach to 0x%p, session %d for registered notification callout @ 0x%p\n",
OpaqueSession,
SessionId,
CalloutRoutine));
MmQuitNextSession(OpaqueSession);
return Status;
}
//
// Dispatch notification to the callout routine.
//
(CalloutRoutine)(Parameters);
//
// Detach from the session.
//
Status = MmDetachSession (OpaqueSession, &ApcState);
ASSERT(NT_SUCCESS(Status));
//
// Dereference the session object.
//
Status = MmQuitNextSession (OpaqueSession);
ASSERT(NT_SUCCESS(Status));
}
return Status;
}