DPL
-
对于数据段来说,决定了访问它们所应当具备的最低特权级。
CPL <= DPL
假设 DPL 为 2,则只有特权级为 0、1、2 的程序才可访问它。
CPL
- 当前处理器正在执行的代码段,则该代码段的特权级称为当前特权级。
- 位置:选择子位于 CS 寄存器中,最低两位就是当前特权级的值。
特权指令
- 指令当前特权级 CPL 为 0 时才能执行的指令,称为特权级指令。
RPL - 请求特权级
无论是通过 jmp / call 指令,并且提供目标代码段的选择子,转移到其它代码段。还是访问内存数据,通过一些寄存器,且提供段选择子加载到段寄存器中。
这些都可以看做是一个请求,请求者提供一个段选择子,请求访问某个代码段,又或者是请求访问内存数据。
在大多数时候,请求者都是当前程序自己,因此 CPL = RPL,要判断请求者是谁,最简单的方法就是看谁提供的选择子。
[SECTION A] ; 书上没有这个,放在这只是为了方便理解
jmp dword 0x0010:flush ; 由当前 A 段提供的选择子,向 CS 请求 0x0010
[SECTION A]
mov eax, 0x0008 ; 这里并不触发 RPL,只是单纯把值传入 EAX
mov ds, eax ; 由当前 A 段提供的选择子,向 DS 请求 0x0008
当然,也有 CPL != RPL 的情况:
特权级为 3 的应用程序想要访问磁盘,只能通过调用特权级为 0 的例程来实现,但必须通过调用门,因为特权级间的控制转移必须通过门。假设必须传入三个参数,分别为 CX 中保存的数据段选择子、EBX 寄存器中保存的段内偏移,以及 EAX 中的逻辑扇区号。
高特权级别的程序可以访问低特权级别的数据段。 因此操作系统会使用传入的参数 CX 中的数据段选择子,将其代入段寄存器:
mov ds, cx
在执行这条执行时,CX 中的段选择子,其中 RPL 字段值为 3,当前特权级 CPL 变为 0,因为通过 call 调用门可以改变当前特权级。
而请求者并非当前程序(即特权级为 0 的例程),而是特权级为 3 的应用程序,RPL != CPL。
如图所示,假设应用程序已知操作系统数据段的选择子,希望通过这个选择子访问操作系统的数据段。这显然不可能,因为操作系统中的数据段 DPL 为 0,而应用程序的 CPL 为3,处理器会拒绝该访问。
但是,可以借助调用门。调用门工作在目标代码段的特权级上,一旦处理器的执行流离开应用程序,通过调用门进入操作系统例程是,当前特权级从 3 变为 0。当某个恶意程序将一个执行操作系统的数据段的选择子通过 CX 寄存器作为参数传入调用门时,因为当前特权级已经从 3 变为 0,因此可以从磁盘读写数据。
引入请求特权级(RPL) 的原因是处理器在遇到一条将选择子传送到寄存器的指令时,无法区分真正的请求者是谁。
就拿上面的图来说,应用程序通过调用门调用内核例程,并且传入 CX 内容为指向内核数据段的选择子,内核例程中有这么一条语句 mov ds, cx
。此时虽然我们可以人为分析出来,请求者是应用程序,但处理器并不知道请求者到底是应用程序还是内核例程。
引入 RPL 并不能解决这个问题,这只是处理器和操作系统之间的一种协议。
每当处理器执行一个将段选择子传送到段寄存器(DS、ES、FS、GS)的指令时,会检查以下两个条件是否都满足:
-
当前特权级 CPL 高于或和数据段描述符的 DPL 相同。
CPL <= 数据段描述符的 DPL
-
请求特权级 RPL 高于或和数据段描述符的 DPL 相同。
RPL <= 数据段描述符的 DPL
若以上两个条件不能同时成立,则处理器就会阻止这种操作,并引发异常。
引入 RPL 的意图是 “确保特权代码不会代替应用程序访问一个段,除非应用程序自己拥有那个段的权限”。
栈段
处理器要求,在任何时候,栈段的特权级别必须和当前特权级 CPL 相同。因此,要对段寄存器 SS 进行修改时,必须进行特权级检查。
mov ss, ax ; 修改 SS
在对段寄存器 SS 进行修改时,要求当前特权级 CPL 和请求特权级 RPL 必须等于目标栈段描述符的 DPL。即在数值上:
CPL = 目标栈段描述符的 DPL
RPL = 目标栈段描述符的 DPL
特权级转移
一般来说,控制转移只允许发生在两个特权级相同的代码段之间。
低特权返回高特权
第一种方法:将高特权级的代码段定义为依从的。
在段描述符中,有个 TYPE 字段,其中的 C 位,若 C = 0,则该代码段只可被相同的特权级所调用。若 C = 1,则该代码段为依从的代码段。
// 若要调用某个过程,则需要满足:
CPL >= 目标代码段描述符的 DPL
若要调用的目标过程的 DPL 为 1,则该代码段只能被特权级分别为 1、2、3 的代码段所调用。
第二种方式:通过调用门。
高特权转移到低特权
不允许直接从高转移到低,只能通过 ref 或 iret 才能实现从高特权转移到低特权级。
门
门的类型:
- 调用门:用于不同特权级之间的切换。
- 中断门 / 陷阱门:作为中断过程使用。
- 任务门:对应单个任务,用来执行任务切换。
通过调用门进行转移:jmp far 或 call far 指令,将调用门描述符的选择子作为操作数进行转移。
jmp far
:可以将控制通过门转移到当前特权级高的代码段,但不改变当前特权级。call far
:可以将控制通过门转移到当前特权级高的代码段,同时将当前特权级提升到目标代码段的特权级别。