保护模式中最重要的一个思想就是通过分级把代码隔离了起来,不同的代码在不同的级别 ,使大多数情况下都只和同级代码发生关系。 Intel的80286以上的cpu可以识別4个特权级(或特权层),0级到3级。数值越大特权越小。一般用把系统内核放在0级,系统的其他服务程序位于1、2级,3级则是应用软件。一般情况下代码都在自己的级别下做自己的工作,同一级别之间可以相互访问,而一般是不允许不同级别的代码间随意访问的。但有时候不同级别的程序之间一定要访问,比如系统的接口函数等,必须能够使得应用程序能够随意调用。于是Intel将代码分为:
1.非一致码:受到隔离的代码,只能在同一级别间相互访问
2.一致码:不受到隔离的就是,允许被同等级或低等级代码调用
至于这部分我们下回再详细说,这里主要搞清楚RPL、DPL、CPL之间的关系。
Intel设置DPL、RPL、CPL以实现分级和权限检查。
DPL:描述符特权(Descriptor Privilege Level)
存储在描述符中的权限位,用于描述代码的所属的特权等级,也就是代码本身真正的特权级。一个程序可以使用多个段(Data,Code,Stack)也可以只用一个code段等。正常的情况下,当程序的环境建立好后,段描述符都不需要改变——当然DPL也不需要改变,因此每个段的DPL值是固定。
RPL:请求特权级RPL(Request Privilege Level)
RPL保存在选择子的最低两位。 RPL说明的是进程对段访问的请求权限,意思是当前进程想要的请求权限。RPL的值由程序员自己来自由的设置,并不一定RPL>=CPL,但是当RPL<CPL时,实际起作用的就是CPL了,因为访问时的特权检查是判断:EPL=max(RPL,CPL)<=DPL是否成立,所以RPL可以看成是每次访问时的附加限制,RPL=0时附加限制最小,RPL=3时附加限制最大。所以你不要想通过来随便设置一个rpl来访问一个比cpl更内层的段。
因为你不可能得到比自己更高的权限,你申请的权限一定要比你实际权限低才能通过CPU的审查,才能对你放行。所以实际上RPL的作用是程序员可以把自己的程序降级运行——有些时候为了更好的安全性,程序可以在适当的时机把自身降低权限(RPL设成更大的值)。
网上许多人都说在问rpl的作用,我也很晕。Intel的手册中对RPL的作用只是这样做的简短解释的:
TheRPL can be used to insure that privileged code does not access asegment on behalf of an application program unless the program itselfhas access privileges for that segment.
后来找到了一些资料对这段话进行了扩充和举例,我才明白一些:
对于特权级高的进程RPL是作用是防止自己不小心访问到一些资料段。比方说,如果进程A的CPL=0,它知道它的委托进程B的DPL=3,也知道数据段C的DPL=2,而这数据段是不能让CPL>2的进程访问的。
那么如果你是进程A的程序员根本不需要RPL的帮助,也不会试图让进程A访问数据段C的数据,因为这样做只会浪费时间。当然如果你一定要访问数据段C的数据然后把数据传给委托进程B,这就是你的选择,你真的可以这样做,但后果自负。只是有时候要访问的数据段我们不知道它的DPL是怎么,也不知道能不能让进程B访问,其中的一个解决方法就是把委托进程B的DPL以RPL的方法告诉数据段C让它决定接受或不接受。(我想应该是通过程序把B的DPL装入到A的选择子中,然后再由A去访问数据段C)
CPL:当前任务特权(Current Privilege Level)
表示当前正在执行的代码所处的特权级。CPL保存在CS中的最低两位,是针对CS而言的。当选择子成功装入CS寄存器后,相应的选择子中的RPL就变成了CPL。因为它的位置变了,已经被装入到CS寄存器中了,所表达的意思也发生了变——原来的要求等级已经得到了满足,就是当前自己的等级。
选择子可以有许多个,因此RPL也就有许多个。而CPL就不同了,正在执行的代码在某一时刻就只有这个值唯一的代表程序的CPL.
另外特别要求CS与SS的特权级必须保持一致。对于装入DS、ES、FS、GS的选择子INTEL没有给它们起什么特殊的名称,我也不知道应该叫它们什么,也许可以仍然称它为RPL。
应用RPL实现数据段的特权级保护的实例
计算机软件一般由操作系统程序和应用程序组成。为了组建高可靠性的软件系统,要求操作系统的数据不允许被应用程序改变,否则,应用程序会影响系统的安全运行,严重时导致系统崩溃瓦解。一个由操作系统程序与应用程序组成的软件如图1所示。在操作系统程序中有一MOVE过程把一个数据段中的数据块写到另一个数据段中,MOVE过程是通过参数传入被调用的,即 MOVE(destination,source,count)。其中:source源数据块的逻辑地址;destination 目标数据块的逻辑地址;count 传送的字节数
数据块的逻辑地址由数据段选择字对应的数据段描述符和数据块偏移量所描述。
MOVE过程是操作系统的程序,它可以合法地被操作系统中的其它过程调用,完成操作系统数据段中的数据块移动和操作系统数据段与应用程序数据段中的数据块移动。但是,若MOVE过程被应用程序调用,操作系统数据段的数据有可能失去保护作用。设应用程序的典型调用情形为:
①MOVE(数据块A,数据块1,count)
②MOVE(数据块B,数据块A,count)
③MOVE(数据块2,数据块1,count)
④MOVE(数据块1,数据块A,count)
情形①:应用程序通过MOVE过程得到操作系统数据块的信息;
情形②:应用程序利用MOVE过程完成应用程序数据块之间的数据移动;
①、②这二种情形是合法的要求。
情形③:应用程序控制了操作系统的工作,没有按操作系统的工作步骤,强行改变了数据块2的内容;
情形④:应用程序将应用程序数据块A的内容移动到操作系统数据块1中,自行改变了数据块1的内容;
在③、④这二种情形使操作系统的数据得不到核心保护,操作系统的数据受到了应用程序的侵犯。
针对上述的侵权示例,如何防止应用程序改变操作系统的数据,即情形①和②;并且,应用程序能通过调用操作系统程序中的MOVE过程合法地使用,即情形③和④;这是操作系统软件设计中必须考虑的问题。
设调用MOVE过程的程序段Proc为:
……
LDS EAX,source ;DS:EAX=source源数据块的逻辑地址
PUSH DS ;压入source源数据块的选择字
PUSH EAX ;压入source源数据块的偏移量
LDS EAX,destination ;DS:EAX=destination目标数据块的逻辑地址
PUSH DS ;压入destination目标数据块的选择字
PUSH EAX ;压入destination目标数据块的偏移量
MOV ECX,count ;ECX=传送的字节数count
PUSH ECX ;压入传送的字节数count
CALL MOVE ;调用MOVE过程,MOVE由调用门描述
……
如果Proc在操作系统程序中,无论source源数据块和destination目标数据块属于操作系统或应用程序,都能合法的运作。
如果Proc在应用程序中,有可能产生情形③和④。例如,应用程序传入的目标数据块选择字的RPL=0,就意味着应用程序要对操作系统的数据段进行非法写入。为此,必须在MOVE过程中加以判断,避免情形③和④的产生。
在MOVE过程中采用的处理方法是:目标数据块选择子的RPL应以主调程序的CPL为准。即采用ARPL指令改目标数据块选择子的RPL为主调程序的CPL(关于ARPL指令的使用说明见最后)。依据应用程序调用MOVE过程的堆栈,如图2所示,在MOVE过程始部(开始)加入的指令为:
MOV AX,SS:[ESP+4] ;AX=CS主调程序的代码段选择字
;其RPL字段为主调程序的CPL
ARPL SS:[ESP+10],AX ;改目标数据块选择子的RPL为主调程序的CPL
MOV ES,SS:[ESP+10] ;ES=目标数据块选择子(其RPL字段为主调程序 ;的CPL)
……
进行了上述处理以后,如果应用程序传入的目标数据块选择字的RPL=0,ARPL把它修改为RPL=3,再传送到ES段寄存器中,显然不能满足选择字的装载要求,即MAX(CPL,RPL)≤DPL,其中,MOVE过程的CPL=0;目标数据块选择字对应的数据段描述符中的DPL=0。CPU内部立即产生通用保护异常中断进行处理。处理的结果通常为:终止当前的MOVE过程操作,返回应用程序;或终止应用程序的运行,返回操作系统。从而达到了保护目的。而情形①、②,应用程序完全可以合法实现。
[关于ARPL指令的使用说明]
调整申请特权级指令: ARPL OPRD1,OPRD2
其中,操作数OPRD1可以是16位通用寄存器或存储单元,操作数OPRD2是16位通用寄存器。该指令把操作数OPRD1和OPRD2视为两个选择子,用OPRD2的RPL去检查OPRD1的RPL。(选择子OPRD1和OPRD2的RPL分别由它们的最低2个位规定。)
如果OPRD1的RPL值小于OPRD2的RPL值(OPRD1的特权级高于OPRD2),那么零标志ZF被置1,并把OPRD2的RPL值赋予OPRD1的RPL(使两个操作数的最低2位相等);否则,零标志ZF被清0。OPRD1和OPRD2都可为空选择子。该指令只影响ZF标志。