linux操作系统CPL、DPL、RPL说明

linux操作系统中特权级有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)
表示门或者段的特权级,存储在门(中断描述符IDT)或者段的描述符(GDT)的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会起决定性作用。

二、对代码段访问的特权级控制(代码执行权的特权转移):

让我们先来记一些“定律”
所有的程序转跳,CPU 都不会把段选择子的RPL赋给转跳后程序的CS.RPL. .

转跳后程序的CPL(CS.RPL)只会有下面的俩种可能
转跳后程序的CPL(CS.RPL) = 转跳前程序的CPL(CS.RPL) 

转跳后程序的CPL(CS.RPL) = 转跳后程序的CodeDescriptor.DPL

以 Call 为例(只能跳到等于当前特权级或比当前特权级更高的段):
怎样决定这两种选择,这就要首先知道转跳后程序的段是一致代码段还是非一致代码段.其实也很简单,规则如下:
如果能成功转跳到一致代码段, 转跳后程序的CPL(CS.RPL) = 转跳前程序的CPL(CS.RPL),(转跳后程序的CPL继承了转跳前程序的CPL)
如果能成功转跳到非一致代码段, 转跳后程序的CPL(CS.RPL) =转跳后程序的Descriptor.DPL。(转跳后程序的CPL变成了该代码段的特权级.我在前面提到DPL是 段描述符中的特权级, 它的本意是用来代表它所描述的段的特权级)怎样才能成功转跳啦?

这里有四个重要的概念

1).段的保护观念是高特权级不找低特权级办事,低特权级找高特权级帮忙,相同的一定没问题.(这样想逻辑是没错,事实对不对就不知道.)也就是县长不找 乡长,乡长不求农民,反过来农民求乡长,乡长找县长.这个概念是最重要的。
2) 一致代码段的意义: 让客人很方便的利用主人(一致代码段)的东西为自己办事.但客人这身份没有改变NewCS.RPL=OldCS.RPL所以只能帮自己办事。比方说乡长有 一头牛,农民可以借来帮自己种田,但不能种别人的田.但是如果你是乡长当然可以种乡里所有的田。
3) 非一致代码段的意义:主人(非一致代码段)可以帮客人但一定是用自己的身份NewCS.RPL= DestinationDescriptorCode.DPL这里可能有安全的问题, 搞不好很容易农民变县长。主人太顽固了一定要坚持自己的身份,有什么方法变通一下,来个妥协好不好。好的,它就是RPL的用处。
4) RPL: 它让程序有需要的时候可以表示一个特权级更低的身份Max(RPL,CPL)而不会失去本身的特权级CPL(CS.RPL),有需要的时候是指要检查身份 的时候。事实上RPL跟段本身的特权级DPL和当前特权级CPL没有什么关系,因为RPL的值在成功转跳后并不赋给转跳后的CS.RPL。
还是要问怎样才能成功转跳啦?这里分两种情况:

普通转跳(没有经过Gate 这东西):即JMP或Call后跟着48位全指针(16位段选择子+32位地址偏移),且其中的段选择子指向代码段描述符,这样的跳转称为直接(普通)跳 转。普通跳转不能使特权级发生跃迁,即不会引起CPL的变化,看下面的详细描述:

目标是一致代码段:
要求:CPL(CS.RPL)>=DestinationDescriptorCode.DPL ,其他RPL是不检查的。
转跳后程序的CPL(NewCS.RPL) = 转跳前程序的CPL( OldCS.RPL)
上面的安排就是概念1,2的意思,此时,CPL没有发生变化,纵使它执行了特权级(DPL)较高的代码。若访问时不满足要求,则发生异常。
目标是非一致代码段:
要求:CPL(CS.RPL)=DestinationDescriptorCode.DPL AND RPL≤CPL(CS.RPL)
转跳后程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
上面的安排就是概念3的意思和部分1的意思----主人(非一致代码段)只帮相同特权级的客人做事。因为前提是CPL=DPL,所以转跳后程序的 CPL(NewCS.RPL) = DestinationDescriptorCode.DPL不会改变CPL的值,特权级(CPL)也没有发生变化。如果访问时不满足前提 CPL=DPL,则引发异常。


