note 0:
windows提供了一個作業(job)核心對象,它允許你将程序組合在一起并建立一個"沙箱"來限制程序能夠做什麼.最好将作業對象想象成一個程序容器.但是,即使作業中隻包含一個程序,也是非常有用的,因為這樣可以對程序施加平時不能施加的限制.
note 1:
以下的startrestrictedprocess函數将一個程序放入一個作業中,以限制此程序具體能夠做哪些事情,如下所示:
void startrestrictedprocess() {
// check if we are not already associated with a job.
// if this is the case, there is no way to switch to
// another job.
bool binjob = false;
isprocessinjob(getcurrentprocess(), null, &binjob);
if (binjob) {
messagebox(null, text("process already in a job"),
text(""), mb_iconinformation | mb_ok);
return;
}
// create a job kernel object.
handle hjob = createjobobject(null,
text("wintellect_restrictedprocessjob"));
// 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.
jobli.perjobusertimelimit.quadpart = 10000; // 1 sec in 100-ns intervals
// 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_uilimit_none; // a fancy zero
// the process can't log off the system.
jobuir.uirestrictionsclass |= job_object_uilimit_exitwindows;
// the process can't access user objects (such as 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’ 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;
tchar szcmdline[8];
_tcscpy_s(szcmdline, _countof(szcmdline), text("cmd"));
bool bresult =
createprocess(
null, szcmdline, null, null, false,
create_suspended | create_new_console, null, null, &si, &pi);
// place the process in the job.
// note: if this process spawns any children, the children are
// automatically part of the same job.
assignprocesstojobobject(hjob, pi.hprocess);
// now we can allow the child process' 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...
filetime creationtime;
filetime exittime;
filetime kerneltime;
filetime usertime;
tchar szinfo[max_path];
getprocesstimes(pi.hprocess, &creationtime, &exittime,
&kerneltime, &usertime);
stringcchprintf(szinfo, _countof(szinfo), text("kernel = %u | user = %u\n"),
kerneltime.dwlowdatetime / 10000, usertime.dwlowdatetime / 10000);
messagebox(getactivewindow(), szinfo, text("restricted process times"),
mb_iconinformation | mb_ok);
// clean up properly.
closehandle(pi.hprocess);
closehandle(hjob);
note 2:
isprocessinjob函數可以驗證目前程序是否在一個現有的作業控制之下運作:
bool isprocessinjob(
handle hprocess,
handle hjob,
pbool pbinjob);
note 3:
預設情況下,在windows vista中通過windows資料總管來啟動一個應用程式時,程序會自動同一個專用的作業關聯,此作業的名稱使用了"pca"字元串字首.作業中的一個程序退出時,我們是可以接收到一個通知的.是以,一旦通過windows資料總管啟動的一個曆史遺留的程式出現問題,就會觸發program compatibility assistant(程式相容性助手).
windows vista提供這個功能的目的是檢測相容性問題.是以,如果你已經為應用程式定義了一個清單(manifest),windows資料總管就不會将你的程序同"pca"字首的作業關聯,它會假定你已經解決了任何可能的相容性問題.
但是,在需要調試應用程式的時候,如果調試器是從windows資料總管啟動的,即使有一個清單(mainifest),應用程式也會從調試器繼承帶有"pca"字首的作業.一個簡單的解決方案是從指令行而不是windows資料總管中啟動調試器.在這種情況下,不會發生與作業的關聯.
note 4:
createjobobject函數可以用來建立一個新的作業核心對象:
handle createjobobject(
psecurity_attributes psa,
pctstr pszname);
openjobobject函數可以用來打開一個作業核心對象:
handle openjobobject(
dword dwdesiredaccess,
bool binherithandle,
note 5:
關閉作業的句柄會導緻所有程序都不可通路此作業,即使這個作業仍然存在.如以下代碼所示:
// create a named job object.
handle hjob = createjobobject(null, text("jeff"));
// put our own process in the job.
assignprocesstojobobject(hjob, getcurrentprocess());
// closing the job does not kill our process or the job.
// but the name ("jeff") is immediately disassociated with the job.
// try to open the existing job.
hjob = openjobobject(job_object_all_access, false, text("jeff"));
// openjobobject fails and returns null here because the name ("jeff")
// was disassociated from the job when closehandle was called.
// there is no way to get a handle to this job now.
note 6:
建立好一個作業之後,接着一般需要限制作業中的程序能做的事情;換言之,現在要設定一個"沙箱".可以向作業應用以下幾種類型的限制:
基本限制和擴充基本限制,防止作業中的程序獨占系統資源.
基本的ui限制,防止作業内的程序更改使用者界面.
安全限制,防止作業内的程序通路安全資源(檔案、系統資料庫子項等).
note 7:
setinformationjobobject函數可以向作業應用限制:
bool setinformationjobobject(
jobobjectinfoclass jobobjectinformationclass,
pvoid pjobobjectinformation,
dword cbjobobjectinformationsize);
限制類型
限制 第二個參數的值 第三個參數的結構
類型
基本限制 jobobjectbasiclimitinformation jobobject_basic_limit_information
擴充後的基本限制 jobobjectextendedlimitinformation jobobject_extended_limit_information
基本的ui限制 jobobjectbasicuirestrictions jobobject_basic_ui_restrictions
安全限制 jobobjectsecuritylimitinformation jobobject_security_limit_information
note 8:
針對作業對象基本使用者界面限制的位标志
标志 描述
job_object_uilimit_exitwindows 阻止程序通過exitwindowsex函數登出、關機、重新開機或斷開系統電源.
job_object_uilimit_readclipboard 阻止程序讀取剪貼闆中的内容
job_object_uilimit_writeclipboard 阻止程序清除剪貼闆中的内容
job_object_uilimit_systemparameters 阻止程序通過systemparametersinfo 函數更改系統參數
job_object_uilimit_displaysettings 阻止程序通過changedisplaysettings函數更改顯示設定
job_object_uilimit_globalatoms 為作業指定其專有的全局atom表,并規定作業中的程序隻能通路作業的表
job_object_uilimit_desktop 阻止程序使用createdesktop或switchdesktop函數來建立或切換桌面
job_object_uilimit_handles 阻止作業中的程序使用同一個作業外部的程序所建立的user對象(比如hwnd)
note 9:
有時需要讓作業内部的一個程序同作業外部的一個程序通信.一個簡單的辦法是使用視窗消息.但是,如果作業中的程序不能通路ui句柄,那麼作業内部的程序就不能向作業外部的程序建立的一個視窗發送或張貼視窗消息.幸運的是,可以用另一個函數來解決這個問題,如下所示:
bool userhandlegrantaccess(
handle huserobj,
bool bgrant);
note 10:
queryinformationjobobject函數能查詢對作業施加的限制:
bool queryinformationjobobject(
pvoid pvjobobjectinformation,
dword cbjobobjectinformationsize,
pdword pdwreturnsize);
向此函數傳遞作業的句柄(這類似于setinformationjobobject函數)——這是一個枚舉類型,它指出了你希望哪些限制資訊,要由函數初始化的資料結構的位址,以及包含該資料結構的資料塊的大小.最後一個參數是pdwreturnsize,它指向由此函數來填充的一個dword,指出緩沖區中已填充了多少個位元組.如果對這個資訊不在意,可以(通常也會)為此參數傳遞一個null值.
作業中的程序可以調用queryinformationjobobject獲得其所屬作業的相關資訊(為作業句柄參數傳遞null值).這是很有用的一個技術,因為它使程序能看到自己被施加了哪些限制.不過,如果為作業句柄參數傳遞null值,setinformationjobobject函數調用會失敗——目的是防止程序删除施加于自己身上的限制.
note 11:
assignprocesstojobobject函數可以将程序顯式地放入我建立的作業中:
bool assignprocesstojobobject(
handle hprocess);
這個函數隻允許将尚未配置設定給任何作業的一個程序配置設定給一個作業,你可以使用isprocessinjob函數對此進行檢查.
note 12:
一旦程序已經屬于作業的一部分,它就不能再移動到另一個作業中,也不能成為所謂"無作業"的.還要注意,當作業中的一個程序生成了另一個程序的時候,新程序将自動成為父程序所屬于的作業的一部分.但可以通過以下方式改變這種行為:
1.打開jobobject_basic_limit_information的limitflags成員的job_object_limit_breakaway_ok标志,告訴系統新生成的程序可以在作業外部執行.為此,必須在調用createprocess函數時指定新的create_breakaway_from_job标志.如果這樣做了,但作業并沒有打開job_object_limit_breakaway_ok限制标志,createprocess調用就會失敗.如果希望由新生成的程序來控制作業,這就是非常有用的一個機制.
2.打開jobobject_basic_limit_information的limitflags成員的job_object_limit_silent_breakaway_ok标志.此标志也告訴系統新生成的子程序不是作業的一部分.但是,現在就沒有必要向createprocess函數傳遞任何額外的标志.事實上,此标志會強制新程序脫離目前作業.如果程序在設計之初對作業對象一無所知,這個标志就相當有用.
在調用了assignprocesstojobobject之後,新程序就成為受限制的作業的一部分.然後,調用resumethread,使程序的線程可以在作業的限制下執行代碼.
note 13:
visual studio沒有一個簡單的辦法來停止正在進行的一次生成,因為它必須知道哪些程序是從它生成的第一個程序生成的.(這非常難.在microsoft systems journal 1998年6月期的win 32 q&a專欄讨論
過developer studio是如何做到這一點的,可以通過以下網址找到這篇文章:http://www.microsoft.com/msj/0698/win320698.aspx.)也許visual studio未來的版本會轉而使用作業,因為這樣一來,代碼的編寫會變得更容易,而且可以用作業來做更多的事情.
note 14:
terminatejobobject函數可以殺死作業内部的所有程序:
bool terminatejobobject(
uint uexitcode);
這類似于為作業内的每一個程序調用terminateprocess,将所有退出代碼設為uexitcode.
note 15:
getprocessiocounters函數可以獲得沒有放入作業的那些程序的資訊,如下所示:
bool getprocessiocounters(
pio_counters piocounters);
note 16:
任何時候都可以調用queryinformationjobobject,獲得作業中目前正在運作的所有程序的程序id集.為此,必須首先估算一下作業中有多少個程序,然後,配置設定一個足夠大的記憶體塊來容納由這些程序id構成的一個數組,另加一個jobobject_basic_process_id_list結構的大小:
typedef struct _jobobject_basic_process_id_list {
dword numberofassignedprocesses;
dword numberofprocessidsinlist;
dword processidlist[1];
} jobobject_basic_process_id_list, *pjobobject_basic_process_id_list;
是以,為了獲得作業中目前的程序id集,必須執行以下類似的代碼:
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 =
(pjobobject_basic_process_id_list)_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 (dword 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.
這就是使用這些函數所能獲得的所有資訊.
note 17:
作業限制下的所有程序都用棕色來突出顯示.
note 18:
作業中的程序如果尚未用完已配置設定的cpu時間,作業對象就是nonsignaled(無信号)的.一旦用完所有已配置設定的cpu時間,windows就會強行殺死作業中的所有程序,作業對象的狀态會變成signaled(有信号).通過調用waitforsingleobject(或者一個類似的函數),可以輕松捕捉到這個事件.順便提一句,可以調用setinformationjobobject并授予作業更多的cpu時間,将作業對象重置為原來的nonsignaled狀态.
note 19:
microsoft選擇在已配置設定的cpu時間到期時,才将作業的狀态變成signaled.在許多作業中,都會有一個父程序一直在運作,直至其所有子程序全部結束.是以,我們可以隻等待父程序的句柄,借此得知整個作業何時結束.
note 20:
如何獲得一些"進階"的通知(比如程序建立/終止運作).要獲得這些額外的通知,必須在自己的應用程式中建立更多的基礎結構.具體來講,你必須建立一個i/o完成端口(completion port)核心對象,并将自己的作業對象與完成端口關聯.然後,必須有一個或者多個線程在完成端口上等待并處理作業通知.
note 21:
一旦建立了i/o完成端口,就可以調用setinformationjobobject将它與一個作業關聯起來,如下所示:
jobobject_associate_completion_port joacp;
joacp.completionkey = 1; // any value to uniquely identify this job
joacp.completionport = hiocp; // handle of completion port that
// receives notifications
setinformationjobobject(hjob, jobobjectassociatecompletionportinformation,
&joacp, sizeof(jaocp));
執行上述代碼後,系統将監視作業,隻要有事件發生,就會把它們post到i/o完成端口(順便提一句,你可以調用queryinformationjobobject來擷取completion key和完成端口句柄,但很少有必要這樣做).線程通過調用getqueuedcompletionstatus來監視完成端口:
bool getqueuedcompletionstatus(
handle hiocp,
pdword pnumbytestransferred,
pulong_ptr pcompletionkey,
poverlapped *poverlapped,
dword dwmilliseconds);
note 22:
在預設情況下,作業對象是這樣配置的:當作業已配置設定的cpu時間到期時,它的所有程序都會自動終止,而且不會post job_object_msg_end_of_job_time這個通知.如果你想阻止作業對象殺死程序,隻是簡單地通知你cpu時間到期,就必須像下面這樣執行代碼:
// create a jobobject_end_of_job_time_information structure
// and initialize its only member.
jobobject_end_of_job_time_information joeojti;
joeojti.endofjobtimeaction = job_object_post_at_end_of_job;
// tell the job object what we want it to do when the job time is
// exceeded.
setinformationjobobject(hjob, jobobjectendofjobtimeinformation,
&joeojti, sizeof(joeojti));
針對endofjobtimeaction,你惟一能指定的另一個值就是job_object_terminate_at_end_of_job.這是建立作業時的預設值.
note 23:
使用psapi.h中的函數(比如getmodulefilenameex和getprocessimagefilename),可以根據程序id來獲得程序的完整路徑名稱.但是,當作業收到通知,知道一個新程序在它的限制下建立時,前一個函數調用會失敗.因為在這個時候,位址空間尚未完全初始化:子產品尚未與它建立映射.getprocessimagefilename則很有意思,因為即使在那種極端情況下,它也能擷取完整路徑名.但是,路徑名的格式近似于核心模式(而不是使用者模式)中看到的格式.例如,是\device\harddiskvolume1\windows\system32\notepad.exe,而不是c:\windows\system32\notepad.exe.這就是你應該依賴于新的queryfullprocessimagename函數的
原因.該函數在任何情況下都會傳回你預期的完整路徑名.