特权机制
特权
特权级数值越大,级别越小。
- 通常,因为操作系统是为所有程序服务的,可靠性最高,而且必须对软硬件有完全的控制权,所以它的主体部分必须拥有特权级0,并处于整个环形结构的中心。也正是因为这样,操作系统的主体部分通常又被称做内核(Kernel、 Core)。
- 特权级1和2通常赋予那些可靠性不如内核的系统服务程序,比较典型的就是设备驱动程序。当然,在很多比较流行的操作系统中,驱动程序与内核的特权级别相同,都是0。
- 应用程序的可靠性被视为是最低的,而且通常不需要直接访问硬件和一些敏感的系统资源,调用设备驱动程序或者操作系统例程就能完成绝大多数工作,故赋予它们最低的特权级别3。
大多数现代x86内核只使用两个特权级别:0和3。
特权转移 与 TSS
x86 特权转移规则如下:
- 低特权向高特权转移:使用中断门,调用门结合 TSS 完成转移。
- 高特权向低特权转移:只有在低特权代码中调用返回才能回到高特权。
一段在处理器运行的程序 (“任务”)的不同特权代码部分都需要独立的栈。当完成特权的转移时,CPU将完成上下文与栈的切换。一个特权级对应一个独立栈。一个任务最多有 4 个栈。
TSS 用于维护一个任务的栈与上下文信息。CPU 将根据 TSS 完成上下文与栈的切换。
由于除了调用返回以外,高特权代码无法向低特权代码转移(只有在低特权代码转移到高特权,由高特权返回才能回到低特权),而调用返回本身就维护了低特权代码栈指针,这意味着任务只需要维护比自身代码特权级更高的 TSS 信息,因为它已经无法向更低特权转移。一特权级为 2 的程序只能通过 TSS 向更高的 1,0 转移,因此该程序的 TSS 只需要维护 TSS 内 0, 1 特权的栈指针。 TSS 本身只维护 3 个栈的指针,因为特权3已经无法向更低的特权转移。
CPU 不会主动更新 TSS。如果在高特权代码中使用了栈,CPU 并不会对 TSS 中相应的值进行更新。
无论是否使用门,jmp 都只允许平级转移,因为 jmp 本身不支持调用返回。
特权描述符
DPL
DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。
RPL
RPL 位于段选择子中,说明的是进程对段访问的请求权限(Request Privilege Level),每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限。
CPL
位于当前代码段寄存器 CS 中的 RPL 被称为 CPL,是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级。
特权描述符如何保证访问是安全的?
一般情况下,用户进程由操作系统创建,此时 CPL 已被设置为 3。用户程序只能使用操作系统提供的选择子,
- 用户程序试图伪造选择子:由于 CPL 没有被改变,因此访问被拒绝。
- 用户程序试图利用门和伪造选择子:在调用门时,操作系统内核负责将用户提交的选择子的 RPL 替换为用户进程 CPL,在委托门执行高权限代码时,RPL 实际上反映的真正资源请求者的权限级别。尽管 CPL=0, 但是 RPL 仍然为 3,因此访问被拒绝。
- 用户试图利用一致代码段执行非法操作:一致代码段的 CPL 不会被改变,因此访问被拒绝。
特权级检查规则
当进程访问一个段时,需要进程特权级检查,具体而言:
- 对于 数据段、调用门、TSS DPL 规定了访问所需的最低特权。也即特权等级必须高于或等于该 DPL 才能访问。此时 CPU 将会取 CPL,RPL 的最大值(最低特权)来比较
- 对于 非一致代码段 CPL 特权等级必须严格符合 DPL,RPL 必须小于等于 DPL。
- 对于 一致代码段或通过调用门访问的非一致代码段 DPL 规定了访问所需的最高特权。也即 CPL 必须大于或等于该 DPL 才能访问,RPL 则不做要求。
调用门 (Call Gate)
调用门将控制从较低权限代码转移到与当前特权级相同或较高权限的代码段,调用 Call Gate 指向的函数,只需要使用调用门选择子作为 CALL FAR / JMP FAR 的操作数。
“门”是一段特殊的代码段,必须构造 门的目标段,门的选择子,门的描述符。
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4dw (%2 & 0FFFFh) ; 偏移1dw %1 ; 选择子dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性dw ((%2 >> 16) & 0FFFFh) ; 偏移2
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
调用门具有以下特权规则:
调用门存在的意义在于:无论代码目标段是否为一致代码段,都可以利用 call [调用门选择子] 间接跳转到目标代码段完成从低特权级到高特权级别的转移