void StartRestrictedProcess() {
// Create a job kernel object
HANDLE hjob = CreateJobObject(NULL, NULL);
// Place some restrictions on processes in the job
// First, set some basic restrictions
JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };
// The process always runs in the idle priority class
jobli.PriorityClass = IDLE_PRIORITY_CLASS;
// The job cannot use more than 1 second of CPU time
// 1 second in 100-ns intervals
jobli.PerJobUserTimeLimit.QuadPart = 10000000;
// These are the only 2 restrictions I want placed on the job (process)
jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS |
JOB_OBJECT_LIMIT_JOB_TIME;
SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jobli,
sizeof(jobli));
// Second, set some UI restrictions
JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
jobuir.UIRestrictionsClass = JOB_OBJECT_UILIM3wIT_NONE; // A fancy 0
// The process can't logoff the system
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;
// The process can't access USER object (like other windows) in the system
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES
SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir,
sizeof(jobuir));
// Spawn the process that is to be in the job
// Note: You must first spawn the process and then place the process in the
// job.
// This means that the process's thread must be initially suspended so that
// it can't execute any code outside of the job's restrictions.
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, "CMD", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL,
&si, &pi);
// Place the process in the job.
// Note: if this process spawns any children, the children are
// automatically associated with the same job
AssignProcessToJobObject(hjob, pi.hProcess);
// Now, we can allow the child process's thread to execute code.
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
// Wait for the process to terminate or for all the job's allotted CPU time
// to be used
HANDLE h[2];
h[0] = pi.hProcess;
h[1] = hjob;
DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
switch (dw - WAIT_OBJECT_0) {
case 0:
// The process has terminated…
break;
case 1:
// All of the job's allotted CPU time was used…
break;
}
// Clean-up properly
CloseHandle(pi.hProcess);
CloseHandle(hjob);
}
Figure 2 Setting Restrictions
To Set
|
Value for Second Parameter
|
Structure Used for Third Parameter
|
Basic Limits
|
JobObjectBasicLimitInformation
|
JOBOBJECT_BASIC_LIMIT_INFORMATION
|
Extended Basic Limits
|
JobObjectExtendedLimitInformation
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
Basic UI Restrictions
|
JobObjectBasicUIRestrictions
|
JOBOBJECT_BASIC_UI_RESTRICTIONS
|
Security Limits
|
JobObjectSecurityLimitInformation
|
JOBOBJECT_SECURITY_LIMIT_INFORMATION
|
Figure 3
JOBOBJECT_BASIC_LIMIT_INFORMATION
Member Descriptions
Member
|
Description
|
Notes
|
PerProcessUserTimeLimit
|
Specifies the maximum user mode time allotted to each process (in 100 nanosecond intervals).
|
The system automatically terminates any process that uses more than its allotted time.
|
PerJobUserTimeLimit
|
Specifies how much more user mode time can be used by processes in this job (in 100 Nanosecond intervals).
|
By default, the system automatically terminates all processes when this time limit is reached. You can change this value periodically as the job runs. |
LimitFlags
|
Flags indicating which restrictions you want to apply to the job.
|
Explained in the article text.
|
MinimumWorkingSetSize/ MaximumWorkingSetSize |
Specifies the minimum and maximum working set size for each process (not for all processes within the job).
|
Normally, a process’s working set can grow above its maximum, but setting the MaximumWorkingSetSize forces a hard limit; once the process’s working set reaches this limit, the process pages against itself. Calls to SetProcessWorkingSetSize by an individual process are ignored unless the process is just trying to empty its working set. |
ActiveProcessLimit
|
Specifies the maximum number of processes that can run concurrently in the job.
|
Attempting to go over this limit causes the new process to be terminated with a “not enough quota” error.
|
Affinity
|
Specifies the subset of CPUs that can run the processes.
|
Individual processes can limit this even further.
|
PriorityClass
|
Specifies the priority class used by all processes.
|
If a process calls SetPriorityClass, the call will return success even though it actually fails. If the process calls GetPriorityClass, it returns what the process has set the priority class to, although this may not be the process’s actual priority class. In addition, SetThreadPriority fails to raise threads above normal priority, but may be used to lower a thread’s priority.
|
SchedulingClass
|
Specifies a relative time quantum difference assigned to threads in the job.
|
Value can be from 0 to 9, inclusive. 5 is the default.
|
Figure 4 UIRestrictionsClass Bit Flags
Flag
|
Description
|
JOB_OBJECT_UILIMIT_EXITWINDOWS
|
Prevents processes from logging off, shutting down, rebooting, or powering off the system via the ExitWindowsEx function
|
JOB_OBJECT_UILIMIT_READCLIPBOARD
|
Prevents processes from reading the clipboard
|
JOB_OBJECT_UILIMIT_WRITECLIPBOARD
|
Prevents processes from erasing the clipboard
|
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS
|
Prevents processes from changing system parameters via the SystemParametersInfo function
|
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS
|
Prevents processes from changing the display settings via the ChangeDisplaySettings function
|
JOB_OBJECT_UILIMIT_GLOBALATOMS
|
Gives the job its own global atom table and restricts processes in the job to accessing only the job’s table
|
JOB_OBJECT_UILIMIT_DESKTOP
|
Prevents processes from creating/switching desktops via the CreateDesktop/SwitchDesktop functions
|
JOB_OBJECT_UILIMIT_HANDLES
|
Prevents processes from using USER objects (such as HWNDs) created by processes outside the same job
|
Figure 6 JOBOBJECT_SECURITY_LIMIT_INFORMATION
Member
|
Description
|
SecurityLimitFlags
|
Flags indicating whether to disallow administrator access, disallow unrestricted token access, force a specific access token, or to disable certain SIDs/privileges
|
JobToken
|
Access token to be used by all processes in the job
|
SidsToDisable
|
Indicates which SIDs to disable for access checking
|
PrivilegesToDelete
|
Indicates which privileges to delete from the access token
|
RestrictedSids
|
Indicates a set of deny-only SIDs that should be added to the access token
|
Figure 7
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
Members
Member
|
Description
|
TotalUserTime
|
Specifies how much user mode CPU time processes in the job have used
|
TotalKernelTime
|
Specifies how much kernel mode CPU time processes in the job have used
|
ThisPeriodTotalUserTime
|
Like TotalUserTime except this value is reset to zero when SetInformationJobObject is called change to basic limit information and the JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME
limit flag is not used
|
ThisPeriodTotalKernelTime
|
Like ThisPeriodTotalUserTime except it shows kernel mode time
|
TotalPageFaultCount
|
Specifies the total number of page faults processes in the job have accrued
|
TotalProcesses
|
Specifies the total number of processes that have ever been a part of the job
|
ActiveProcesses
|
Specifies the number of processes currently a part of the job
|
TotalTerminatedProcesses
|
Specifies the number of processes that have been killed due to exceeding their allotted CPU time limit
|
Figure 8 EnumProcessIdsInJob
void EnumProcessIdsInJob(HANDLE hjob) {
// I assume that there will never be more than 10 processes in this job
#define MAX_PROCESS_IDS 10
// Calculate the number of bytes needed for structure & process IDs
DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) +
(MAX_PROCESS_IDS - 1) * sizeof(DWORD);
// Allocate the block of memory
PJOBOBJECT_BASIC_PROCESS_ID_LIST pjobpil = _alloca(cb);
// Tell the function the maximum number of processes that we allocated space
// for
pjobpil->NumberOfAssignedProcesses = MAX_PROCESS_IDS;
// Request the current set of process IDs
QueryInformationJobObject(hjob, JobObjectBasicProcessIdList, pjobpil, cb,
&cb);
// Enumerate the Process IDs
for (int x = 0; x < pjobpil->NumberOfProcessIdsInList; x++) {
// Use pjobpil->ProcessIdList[x]…
}
// Since _alloca was used to allocate the memory, we don't need to free it here
}
Figure 11 Job Events
Event
|
Description
|
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
|
Posted when no processes are running in the job.
|
JOB_OBJECT_MSG_END_OF_PROCESS_TIME
|
Posted when a process’s allotted CPU time is exceeded. The process is terminated and the process’s ID is given.
|
JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT
|
Posted when attempting to exceed the number of active processes in the job.
|
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT
|
Posted when a process attempts to commit storage of the process’s limit. The process’s ID is given.
|
JOB_OBJECT_MSG_JOB_MEMORY_LIMIT
|
Posted when a process attempts to commit storage over the job’s limit. The process’s ID is given.
|
JOB_OBJECT_MSG_NEW_PROCESS
|
Posted when a process is added to a job. The process’s ID is given.
|
JOB_OBJECT_MSG_EXIT_PROCESS
|
Posted when a process terminates. The process’s ID is given.
|
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS
|
Posted when a process terminates due to an unhandled exception. The process’s ID is given.
|
JOB_OBJECT_MSG_END_OF_JOB_TIME
|
Posted when the job’s allotted CPU time is exceeded. The processes are not terminated. You can allow them to continue running, set a new time limit, or call TerminateJobObject yourself.
|