Orange'S:一个操作系统的实现

保护模式-特权级

Posted by BINBIN Blog on October 29, 2019

特权级

IA32 的分段机制中,总共有4个特权级别,从高到低分别是0、1、2、3。数字越小表示的特权级别越大!

核心的代码和数据,一般都放在特权级别较高的层次。处理器用这个样的机制来避免低特权级的任务在不被允许的情况下访问位于高特权级的段!如果处理器检测到一个访问请求时不合法的,将会产生常规保护错误(#GP)。

一般讲高特权级称作内层,把低特权级称作外层!

前面介绍的描述符里面有个DPL!即使其中一种特权级! 总结的特权级别共有三种:CPL RPL DPL!

CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。 通常情况下CPL 等于代码所在的段的特权级。当程序转移到不同特权级的代码段时,处理器将改变CPL。 遇到一致代码段时,情况稍稍有点特殊!一致代码段可以被相同或者更低特权级的代码访问。当处理器访问一个与CPL特权级不同的一致代码段时,CPL 不会被改变!

RPL说明的是进程对段访问的请求权限(Request Privilege Level),是对于段选择子而言的,每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定的,两次访问同一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样它对该段仍然只有特权为3的访问权限。 操作系统过程往往用RPL 来避免低特权级别应用程序访问高特权级段内的数据!

DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。 可以表示段或者门的特权级!

当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL,RPL}。下面打一个比方,中国官员分为6级1、国家2、总理3、省长4、市长5、县长6、乡长,假设我是当前进程,级别总理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省长的级别(RPL=3 这样也能吓死他们:-))去访问,可以吧,如果我用县长的级别,人家就不理咱了(你看看电视上的微服私访,呵呵),明白了吧!为什么采用RPL,是考虑到安全的问题,就好像你明明对一个文件用有写权限,为什么用只读打开它呢,还不是为了安全!

针对 DPL 介绍一下各种类型的段或者门的情况!

  • 数据段: DPL 规定了可以访问此段的最低特权级。比如,一个数据段的DPL 是1,那么只有运行在CPL 为0 或者 1 的程序才有权访问他。
  • 非一致代码 (不使用调用门的情况下): DPL 规定访问此段的特权级。 比如,一个非一致代码段的特权级为0.那么只有CPL 为0的程序才可以访问它。
  • 调用门 : DPL 规定了当前执行的程序或任务可以访问此调用门的最低特权级(这与数据段的规则是一致的)。
  • 一致代码段和通过调用门访问的非一致性代码段: DPL规定了访问此段的最高特权级。 比如,一个一致性代码段的DPL是2,那么CPL 为0和1的程序将无法访问此段。
  • TSS : DPL规定访问此TSS 的最低特权级(这与数据段的规则是一致的)。

为甚麽在CPL之外增加一个RPL Intel手册上的解释为:The RPL can be used to insure that privileged code does not access a segment on behalf of an application program unless the program itself has access privileges for that segment. (RPL能够用来确保具有特权级的代码不会代表另一个应用程序去访问一个段,除非那个应用程序具有访问那个段的权限.)

比方说:A进程的DPL为0,C进程的DPL为1,现在有一个B进程他的DPL为2,这B进程想委托A进程(外围程序可以访问一致代码段的内核)去访问C的数据(内核可以访问外围数据),如果没有RPL来限制的话,这样的委托访问是可以成功的,但这样是非常不安全的。 有了RPL以后,A进程在访问C的时候还要受到RPL的约束,此时可以将访问C的选择子的RPL设为B的DPL,这样A的访问权限就相当为EPL=max(RPL,DPL)=2,这样他就无法代表B去越权访问C了。(那还要委托A干嘛?反正B如果不够权限,委托谁都没用;如果B有权限,不用委托别人也可以啊?)

有RPL的情形,CPU同时检查CPL和RPL来判断是否允许对一个段的访问。在低特权级代码调用高特权级代码时,你可以把RPL认为是调用者CPL的影子。即使高特权级代码在运行,但它是应低特权级代码的请求,作为低特权级代码的代理在执行任务,在必要的时候,RPL作为对CPL的覆盖,可以削弱当前执行代码的可访问的区域,从而保证高特权级代码不会代表低特权级代码去访问一个后者没有访问权限的段。 这里有一个问题,就是低特权级代码在向高特权级代码传递段选择子时,可以任意设置RPL。所以x86处理器有一条专门的指令ARPL用来纠正RPL。

网上有个农民的例子讲的挺贴合实际!

一个农民(低特权级)请县长(高特权级)打听一种超级种子,如果找到的话帮忙拿一点回来,听闻这种超级种子可让收成倍增。县长说:好!我认识很多当官的,我可以帮你打听一下哪里有,但是有些地方如果需要表示身分的话我只能说我是农民的代理人。县长利用自己的身份很容易找到了种子在哪里—找的时候没有人问起他代表谁。县长问种子管理员可不可以给他一点,管理员说种子不能给农民因为种子还在试验阶段,我们可以给县长让他们带回当地的专家来帮忙一起做试验,但是一定要县长来申请。那你是谁?县长说我是农民的代理人,因为县长保证他会这样回答的(他也不知道那农民是不是专家),管理员当然不给。县长没办法只能告诉农民拿不到种子。这件事里面县长是以县长的身份帮农民找到种子,但需要表示身分的时候他说只是农民的代理人。这样做县长可以帮人但也不会给别人利用。(农民可能把种子拿回来卖钱也说不定,没人知道) 在这里RPL就是县长的另一个身份—农民的代理人也就是农民—他会带在身上,人家没有问他的时候他不会告诉别人,所以别人也就以县长的身分来看待他。当查身份的时候他才告诉你—我是农民的代理人。

PL和DPL都是见字面就能理解含义的。唯独RPL不容易理解。 今天就说说我的理解,将来回头来看看,今天说的到底对不对。 1、- 当前进程的意愿 - RPL代表了当前进程的意愿(因为是一种request)。注意,是当前进程的意愿而不是别的进程。其次是一种意愿,是否实现还有待CPU的许可。 2、- 不能超越自己的特权级 - 前面的帖子已经说了,RPL可以由当前进程随便写,但是CPU会检查RPL和CPL的取值,如果RPL填写的特权级比自己实际情况还高,CPU就不会认可,仍旧给他当前的权限。 3、- 只能下降权限 - 鉴于上面的原因,使用RPL的真正情况就是当前进程使用比自己低的或者相同的RPL。这种情况最典型的,就是外围程序A调用内核B,然后通过内核B再访问程序C。由于访问的来源是A,所以内核为了安全起见,将RPL(来自A的访问意愿)设置为A的DPL。这样在逻辑上就完整了。内核就不会越俎代庖了。

一个小实验

书上还举例的一个小实验!

特权级的检验,只要CPL 和 RPL 都小于被访问的数据段的DPL 就可以了!

修改一下前面的代码,将LABEL_DESC_DATA对应的描述符的DPL修改为1!

修改后:

LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW+DA_DPL1 ; Data

编译运行是可以得!

继续修改:

SelectorData equ LABEL_DESC_DATA - LABEL_GDT + SA_RPL3

再次编译运行!

结果,bochs重启,系统崩溃了,可以在控制台看到

load_seg_reg(DS): RPL & CPL must be <= DPL

意思是违反了特权级规则! 又没有相应的异常处理模块!