通过调用门的跳转:当段间转移指令JMP和段间转移指令CALL后跟着的目标段选择子指向一个调用门描述符时,该跳转就是利用调用门的跳转。这时如果选择子后跟着32位的地址偏移,也不会被cpu使用,因为调用门描述符已经记录了目标代码的偏移。使用调门进行的跳转比普通跳转多一个步骤,即在访问调用门描述符时要将描述符当作一个数据段来检查访问权限,要求指示调用门的选择子的RPL≤门描述符DPL,同时当前代码段CPL≤门描述符DPL,就如同访问数据段一样,要求访问数据段的程序的CPL≤待访问的数据段的DPL,同时选择子的RPL≤待访问的数据段或堆栈段的DPL。只有满足了以上条件,CPU才会进一步从调用门描述符中读取目标代码段的选择子和地址偏移,进行下一步的操作。
从调用门中读取到目标代码的段选择子和地址偏移后,我们当前掌握的信息又回到了先前,和普通跳转站在了同一条起跑线上(普通跳转一开始就得到了目标代码的 段选择子和地址偏移),有所不同的是,此时,CPU会将读到的目标代码段选择子中的RPL清0,即忽略了调用门中代码段选择子的RPL的作用。完成这一步后,CPU开始对当前程序的CPL,目标代码段选择子的RPL(事实上它被清0后总能满足要求)以及由目标代码选择子指示的目标代码段描述符中的DPL进行特权级检查,并根据情况进行跳转,具体情况如下:


目标是一致代码段:
要求:CPL(CS.RPL)≥DestinationDescriptorCode.DPL ,RPL不检查,因为RPL被清0,所以事实上永远满足RPL≤DPL,这一点与普通跳转一致,适用于JMP和CALL。
转跳后程序的CPL(NewCS.RPL) = 转跳前程序的CPL( OldCS.RPL),因此特权级没有发生跃迁。

目标是非一致代码段:
当用JMP指令跳转时:
要求:CPL(CS.RPL)=DestinationDescriptorCode.DPL AND RPL<= CPL(CS.RPL)(事实上因为RPL被清0,所以RPL≤CPL总能满足,因此RPL与CPL的关系在此不检查)。若不满足要求则程序引起异常。
转跳后程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
因为前提是CPL=DPL,所以转跳后程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL不会改变CPL的值,特权级也没有发生变化。如果访问时不满足前提CPL=DPL,则引发异常。
当用CALL指令跳转时:
要求:CPL(CS.RPL)≥DestinationDescriptorCode.DPL(RPL被清0,不检查),若不满足要求则程序引起异常。
转跳后程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
当条件CPL=DPL时,程序跳转后CPL=DPL,特权级不发生跃迁;当CPL>DPL时,程序跳转后 CPL=DPL,特权级发生跃迁,这是我们当目前位置唯一见到的使程序当前执行忧先级(CPL)发生变化的跳转方法,即用CALL指令+调用门方式跳转, 且目标代码段是非一致代码段。


总结:以上介绍了两种情况的跳转,分别是普通跳转和使用调用门的跳转,其中又可细分为JMP跳转和CALL跳转,跳转成功已否是由CPL,RPL和DPL 综合决定的。所有跳转都是从低特权级代码向同级或更高特权级(DPL)跳转,但保持当前执行特权级(CPL)不变,这里有点难于区别为什么说向高特权级跳转,又说特权级没变,这里“高特权级”是指目标代码段描述符的DPL,它规定了可以跳转到该段代码的最高特权级;而后面的CPL不变才真正说明了特权级未发生跃迁。我们可以看到,只有用CALL指令+调用门方式跳转,且目标代码段是非一致代码段时,才会引起CPL的变化,即引起代码执行特权级的跃迁,这是目前得知的改变执行特权级的唯一办法,如果各位读者还知道其他方法请留言告诉我。

 

 

---------------------------------------------------

一致代码段,非一致代码段

一致位:在描述符属性中TYPE(包含4个二进制位)字段的第2位。
当 S=1 时TYPE中的4个二进制位情况:
     3       2       1       0
   执行位 一致位 读写位 访问位
执行位:置1时表示可执行,置0时表示不可执行;
一致位:置1时表示一致码段,置0时表示非一致码段;
读写位:置1时表示可读可写,置0时表示只读;
访问位:置1时表示已访问,置0时表示未访问。

所以一致代码段和非一致代码段的意思就是指这个一致位是否置1,置1就是一致代码段,置0就为非一致代码段 。


你可能感兴趣的:(linux操作系统CPL、DPL、RPL说明)