兰陵月 发表于 2017-12-9 21:05:17

X86汇编语言-从实模式到保护模式—笔记(38)-第14章 任务和特权级保护(4)

本帖最后由 兰陵月 于 2017-12-15 12:21 编辑

代码段的特权级检查是很严格的。一般来说,控制转移只允许发生在两个特权级相同的代码段之间。如果当前特权级为2,那么,它可以转移到另一个DPL为2的代码段接着执行,但不允许转移到DPL为0、1和3的代码段执行。为了让特权级低的应用程序可以调用特权级高的操作系统例程,处理器也提供了相应的解决办法。
第一种方法:将高特权级的代码段定义为依从的。先复习一下描述符的结构,如下图图14-005,位11~8为描述符的TYPE字段:
再回忆一下TYPE字段的各个位代表的意义。下图图14-006为代码段和数据段描述符的TYPE字段:
如上图,当代码段描述符的TYPE字段有C位(也就是当X=1时),如果C=0,这样的代码段只能供同特权级的的程序使用;如果C=1,则这样的代码段称为依从的代码段,可以从特权级比它低的程序调用并进入。但是,即使是将控制转移到依从的代码段,也是有条件的,要求当前特权级CPL必须低于或者等于目标代码段描述符的DPL。即,在数值上,CPL≥目标代码段描述符的DPL。举例来说,如果一个依从的代码段,其描述符的DPL为1,则只有特权级别为1、2、3的程序可以调用,而特权级为0的程序则不能。在任何时候,都不允许将控制从较高的特权级转移到较低的特权级。依从的代码段不是在它的DPL特权级上运行,而是在调用程序的特权级上运行。就是说,当控制转移到依从的代码段上执行时,不改变当前特权级CPL,段寄存器CS的CPL字段不发生变化,被调用过程的特权级依从于调用者的特权级,这就是为什么它被称为“依从的”代码段。第二种方法:使用门。门(Gate)是另一种形式的描述符,称为门描述符,简称门。和段描述符不同,段描述符用于描述内存段,门描述符则用于描述可执行的代码,比如一段程序、一个过程(例程)或者一个任务。
门的类型,根据用途不同,分为调用门、中断门/陷阱门、任务门。调用门:用于不同特权级之间的过程调用;中断门/陷阱门:用于中断处理过程使用;任务门:对应单个任务,用来执行任务切换。所有描述符都是64位的,调用门描述符也不例外。在调用门描述符中,定义了目标过程(例程)所在代码段的选择子,以及段内偏移。要想通过调用门进行控制转移,可以使用jmp far或者call far指令,并把调用门描述符的选择子作为操作数。jmp far指令,可以将控制通过门转移到比当前特权级高的代码段,但不改变当前特权级别。call far指令,也可以将控制通过门转移到比当前特权级高的代码段,但当前特权级会提升到目标代码段的特权级别。也就是说,处理器是在目标代码的特权级上运行的。但是,除了从高特权级别的例程(通常是操作系统例程)返回外,不允许从特权级高的代码段将控制转移到特权级低的代码段(是代码段转移到代码段,高级别代码段访问低级别数据段是允许的),因为操作系统不会引用可靠性比自己低的代码。
描述符属性字段:0x00CF9200;
其二进制表示为:0000 0000 1100 1111 1001 0010 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x00409800;
其二进制表示为:0000 0000 0100 0000 1001 1000 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x00CF9600;
其二进制表示为:0000 0000 1100 1111 1001 0110 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x0040920B;
其二进制表示为:0000 0000 0100 0000 1001 0010 0000 1011;
其位14、13位为DPL位,值为“00”
上面这个4个段的特权级都是最高级别,0特权级。特权级保护机制只在保护模式下才能启用,而进入保护模式的方法是设置CR0寄存器的PE位。而且,处理器建议,在进入保护模式后,执行的第一条指令应当是跳转或者过程调用指令,以清空流水线和乱序执行的结果,并串行化处理器,就像下面的指令:jmp dword 0x0010:flush转移到的目标代码是刚刚定义的,描述符特权级DPL为0。要将控制转移到这样的代码段,当前特权级必须为0。不过,这并不是问题。进入保护模式之后,处理器自动当前特权级CPL设定为0,以0特权级的身份开始执行保护模式的初始指令。段选择子实际上由三部分组成,分别是描述符的索引号、表指示器TI和RPL字段。在以上指令中,段选择子0x0010的TI位是0,意味着目标代码段的描述符在GDT中。该选择子的索引字段的值是2,指向(GDT中的)2号描述符。GDT中的1号(该程序的1#描述符应该为一个数据段,而不是初始代码段,所以这里的“1号”应该改为“2号”)描述符是保护模式下的初始代码段描述符,特权级DPL为0,而当前特权级CPL也是0,从初始的0特权级转移到另一个0特权级的代码段,这是允许的。转移之后,jmp指令中的选择子0x0010被加载到段寄存器CS,其低两位采用目标代码段描述符DPL的值。也就是说,控制转移之后,当前特权级仍为0。选择子中的RPL字段,RPL的意思是请求特权级(Requested Privilege Level)。我们知道,要将控制从一个代码段转移到另一个代码段,通常是使用jmp和call指令,并在指令中提供目标代码段的选择子,以及段内偏移量(入口点)。而为了访问内存中的数据,也必须先将段选择子加载到段寄存器DS、ES、FS或者GS中。不管是实施控制转移,还是访问数据,这都可以看成是一个请求,请求者提供一个段选择子,请求访问指定的段。从这个意义上来说,RPL也是请求者的特权级别。在绝大多数时候,请求者都是当前程序自己,因此,CPL=RPL。要判断请求者是谁,最简单的方法就是看谁提供了选择子。以下是两个典型的例子:代码清单13-1中的第55行:jmp dword 0x0010:flush在这里,提供选择子0x0010(书中此处为“0x0008”,应该为笔误)的是当前程序自己。再比如同一代码清单中的第59、60行:mov eax,0x0008mov ds,eax非常清楚的是,这同样是当前程序自己拿着段选择子0x0008来“请求”代入段寄存器DS,以便在随后的指令中访问该段中的数据。但是,在一些并不多见的情况下,RPL和CPL并不相同。如下图图14-007所示,特权级为3的应用程序希望从硬盘读一个扇区,并传送到自己的数据段,因此,数据段描述符的DPL同样会是3。
由于I/O特权级的限制,应用程序无法自己访问硬盘。好在位于0特权级的操作系统提供了相应的例程,但必须通过调用门才能使用,因为特权级间的控制转移必须通过门。假设,通过调用门使用操作系统例程时,必须传入3个参数,分别是CX寄存器中的数据段选择子、EBX寄存器中的段内偏移,以及EAX中的逻辑扇区号。高特权级别的程序可以访问低特权级别的数据段,这是没有问题的。因此,操作系统例程会用传入的数据段选择子代入段寄存器,以便代替应用程序访问那个段。比如mov ds,cx,在执行这条指令时,CX寄存器中的段选择子,其RPL字段的值是3,当前特权级CPL已经变成0,因为通过调用门实施控制转移可以改变当前特权级。显然,请求者并非当前程序,而是特权级为3的应用程序,RPL和CPL并不相同。不过,上面的例子只是表明RPL有可能和CPL并不相同,但并没有说明引入RPL到底有什么必要性,它似乎是多余的,没有它,程序也能正常工作,但实际上情况并不是这样的,它是很有用的。
如上图图14-008所示,应用程序的编写者通过钻研,知道了操作系统数据段的选择子,而且希望用这个选择子访问操作系统的数据段。当然,他不可能在应用程序里访问操作系统数据段,因为那个数据段的DPL为0,而应用程序工作时的当前特权级为3,处理器会很机警地把来访者拒之门外。但是,他可以借助于调用门。调用门工作在目标代码段的特权级上。一旦处理器的执行流离开应用程序,通过调用门进入操作系统例程时,当前特权级从3变为0。当那个不怀好意的程序将一个指向操作系统数据段的选择子通过CX寄存器作为参数传入调用门时,因为当前特权级已经从变为0,可以从硬盘读出数据,并且允许向操作系统数据段写入扇区数据。处理器的智商很低,它不可能知道谁是真正的请求者。作为最聪明的灵长类动物,我们当然可以通过分析程序的行为来区分它们,但处理器不能。因此,当指令mov ds,ax或者mov ds,cx执行时,AX或CX寄存器中的选择子可能是操作系统自己提供的,也可能来自于恶意的用户程序,这两种情况要区别对待,但已经超出了处理器的能力和职权范围。引入请求特权级(RPL)的原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁。但是,引入RPL本身并不能完全解决这个问题,这只是处理器和操作系统之间的一种协议,处理器负责检查请求特权级RPL,判断它是否有权访问,但前提是提供了正确的RPL;内核或者操作系统负责鉴别请求者的身份,并有义务保证RPL的值和它的请求者身份相符,因为这是处理器无能为力的。操作系统的编写者很清楚段选择子的来源,即,真正的请求者是谁。当它自己读写一个段时,这没有什么好说的;当它提供一个服务例程时,3特权级别的用户程序给出的选择子在哪里,也是由它定的,它也知道。在这种情况下,它所要做的,就是将该选择子的RPL字段设置为请求者的特权级(可以使用arpl指令,将在本章的后面介绍)。剩下的工作就看处理器了。每当处理器执行一个将段选择子传送到段寄存器(DS、ES、FS、GS)的指令,比如mov ds,cx时,会检查以下两个条件是否都能满足:一、当前特权级CPL高于或者和数据段描述符的DPL相同。即,在数值上,CPL≤数据段描述符的DPL;二、请求特权级RPL高于或者和数据段描述符的DPL相同。即,在数值上,RPL≤数据段描述符的DPL。如果以上两个条件不能同时成立,处理器就会阻止这种操作,并引发异常中断。按照Intel公司的说法,引入RPL的意图是“确保特权代码不会代替应用程序访问一个段,除非应用程序自己拥有访问那个段的权限”。多数读者都只在字面上理解这句话的意思,而没有意识到,这句话只是如实地描述了处理器自己的工作,并没有保证它可以鉴别RPL的有效性。
总结一下基本的特权级检查规则:首先,将控制直接转移到非依从的代码段,要求当前特权级CPL和请求特权级RPL都等于目标代码段描述符的DPL。即,在数值上,CPL=目标代码段描述符的DPL;RPL=目标代码段描述符的DPL。一个典型的例子就是使用jmp指令进行控制转移,比如:jmp 0x0012:0x00002000因为两个代码段的特权级相同,故,转移后当前特权级不变。其次,要将控制直接转移到依从的代码段,要求当前特权级CPL和请求特权级RPL都低于或者和目标代码段描述符的DPL相同。即,在数值上,CPL≥目标代码段描述符的DPL;
RPL≥目标代码段描述符的DPL。控制转移后,当前特权级保持不变。第三,高特权级别的程序可以访问低特权级别的数据段,但低特权级别的程序不能访问高特权级别的数据段。访问数据段之前,肯定要对段寄存器DS、ES、FS和GS进行修改,比如mov fs,ax,在这个时候,要求当前特权级别CPL和请求特权级别RPL都必须高于或者和目标数据段描述符的DPL相同。即,在数值上,CPL≤目标数据段描述符的DPL;RPL≤目标数据段描述符的DPL。最后,处理器要求,在任何时候,栈段的特权级别必须和当前特权级CPL相同。因此,随着程序的执行,要对段寄存器SS的内容进行修改时,必须进行特权级检查。比如mov ss,ax,在对段寄存器SS进行修改时,要求当前特权级CPL和请求特权级RPL必须等于目标栈段描述符的DPL。即,在数值上,CPL=目标栈段描述符的DPL;RPL=目标栈段描述符的DPL。0特权级是最高的特权级别,当一个系统的各个部分都位于0特权级时,各种特权级检查总能够获得通过,就像这种检查和检验并不存在一样。所以,处理器的设计者建议,如果不需要使用特权机制的话,可以将所有程序的特权级别都设置为0,就像我们一直所做的那样。
小结:
一、程序员在写程序时,不需要指定特权级别。当程序运行时,操作系统将程序创建为任务局部空间的内容,并赋予较低特权级别,比如3,操作系统对应着任务全局空间的内容。如果有多个任务,则操作系统属于所有任务的公共部分。二、当任务运行在局部空间时,可以在各个段之间转移控制,并访问私有数据,因为它们具有相同的特权级别,但不允许直接将控制转移到高特权级别的全局空间的段,除非通过调用门,或者目标端是依从的代码段。三、当通过调用门进入全局空间执行时,操作系统可以在全局空间内的各个段之间转移控制并访问数据,因为它们也具有相同的特权级别。同时,操作系统还可以访问任务局部空间的数据,即低特权级别的数据段。但除了调用门返回外,不允许将控制转移到低特权级别的局部空间内的代码段。四、任何时候,当前栈的特权级别必须和CPL是一样的。进入不同特权级别的段执行时,要切换栈。

检测点14.1
1、B、A;
2、非依从的:CPL=RPL=目标代码段描述符的DPL
   依从的:CPL≥目标代码段描述符的DPL
         RPL≥目标代码段描述符的DPL
页: [1]
查看完整版本: X86汇编语言-从实模式到保护模式—笔记(38)-第14章 任务和特权级保护(4)