mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3439 lines
110 KiB
3439 lines
110 KiB
/*++
|
|
|
|
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;
|
|
}
|