目錄
一、前景回顧
二、什麼是特權級檢查
三、門
四、如何進行特權級檢查
五、呼叫門的跳轉執行流程
六、呼叫門的跳轉許可權檢查
我們在前面講過保護模式較之於真真實模式的三大特點:分頁機制、特權級和分時機制。現在分頁機制的坑已經填好了,接下來我們開始填特權級的坑。
首先我們來看看什麼是特權級,CPU為了計算機的安全,將程式擁有的權力劃分為了4個等級,也就是特權級,特權級按照權力大小劃分為了0、1、2、3級,數位越小,權力越大。我們作業系統核心所佔的特權級就是0級,處於至高無上的地位,而使用者的應用程式處於3級,最低特權級。不過在Linux中,實際只用上了0級和3級,即我們常說的使用者態便處於3級,核心態處於0級。
特權級檢查,說到底就是起到一個保護作用,作業系統為了避免使用者程式隨意存取核心,修改核心資料,所以在「存取者」存取「受訪者」那一剎那,體現在實際中就是:段暫存器中的值被重新載入段選擇子時(因為我們已經進入保護模式),檢查存取者的特權級和受訪者的特權級是否匹配。
在特權級中我們要認識一些特定術語:
1、CPL:處理器當前的特權級。
2、DPL:段或者門的特權等級,位於段描述符或者門描述符的DPL欄位。
3、RPL:請求特權級,位於選擇子的RPL欄位。
書上其實關於特權級的介紹還是比較詳細,只不過對於初學者來說,不是那麼好容易理解。我前前後後看了好幾遍才算是大體理解了。所以我嘗試用我自己的語言來描述一下特權級檢查,重點就是CPL、DPL和RPL之間的關係。
首先CPL是用來描述處理器當前的特權級的,我們的處理器只用上了0和3特權級,也就是說如果處理器在執行使用者態程式時,處理器此時的特權級就是3級,如果處理器此時處於核心態,那麼處理器的特權級就是0級。
DPL描述的是段描述符或者門描述符的存取門檻,也就是說如果處理器的特權級低於目標段或者目標門的存取門檻,那麼處理器就無法存取段描述符或者門描述符描述的那段記憶體區域。我相信這個也是比較好理解的。
重點就是RPL,RPL表示的是請求資源的能力。這個其實是最不好理解的,咋一看RPL其實跟CPL應該是沒什麼區別的,因為書上都說,當處理器從一個特權級的程式碼段A轉移到另一個特權級的程式碼段B上執行時(這裡假設特權檢測通過),處理器的CPL就會變更為程式碼段B的DPL,也就是目的碼段描述符的DPL將儲存在程式碼段暫存器CS的RPL位,其實還是有區別的。
這裡我自己的理解是:RPL其實並不是指的處理器的請求資源能力,它應該是描述的段的請求資源的能力。我還是舉例子來說明,如果使用者通過中斷門從使用者態進入核心態(這個是很常見的低特權級進入高特權級的方式),此時CS段暫存器的CPL和RPL都應該是0,因為此時我們已經進入了核心態,無所不能了。如果使用者程式有壞心思,想將程式碼段C(DPL為0)中存放的核心程式碼拷貝到指定的資料緩衝區中,這個緩衝區本質上是一個資料段D。重點來了,不管這個資料段是來自使用者空間還是核心空間,當段選擇子載入到DS暫存器時,該段資料段的RPL都會被作業系統修改為使用者程序的CPL,也就是3。此時我們來看看特權檢查的規則:
請求某特權級為DPL級的資源時,參與特權檢查的不只是CPL,還要加上RPL,CPL和RPL的特權必須同時大於等於受訪者的特權DPL:
數值上CPL≤DPL並且RPL≤DPL
顯然該資料段就無法接收核心程式碼,因為它的RPL為3,沒有這個能力。關於特權級更詳細的介紹我不在贅述,感興趣的朋友可以參考原書《作業系統真象還原》p229~p251。
「門結構」的存在是為了實現從低特權級到高特權級的轉變,CPU中實現特權級變換有四種門。門結構就是記錄一段程式起始地址的描述符。
他們的使用方式有些許不同:
1、任務門:任務門以任務段TSS位單位,用來實現工作切換。但是現代作業系統很少使用,我們後面也不用。
2、中斷門:以int指令主動發中斷的形式實現從低特權級到高特權級轉移,Linux系統呼叫便是使用此門實現的。
3、陷阱門:以int3指令主動發中斷的形式實現從低特權級到高特權級轉移,一般是在編譯器偵錯用。我們也不用。
4、呼叫門:call或jmp指令後接呼叫門選擇子作引數,以呼叫函數例程的方式實現從低特權級到高特權級轉移。
還是照搬書上的總結,當受訪者為資料時:
CPL <= 目標資料段DPL && RPL <=目標資料段DPL,即資料段不允許被比本資料段特權級更低的程式碼段存取。
當受訪者為程式碼時,分為如下三種情況:
1、無門結構且目標為非一致性程式碼段:CPL = RPL = 目的碼段DPL
2、無門結構且目標為一致性程式碼段:CPL >= 目的碼段DPL && RPL >= 目的碼段DPL
3、有門結構:DPL_GATE >= CPL >= DPL_CODE && RPL <= DPL_GATE
這裡以呼叫門的跳轉執行流程為例講解門的使用,先看圖:
呼叫門的門描述符依舊是佔據4個位元組大小,被放置在GDT表中。使用者程式中通過「call 呼叫門選擇子」來存取呼叫門,處理器通過選擇子中的高13位索引號加上GDT基址得到門描述符,在門描述符中又得到核心中被呼叫例程所在程式碼段的選擇子以及偏移,又根據得到的選擇子在GDT表中索引得到核心中被呼叫例程所在程式碼段的基址,將偏移量和基址結合就得到核心中被呼叫例程的地址。
援引書中的一個例子,能夠更好地說明呼叫門的特權級檢查:
假設當前處理器正在 DPL為3的程式碼段上執行,即正在執行使用者程式,故處理器當前特權級CPL為 3。此時使用者程序想獲取安裝的實體記憶體大小,該資料儲存在作業系統的資料段中,該段DPL為0。由於當前執行的是使用者程式,CPL為3,所以無法存取DPL為0的資料段。於是它使用呼叫門向系統救助。呼叫門是作業系統安裝在全域性描述符表GDT中的,為了讓使用者程序可以使用此呼叫門,作業系統將該呼叫門描述符的DPL設為3。該呼叫門只需要一個引數,就是使用者程式用於儲存系統記憶體容量的緩衝區所在資料段的選擇子和偏移地址。呼叫門描述符中記錄的就是核心服務程式所在程式碼段的選擇子及在程式碼段內的偏移量。使用者程序用「call 呼叫門選擇子」的方式使用呼叫門,此呼叫門選擇子是由作業系統提供的,該選擇子的 RPL為3,此時如果使用者偽造一個呼叫門選擇子也沒用,因為此選擇子是用來索引門描述符的,並不用來指向緩衝區的選擇子,呼叫門選擇子中的高13位索引值必須要指向門描述符在GDT中的位置,選擇子中低2位的RPL偽造也沒意義,因為此時CPL為3,是短板,以它為主。此時處理器便進行特權級檢查,CPL為 3,RPL為3,門描述符DPL為3,即數值上(CPL≤DPL && RPL≤DPL)成立,初步檢查通過。接下來還要再將CPL與門描述符中選擇子所對應的程式碼段描述符DPL比較,這是呼叫門對應的核心服務程式的DPL,為敘述方便將其記作DPL_CODE。由於DPL_CODE是核心程式的特權級,所以DPL_CODE為0,CPL為3,即數值上滿足CPL≥DPL_CODE,CPL比目標特權級低,檢查通過,該使用者程式可以用呼叫門,於是處理器的當前特權級CPL的值用DPL_CODE代替,記錄在CS.RPL中,此時CPL變為0。接下來,處理器便以0特權級的身份開始執行該核心服務程式,由於該服務程式的引數是使用者提交的緩衝區所在的資料段的選擇子及偏移量,為避免使用者將緩衝區指向了核心的資料區,安全起見,在該核心服務程式中,作業系統將這個使用者所提交的選擇子的RPL變更為使用者程序的CPL,也就是指向緩衝區所在段的選擇子的 RPL變成了3。前面說過,引數都是核心在0級棧中獲得的,雖然使用者程序將緩衝區的選擇子及偏移量壓在了3特權級棧中,但由於呼叫門的特權級變換,引數已經由處理器在韌體一級上自動複製到0特權級棧中了。使用者的程式碼段暫存器 CS 也在特權級發生變化時,由處理器自動壓入到0特權級棧中,所以作業系統需要的引數都可以在自己的0特權級棧中找到。使用者緩衝區的選擇子修改過後,接下來核心服務程式將使用者所需要的記憶體容量大小寫到這個選擇子和使用者提交的偏移量對應的緩衝區。如果使用者程式想搞破壞,所提交的這個緩衝區選擇子指向的目標段不是使用者程序自己的資料段,而是核心資料段或核心程式碼段,由於目標段的DPL為0,雖然此時已在核心中執行,CPL為0,但選擇子RPL已經被改為3,數值上不滿足CPL≤DPL && RPL≤DPL,往緩衝區中的寫入被拒絕,處理器引發異常。如果使用者程式提交的緩衝區選擇子確實指向使用者程式自己的資料段,DPL則為3,數值上滿足CPL≤DPL && RPL≤DPL,往緩衝區中的寫入則會成功。如果中斷服務程式內部再有存取核心自己記憶體段的操作,還會按照數值上(CPL≤DPL && RPL≤DPL)的策略進行新一輪的特權檢測。通常,如果不是使用者程式向核心提交緩衝區地址來接收資料的話,核心不會主動存取使用者的記憶體段,多是存取自己的資料段或程式碼段,核心服務程式中若存取核心自己的記憶體段,由於記憶體段的DPL為0,所以段選擇子的RPL也必須為0。
好了,本回到此結束了,一兩句話是講不清楚特權級的,要想搞懂還是得認真看書。預知後事如何,請看下回分解。