轉載自:http://blog.csdn.net/hzrandd/article/details/51002903
先說下特權級的概念,在保護模式下,系統依靠特權級來實施代碼和資料的保護,相當于權限
啦。特權級共有4 個級别,0,1,2,3,數字越小表示權限越高。如圖:

較為核心的代碼和資料放在較高(靠内)的層級中,處理器用此來防止較低特權的任務在不被
允許的情況下通路處于高特權級的段。為了防止概念混淆,我們不用特權級大小來說明,改
為内層(高),外層(低)來講。
特權級有3 種:CPL,DPL 和RPL,每個都是有4 個等級。
我對他們的關系了解是這樣:一般來說,CPL 代表目前代碼段的權限,如果它想要去通路
一個段或門,首先要看看對方的權限如何,也就是檢查對方的DPL,如果滿足目前的權限
比要通路的權限高,則有可能允許去通路,有些情況我們還要檢查選擇子的權限,即RPL,
因為我們通過選擇子:偏移量的方式去通路一個段,這算是一個通路請求動作,是以稱為請
求通路權限RPL(Requst Privilege Level)。當請求權限也滿足條件,那麼通路就被
允許了。
CPL(Current Privilege Level)
CPL 是目前執行的任務的特權等級,它存儲在CS 和SS 的第0 位和第1 位上。(兩位表示0~3
四個等級)
通常情況下,CPL 等于代碼所在段的特權等級,當程式轉移到不同的代碼段時,處理器将改變
CPL。
注意:在遇到一緻代碼段時,情況特殊,一緻代碼段的特點是:可以被等級相同或者更低特權級
的代碼通路,當處理器通路一個與目前代碼段CPL 特權級不同的一緻代碼段時,CPL 不會改
變。
DPL(Descriptor Privilege Level)
表示門或者段的特權級,存儲在門或者段的描述符的DPL 字段中。正如上面說的那樣,當目前
代碼段試圖通路一個段或者門時,其DPL 将會和目前特權級CPL 以及段或門的選擇子比較,
根據段或者門的類型不同,DPL 的含義不同:
1.資料段的DPL:規定了通路此段的最低權限。比如一個資料段的DPL 是1,那麼隻有運
行在CPL 為0 或1 的程式才可能通路它。為什麼說可能呢?因為還有一個比較的因素是RPL。
通路資料段要滿足有效特權級别(上述)高于資料段的DPL.
2.非一緻代碼段的DPL(不使用調用門的情況):DPL 規定通路此段的特權,隻有CPL 與
之相等才有可能通路。
3.調用門的DPL,規定了程式或任務通路該門的最低權限。與資料段同。
4.一緻代碼段和通過調用門通路的非一緻代碼段,DPL 規定通路此段的最高權限。
比如一個段的DPL 為2,那麼CPL 為0 或者1 的程式都無法通路。
5. TSS 的DPL,同資料段。
RPL(Rquest Privilege Level)
RPL 是通過選擇子的低兩位來表現出來的(這麼說來,CS 和SS 也是存放選擇子的,同時CPL
存放在CS 和SS 的低兩位上,那麼對CS 和SS 來說,選擇子的RPL=目前段的CPL)。處理
器通過檢查RPL 和CPL 來确認一個通路是否合法。即提出通路的段除了有足夠的特權級CPL,
如果RPL 不夠也是不行的(有些情況會忽略RPL 檢查)。
為什麼要有RPL?
作業系統往往通過設定RPL 的方法來避免低特權級的應用程式通路高特權級的内層資料。
例子情景:調用者調用作業系統的某過程去通路一個段。
當作業系統(被調用過程)從應用程式(調用者)接受一個選擇子時,會把選擇子的RPL 設定稱調
用者的權限等級,于是作業系統用這個選擇子去通路相應的段時(這時CPL 為作業系統的等級,
因為正在運作作業系統的代碼),處理器會使用調用者的特權級去進行特權級檢查,而不是正在
實施通路動作的作業系統的特權級(CPL),這樣作業系統就不用以自己的身份去通路(就防止了
應用去通路需要高權限的内層資料,除非應用程式本身的權限就足夠高)。
那麼RPL 的作用就比較明顯了:因為同一時刻隻能有一個CPL,而當低權限的應用去調用擁
有至高權限的作業系統的功能來通路一個目标段時,進入作業系統代碼段時CPL 變成了操作系
統的CPL,如果沒有RPL,那麼權限檢查的時候就會用CPL,而這個CPL 權限比應用程式高,
也就可能去通路需要高權限才能通路的資料,這就不安全了。是以引入RPL,讓它去代表通路
權限,是以在檢查CPL 的同時,也會檢查RPL.一般來說如果RPL 的數字比CPL 大(權限比
CPL 的低),那麼RPL 會起決定性作用。
說這麼多不明白都不行啦~真累
下面是引用的一個超棒的關于權限控制的總結:
引用位址
還有一篇文章在此。
資料通路時的權限check
一、 通路data segment 時(ds、es、fs 及gs)
1、 程式指令要通路資料時,data segment selector 被加載進 data segment register
(ds、es、fs 和 gs)前,處理器會進行一系列的權限檢查,通過了才能被加載進入segment
register。處理器分為兩步進行檢查:
★ CPL(目前程式運作的權限級别)與RPL(位于selector 中的 RPL)作比較,并設定
有效權限級别為低權限的一個。
★ 得出的有效權限級别與DPL(segment descriptor 中的DPL)作比較,有效權限級
别高于DPL,那麼就通過。低于就不允許通路。
2、舉個例子:
如果:CPL = 3、RPL = 2、DPL = 2。那麼
EPL = CPL > RPL ? CPL : RPL;
if (EPL <= DPL) {
} else {
}
或者:
if ((CPL <= DPL) && (RPL <= DPL)) {
} else {
}
也就是要通路目标data segment,那麼必須要有足夠的權限,這個足夠的權限就是:
目前運作的權限級别及選擇子的請求權限級别要高于等于目标data segment 的權限級
别。
二、 通路stack segment 時
1、 該問stack 時的權限檢查更嚴格,CPL、RPL 及DPL 三者必須相等才能通過該問請
求。
2、 舉個例子:
if (CPL == RPL && RPL == DPL && CPL == DPL) {
} else {
}
也就是說每個權限級别有相對應的statck segment。不能越權通路,即使高權限通路低權
限也是被拒絕的
控制權的轉移及權限檢查。
權限檢查的4 個要素:
★ CPL:處理器目前運作的級别,也就是:目前 CS 的級别,在 CS 的 BIT0 ~ Bit1
★ DPL:通路目标代碼段所需的級别。定義在 segment descriptor 的 DPL 域中
★ RPL: 通過 selector 進行通路時,selector 内定義的級别。
★ conforming/nonconforming:目标代碼屬于 nonconforming 還是 conforming
定義在segment descritptor 的 C 标志位中
x86 的各方面檢查依賴于目标代碼段是 nonconforming(不一緻) 還是
conforming(一緻) 類型
一、 直接轉移(far call 及 far jmp)
1、 直接轉移定義為不帶gate selector 或 taskselector 的遠調用。當執行一條 call
cs:eip 或 jmp cs:eip 指令時,cs 是目标代碼段的selector,處理器在加載指令操作數
中的cs 進cs register 前,要進行一系列的權限檢查,控制權的轉移權限分兩部分,根據
目标代碼段descriptor 定義的兩種情況:
1)、nonconforming target code segment
★ 直接轉移後的權限級别是不能必改變的。是以,CPL 必須要等于目标代碼段的 DPL。
★ 要有足夠的請求權限進行通路。是以,目标代碼段選擇子的RPL <= CPL
2)、conforming target code segment
★ conforming code segment 允許通路高權限級别的代碼。這裡隻需檢
查 CPL >= DPL 即可,RPL 忽略不檢查。
★ 轉移後CPL 不會改變。
2、 以上兩步通過後,處理器加載目标代碼段的CS 進入CS register,但權限級别不
改變,繼而RPL 被忽略。
★ 處理器根據CS selector 在相應的descriptor table 找到 code segment
descriptor。CS 的Bit2(TI 域) 訓示在哪個descriptor table 表查找,CS.TI = 0 時
在GDT 查找,CS.TI = 1 時在LDT 查找。
★ CS 的Bit15~Bit3 是selector index 值,處理器基于GDT 或LDT 來查找segment
descriptor。具體是:GDTR.base 或 LDTR.base + CS.SI × 8 得出code segment
descritpro。
★ 處理器自動加載code segment descriptor 的 base address、segment limit 及
attribute 域進入 CS register 的相應的隐藏域。
★ 轉到CS.base + eip 處執行指令
總結:用代碼形式來說明直接轉移 call cs:eip 這條指令
例: call 1a:804304c (即cs = 1a, eip = 804304c)
target_cs = 1a;
target_eip = 0x0804304c;
CPL = CS.RPL;
RPL = target_cs.RPL;
target_si = target_cs.SI;
target_ti = target_cs.TI;
CODESEG_DESCRIPTOR target_descriptor;
if (target_ti == 0) {
target_descriptor = GDTR.base + target_si * 8
} else {
target_descriptor = LDTR.base + target_si * 8;
}
DPL = target_descriptor.DPL;
if (target_descriptor.type & 0x06) {
if (CPL >= DPL) {
} else {
goto DO_GP_EXCEPTION;
}
} else {
if (CPL == DPL && RPL <= CPL) {
} else {
goto DO_GP_EXCEPTION;
}
}
CS = target_cs;
EIP = target_eip;
goto target_descriptor.base + target_eip;
DO_GP_EXCEPTION:
⋯ ⋯
二、 使用call gate 進行控制權的轉移
使用call gate 進行轉移控制,目的是建立一個利用gate 進行向高權限代碼轉移的一種保
護機制。gate 符相當一個進入高權限代碼的一個通道。
對于指令 call cs:eip 來說:
★ 目标代碼的selector 是一個門符的選擇子。用來擷取門描述符。
★ 門描述符含目标代碼段的selector 及目标代碼的偏移量。
★ 目标代碼的ip 值被忽略。因為門符已經提供了目标代碼的偏移量。
1、 權限的檢查
★ 首先,必須要有足夠的權限來通路gate 符,是以:CPL <= DPLg(門符的DPL)且:
RPL <= DPLg
★ 進前代碼向高權限代碼轉移,是以,對于conforming 類型的代碼段來說,必須CPL >=
DPLs(目标代碼段的DPL)
★ 對于nonconforming 類型代碼段來說,必須CPL = DPLs
★ call 指令改變目前權限,而jmp 指令不改變目前權限。
總結:
if ((CPL <= DPLg) && (RPL <= DPLg)) {
if (target.C == CONFORMING) {
if (CPL >= DPLs) {
} else {
}
} else {
if (CPL == DPLs) {
} else {
}
}
} else {
}
2、 控制權的轉移
指令:call 1a:804304c (其中1a 是call gate selector)
gate_selector = 0x1a;
RPL = gate_selector.RPL;
CPL = CS.RPL;
CALLGATE_DESCRIPTOR call_gate_descriptor;
CODESEG_DESCRIPTOR target_cs_descritpor;
call_gate_si = gate_selector.SI;
call_gate_ti = gate_selector.TI;
if (call_gate_ti == 0) {
call_gate_descriptor = GDTR.base + call_gate_si * 8;
} else {
call_gate_descriptor = LDTR.base + call_gate_si * 8;
}
target_cs = call_gate_descriptor.selector;
target_cs_si = target_cs.SI;
target_cs_ti = target_cs.TI;
if (target_cs_ti == 0)
target_cs_descriptor = GDTR.base + target_cs_si * 8;
else
target_cs_descriptor = LDTR.base + target_cs_si * 8;
DPLg = call_gate_descriptor.DPL;
DPLs = target_cs_descriptor.DPL;
if (CPL <= DPLg && RPL <= DPLg) {
if (target_cs_descriptor.type & 0x06) {
if (CPL >= DPLs) {
} else {
}
} else if (CPL == DPLs) {
} else {
goto DO_GP_EXCEPTION;
}
} else {
goto DO_GP_EXCEPTION;
}
current_CS = target_cs;
current_CS.RPL = DPLs;
current_EIP = call_gate_descriptor.offset;
goto target_cs_descriptor.base + call_gate_descriptor.offset;
return;
DO_GP_EXCEPTION:
三、 使用中斷門或陷井門進行轉移
中斷門符及陷井門必須存放在IDT 中,IDT 表也可以存放call gate。
1、 中斷調用時的權限檢查
用中斷門符進行轉移時,所作的權限檢查同call gate 相同,差別在于intterrupt gate 轉
移不需要檢查RPL,因為,沒有RPL 需要檢查。
★ 必須有足夠的權限通路門符,CPL <= DPLg
★ 向同級權限代碼轉移時,CPL == DPLs,向高權限代碼轉移時,CPL > DPLs
總結
if (CPL <= DPLg) {
if (CPL >= DPLs) {
} else {
}
} else {
}
2、 控制權的轉移
發生異常或中斷調用時
★ 用中斷向量在中斷描述符表查找描述符:中斷向量×8,然後加上IDT 表基址得出描述
符表。
★ 從查找到的描述符中得到目标代碼段選擇子,并在相應的GDT 或LDT 中擷取目标代碼
段描述符。
★ 目标代碼段描述符的基址加上門符中的offset,确定最終執行入口點。
中斷或陷井門符轉移的總結:
例: int 0x80 指令發生的情況
vector = 0x80;
INTGATE_DESCRIPTOR gate_descriptor = IDTR.base + vector * 8;
CODESEG_DESCRIPTOR target_descriptor;
TSS tss = TR.base;
DPLg = gate_descriptor.DPL;
target_cs = gate_descriptor.selector;
if (CPL <= DPLg) {
if (target_cs.TI == 0) {
target_descriptor = GDTR.base + target_cs.SI * 8;
} else {
target_descriptor = LDTR.base + target_cs.SI * 8;
}
DPLs = target_descriptor.DPL;
if (CPL > DPLs) {
switch (DPLs) {
case 0 :
SS = tss.ss0;
ESP = tss.esp0;
break;
case 1:
SS = tss.ss1;
ESP = tss.esp1;
break;
case 2:
SS = tss.ss2;
ESP = tss.esp2;
break;
}
*–esp = SS;
*–esp = ESP;
} else if (CPL == DPLs) {
} else {
}
*–esp = EFLAGS;
EFLAGS.TF = 0;
EFLAGS.NT = 0;
EFLAGS.RF = 0;
EFLAGS.VM = 0;
if (gate_descriptor.type == I_GATE32) {
EFLAGS.IF = 0;
}
*–esp = CS;
*–esp = EIP;
CS = target_selector;
CS.RPL = DPLs;
EIP = gate_descriptor.offset;
goto target_descritptor.base + gate_descriptor.offset;
} else {
}
堆棧的切換
控制權發生轉移後,處理器自動進行相應的堆棧切換。
1、 當轉向到同權限級别的代碼時,不會進行堆棧級别的調整,也就是不進行堆棧切換。
2、 當轉向高權限級别時,将發生相應級别的堆棧切換。從TSS 塊擷取相應級别的stack
結構。
例:假如目前運作級别CPL 為2 時,發生了向0 級代碼轉移時:
TSS tss = TR.base;
CPL = 2;
DPL = 0;
switch (DPL) {
case 0:
SS = tss.ss0;
ESP = tss.esp0;
break;
case 1:
SS = tss.ss1;
ESP = tss.esp1;
break;
case 2:
SS = tss.ss2;
ESP = tss.esp2;
break;
}
*–esp = SS;
*–esp = ESP;
*–esp = CS;
*–esp = EIP;
控制權的傳回
當目标代碼執行完畢,需要傳回控制權給原代碼時,将産生傳回控制權行為。傳回控制權行
為,比轉移控制權行為簡單得多。因為,一切條件已經在交出控制權之前準備完畢,傳回時
僅需出棧就行了。
1、 near call 的傳回
近調用情況下,段不改變,即CS 不改變,權限級别不改變。僅需從棧中pop 傳回位址就
可以了。
2、 直接控制權轉移的傳回(far call 或far jmp)
直接控制權的轉移是一種不改變目前運作級别的行為。隻是發生跨段的轉移。這時,CS
被從棧中pop 出來的CS 值加載進去,處理器會檢查CPL 與這個pop 出來的選擇子中的
RPL 進行檢查,相符則傳回。不相符則發生 #GP 異常。
總結:假如目前運作的目标代碼執行完畢後,将要傳回。這時CPL 為2
CPL = 2;
⋯ ⋯
EIP = *esp++;
CS = *esp++;
if (CPL == CS.RPL) {
return ;
} else {
}
3、 利用各種門符進行向高權限代碼轉移後的傳回
從高權限代碼傳回低權限代碼,須從stack 中pop 出原來的stack 結構。這個stack 結構
屬于低權限代碼的stack 結構。然後直接pop 出原傳回位址就可以了。
總結:
CPL = 0;
⋯ ⋯
EIP = *esp++;
CS = *esp++;
ESP = *esp ++;
SS = *esp++;
return ;