特權級
為了提供保護,CPU識别4個特權級,0~3級,數字越小表示的特權級越高,數字越大表示的特權級越小。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcpHbzMGbahVYw40VZZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DM0YTMzMDMwEzNygDMzEDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
這是Intel官方文檔中關于特權級的描述,是以這些特權級又叫ring0~ring3。從圖中可以看出ring0運作的是比較核心的代碼,例如kernel,越到外層運作的代碼權限越低,運作的代碼也不那麼核心了。4個特權級不一定全都被使用,在隻使用兩個特權級的代碼中使用的是ring0和ring3。CPU提供這樣的特權級保護是為了防止權限級别較低的代碼通路權限級别較高的代碼的資源,如果随意通路會引起安全問題。CPU通過權限級别檢查來保證正确的權限級别間的通路,如果發現違規就會觸發#GP。
為了實施權限級别檢查CPU會檢查如下三種權限級别之間的關系:
- CPL:CPL是目前代碼段寄存器和堆棧段寄存器的第0位和第1位的值,CPL也決定了目前代碼處哪個ring中。當發生任務切換或者是代碼段轉換的時候CPL會随之改變。
- DPL:DPL是段的權限級别,它由段描述符或者門描述符的DPL位決定。對于不同的段DPL的含義是不同的:
- 資料段:對于資料段DPL表示能夠通路該資料段的代碼或者任務的最低權限級别,例如資料段的DPL==1,那麼隻有CPL==0或者CPL==1的代碼或者任務可以通路該段。
- 非一緻性代碼段:DPL表示能夠通路該代碼段的權限級别,例如DPL == 1,那麼隻有CPL==1的代碼段能夠通路該段,即使CPL==0也不行。
- 調用門:與資料段的DPL一緻。
- 通過調用門通路的代碼段:DPL表示能夠通路該段的最高權限級别,例如DPL==2那麼隻有CPL==2或者CPL==3的任務和程式能夠通路該段。這與資料段的DPL的含義正好相反,這也是低權限級别代碼通路高權限級别代碼的方式。
- TSS:TSS的DPL與資料段一緻。
- RPL:RPL叫做要求權限級别,位于段選擇子第0位和第1位,它可以覆寫目前任務或者程式的CPL,當加載一個段選擇子的時候,CPU會将CPL和RPL中權限較低的作為通路段的權限級别。例如當CPL==0,RPL==3的時候要通路一個DPL==2的資料段,那麼實際通路資料段的時候的權限級别是3,因而會觸發#GP。再有當CPL==3,RPL==0的時候通路DPL==2的資料段,那麼實際通路的權限級别仍然是3,也會觸發#GP。
通路資料段時候的特權級檢查
CPU會根據要通路的段的類型進行不同的權限級别的檢查,通路資料段時的檢查主要是檢查通路的權限和DPL的關系,Intel的官方文檔中給出了一個通路的圖例,非常詳細的列舉了各種情況。
我們的例子代碼沒有實作那麼多種詳細的通路情況,但是也足以驗證在通路資料段時的權限級别檢查了,下面這張圖就是我們的例子中的資料段的通路情況:
這裡實作了三種正常的通路情況:
- CPL==0, RPL==0通路DPL==0的資料段。
- CPL==3, RPL==3通路DPL==3的資料段。
- CPL==0, RPL==3通路DPL==3的資料段。
兩種異常情況:
- CPL==0, RPL==3通路DPL==0的資料段。
- CPL==3, RPL==0通路DPL==0的資料段。
示例
這裡的例子是在上一篇文章 《Task》基礎上修改的,為了實作在通路資料段時的權限級别檢查在GDT中添加了兩個資料段,一個DPL==0,一個DPL==3,此外為這兩個段每一個都定義了兩個選擇子,RPL==0,RPL==3:
# test data CPL == 0
# attr = 0x4092(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=0010)
.equ TEST_DATA_DPL0_BASE, 0xc000
.equ TEST_DATA_DPL0_LIMIT, 0xffff
.equ TEST_DATA_DPL0_ATTR, 0x4092
.equ TEST_DATA_DPL0_SELECTOR_RPL0, 0x58
.equ TEST_DATA_DPL0_SELECTOR_RPL3, 0x5b
# test data CPL == 3
# attr = 0x4092(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=0010)
.equ TEST_DATA_DPL3_BASE, 0xc100
.equ TEST_DATA_DPL3_LIMIT, 0xffff
.equ TEST_DATA_DPL3_ATTR, 0x40f2
.equ TEST_DATA_DPL3_SELECTOR_RPL0, 0x60
.equ TEST_DATA_DPL3_SELECTOR_RPL3, 0x63
添加了一個資料段段檔案data.s:
#
# test data
#
.code32
.globl _start
_start:
###############################################################
# CPL0 message
_test_data_cpl0_msg:
.ascii "TEST DATA MESSAGE, CPL == 0, DPL == 0."
.ascii "TEST DATA MESSAGE, CPL == 3, DPL == 0."
dummy1:
.space 0x100-(.-_start), 0x00
###############################################################
# CPL3 message
_test_data_cpl3_msg:
.ascii "TEST DATA MESSAGE, CPL == 3, DPL == 3."
.ascii "TEST DATA MESSAGE, CPL == 0, DPL == 3."
dummy2:
.space 0x200-(.-_start), 0x00
資料檔案中為兩個資料段分别定義了兩條消息,在後面我們會看到通過将這些消息列印到螢幕上來驗證代碼段對于資料段的通路。
權限級别檢查成功
第一種成功的通路資料段的形式情況就是當代碼運作到kernel,這時候CPL==0,通過RPL==0的選擇子來通路DPL==0的資料段,并将資料段内容列印到螢幕上:
# load test data DPL == 0
movl $TEST_DATA_DPL0_SELECTOR_RPL0, %eax
movw %ax, %ds
movl $TEST_DATA_CPL0_DPL0_MSG_OFFSET, %esi
movl $TEST_DATA_CPL0_DPL0_MSG_LENGTH, %ecx
movl $CPL0_DPL0_VIDEO_OFFSET, %edx
call _kernel_echo
第二種成功通路資料段的情況與第一種情況相同,就是當代碼運作到user的時候,這個時候CPL==3,通過RPL==3的選擇子來通路DPL==3的資料段,并且将資料段的内容列印到螢幕上:
# load test data DPL == 3
movl $TEST_DATA_DPL3_SELECTOR_RPL3, %eax
movw %ax, %ds
movl $TEST_DATA_CPL3_DPL3_MSG_OFFSET, %esi
movl $TEST_DATA_CPL3_DPL3_MSG_LENGTH, %ecx
movl $CPL3_DPL3_VIDEO_OFFSET, %edx
call _user_echo
第三種成功的情況是當代碼從user回到kernel中,這時候CPL==0,通過RPL==3的選擇子來通路DPL==3的資料段,并且将段的内容列印到螢幕上:
# load test data DPL == 3
movl $TEST_DATA_DPL3_SELECTOR_RPL3, %eax
movw %ax, %ds
movl $TEST_DATA_CPL0_DPL3_MSG_OFFSET, %esi
movl $TEST_DATA_CPL0_DPL3_MSG_LENGTH, %ecx
movl $CPL0_DPL3_VIDEO_OFFSET, %edx
call _kernel_echo
代碼運作的結果:
通過列印的結果可以看出代碼段正确的通路了資料段并且都通過了權限級别檢查。
權限級别檢查失敗
第一種權限級别檢查失敗是在user代碼中試圖通路DPL==0的資料段:
movl $TEST_DATA_DPL0_SELECTOR_RPL0, %eax
movw %ax, %ds
運作結果:
從結果中可以看出在user代碼中通路了DPL==3資料段之後,在試圖通路DPL==0的資料段的時候觸發了#GP異常。 第二種權限級别檢查失敗是在kernel中使用RPL==3的選擇子去通路DPL==0的資料段:
movl $TEST_DATA_DPL0_SELECTOR_RPL3, %eax
movw %ax, %ds
運作結果:
當回到kernel中,通路DPL==3的資料段之後,使用RPL==3的選擇子試圖通路DPL==0的資料段的時候觸發了#GP。
加載SS寄存器時的特權級檢查
加載SS寄存器的時候進行的權限級别檢查也涉及到了CPL, RPL和DPL,但是檢查規則與資料段不同,CPU要求加載SS寄存器時CPL==RPL==DPL,否則就會觸發#GP。在上面的代碼例子中驗證這個檢查,在kernel代碼中通過RPL==3的堆棧段選擇子來通路DPL==0的堆棧段:
movl $KERNEL_STACK_SELECTOR_RPL3, %eax
movw %ax, %ss
運作結果:
運作結果看上去和上面的是一樣的,但是觸發的機制是不同的。
參考
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》 《自己動手寫作業系統》 《x86/x64體系探索與程式設計》