一致/非一致代码段,CPL|DPL|RPL段特权级
2010-09-17 20:33
CPL 代表当前代码段的权限,如果它想要去访问一个段或门,首先要看看对方的权限如何,也就是检查对方的DPL,如果满足当前的权限比要访问的权限高,则有可能 允许去访问,有些情况我们还要检查选择子的权限,即RPL,因为我们通过选择子:偏移量的方式去访问一个段,这算是一个访问请求动作,因此称为请求访问权 限RPL(Requst Privilege Level)。当请求权限也满足条件,那么访问就被允许了。 一、一致代码段与非一致代码段 一致代码段,非一致代码段 ------------------------------------------------------------------------------------------ 一致位:在描述符属性中TYPE(包含4个二进制位)字段的第2位。 当 S=1 时TYPE中的4个二进制位情况: 3执行位 2一致位 1读写位 0访问位 执行位:置1时表示可执行,置0时表示不可执行; 一致位:置1时表示一致码段,置0时表示非一致码段; 读写位:置1时表示可读可写,置0时表示只读; 访问位:置1时表示已访问,置0时表示未访问。 所以一致代码段和非一致代码段的意思就是指这个一致位是否置1,置1就是一致代码段,置0就为非一致代码段。 “一致”的意思大约是这样,当转移的目标是一个特权级更高的一致代码段时,当前的特权级会被延续下去,而向特权级更高的非一致代码段的转移则会引起常规的 保护异常,除非使用调用门或者任务门。一致代码段往往是用在内核共享的段,这些段是允许应用程序去访问的,而不需要内核转移到应用程序中来访问这些共享的 资源。 对于一致代码段,有以下规则: 1.特权级高的程序不允许访问特权级低的数据。比如,核心态不允许调用用户态的数据。 2.特权级低的程序可以访问到特权级高的数据,但是访问程序的特权级不会发生变化。比如,用户态访问内核态共享的资源时,不会变成内核态。 而对于非一致代码段,则有着不同的规则: 1.只允许同级访问。 2.绝对禁止不同级程序访问。即内核态不能访问用户态,用户态也不能访问内核态。 之所以这么做是为了系统的安全性考虑,分离内核和用户程序,使内核不能被用户程序干涉,同时避免使用户态程序修改内核态的逻辑,导致在内核态下执行用户程 序的代码。上面所说的是代码段,而数据段则全都是非一致的,这意味着不可能被低特权级的代码访问到。然而,与代码段不同的是,数据段可以被更高特权级的代 码访问到,而不需要使用特定的门。规则总如如下: 1.一致代码段 特权级低->高:Yes| 特权级高->低:No | 特权级同级之间:Yes | 适用于何种代码:不访问受保护的资源和某些类型的异常处理的系统代码 2.非一致代码段 特权级低->高:No| 特权级高->低:No | 特权级同级之间:Yes | 适用于何种代码:避免低特权级的程序访问而被保护起来的系统代码 3.数据段(总是非一致) 特权级低->高:No| 特权级高->低:Yes | 特权级同级之间:Yes | 二、特权级 1.CPL、RPL和DPL CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs和ss寄存器的低2位(第0,1位)。CPL表示的是程序或者说任务的当前特权级,它不 属于某个段,当前的程序或任务不可能同时表现出2种特权级,那么CS和SS的第0位和第1位应该总是相同的。尝试将RPL异于CPL的数据段选择子装入 SS会引起异常。它们总是相同的。 CPL就是CS和SS的段选择子的RPL,CPL存放在CS和SS寄存器的RPL字段内,每当一个代码段/堆栈段(特殊的数据段)选择子装入CS/SS寄存器中时,处理器自动地把CPL存放到CS/SS的RPL字段。 RPL说明的是进程对段访问的请求权限(Request Privilege Level), 是对于段选择子而言的,每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定的,2次访问同 一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样它对该段仍然只 有特权为3的访问权限。处理器通过检查RPL和CPL来确认一下请求是否合法。即便提出访问请求的段有足够的特权级,如果RPL不够也是不行的。 (CPL <= DPL) && (RPL <= DPL) 操作系统过程往往用RPL来避免低特权级应用程序访问高特权级段内的数据。当操作系统过程(被调用过程)从一个应用程序(调用过程)接收到一个选择子时, 将会把选择子的RPL设成调用者的特权级。于是,当操作系统用这个选择子去访问相应的段时,处理器将会调用过程的特权级(已经被存到RPL中),而不是更 高的操作过程的特权级(CPL)进行特权检验。这样,RPL就保证了操作系统不会越俎代疱地代表一个应用程序去访问一个段,除非这个程序本身是有权限的。 (可以认为是以CPL来访问段DPL所出示的“证件(RPL)”,如出示的“证件”权级范围在CPL之内且满足DPL的特权检查规则:DPL >= max{CPL,RPL},就能正常通过DPL;反之则不会通过还会发生错误) DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}。当当前代码段试图访问一个段或者门时,DPL将会和CPL以及段或门选择子的RPL相比较,根据段或者门类型的不同,DPL将会被区别对待,下 面介绍一下各种类型的段或者门的情况: 1.数据段:DPL规定了可以访问此段的最低特权级。比如,一个数据段的DPL是1,那么只有运行在CPL为0或者1的程序才有权访问它。 2.非一致代码段(不使用调用门的情况下):DPL规定访问此段的特权级。比如,一个非一致代码的特权级为0,那么只有CPL为0的程序才可以访问它。 3.调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级(这与数据段的规定是一致的)。 4.一致代码段:DPL规定了访问此段的最高特权级。比如,一个一致代码段的DPL是2,那么CPL为0和1的程序将无法访问此段。 5.TSS:DPL规定了可以访问此TSS的最低特权级(这与数据段的规定是一致的)。 2.代码间跳转(段间) (1)普通转跳(没有使用调用门):即JMP或CALL后跟着48位全指针(16位段选择子+32位地址偏移),且其中的段选择子指向代码段描述符,这样的跳转称为直接(普通)跳转。普通跳转不能使特权级发生跃迁,即不会引起CPL的变化,看下面的详细描述: 一致代码段 要求:CPL >= DPL ,RPL不检查,也说是一致代码段描述符中的DPL规定可以转移到一致的代码段的最内层特权级(3级可以转移到0级,而0级只能转移到0级)。一致代码段 描述符内DPL的这种解释,正好与正常的DPL的解释相反。这是为了提供对应用程序的共享支持,而不要求改变特权级。 特权变化:转跳后程序的CPL = 转跳前程序的CPL 非一致代码段 要求:CPL = DPL & RPL<= DPL 特权变化:转跳后程序的CPL = 转跳前程序的CPL (2)通过调用门的跳转:当段间转移指令JMP和段间转移指令CALL后跟着的目标段选择子指向一个调用门描述符时, 该跳转就是利用调用门的跳转。这时如果选择子后跟着32位的地址偏移,也不会被cpu使用,因为调用门描述符已经记录了目标代码的偏移。使用调门进行的跳 转比普通跳转多一个步骤,即在访问调用门描述符时要将描述符当作一个数据段来检查访问权限,要求指示调用门的选择子的 RPL<=门描述符DPL,同时当前代码段CPL<=门描述符DPL,就如同访问数据段一样,要求访问数据段的程序的CPL<=待访问 的数据段的DPL,同时选择子的RPL<=待访问的数据段或堆栈段的DPL。只有满足了以上条件,CPU才会进一步从调用门描述符中读取目标代码段 的选择子和地址偏移,进行下一步的操作。 从调用门中读取到目标代码的段选择子和地址偏移后,我们当前掌握的信息又回到了先前,和普通跳转站在了同一条起跑线上(普通跳转一开始就得到了目标代码的 段选择子和地址偏移),有所不同的是,此时,CPU会将读到的目标代码段选择子中的RPL清0,即忽略了调用门中代码段选择子的RPL的作用。完成这一步 后,CPU开始对当前程序的CPL,目标代码段选择子的RPL(事实上它被清0后总能满足要求)以及由目标代码选择子指示的目标代码段描述符中的DPL进 行特权级检查,并根据情况进行跳转,具体情况如下: 一致代码段 要求:CPL >= DPL ,RPL不检查 特权变化:因为RPL被清0,所以事实上永远满足RPL <= DPL,这一点与普通跳转一致,适用于JMP和CALL。 转跳后程序的CPL = 转跳前程序的CPL,因此特权级没有发生跃迁。 非一致代码段(JMP) 要求:CPL = DPL (RPL被清0,不检查),若不满足要求则程序引起异常。 特权变化:转跳后程序的CPL = DPL,因为前提是CPL=DPL,所以转跳后程序的CPL = DPL不会改变CPL的值,特权级也没有发生变化。如果访问时不满足前提CPL=DPL,则引发异常。 非一致代码段(CALL) 要求:CPL >= DPL(RPL被清0,不检查),若不满足要求则程序引起异常。 特权变化:转跳后程序的CPL = DPL,当条件CPL=DPL时,程序跳转后CPL=DPL,特权级不发生跃迁;当CPL>DPL时,程序跳转后CPL=DPL,特权级发生跃迁,这是我 们当目前位置唯一见到的使程序当前执行优先级(CPL)发生变化的跳转方法,即用CALL指令+调用门方式跳转,且目标代码段是非一致代码段。 -------------------------------------------------------- 本篇只涉及段特权级的内容,关于各种门也只涉及调用门。 先看几个名词: CPL(Current Privilege Level)当前特权级,由CS和 SS中的第0和第1位记录 DPL(De script or Privilege Level)描述符特权级,由描述符中段属性的第5和第6位记录 RPL(Requested Privilege Level)请求特权级,由选择子的第0和第1位记录 TSS(Task-State Segment)任务状态段,用于保存任务的相关信息。 现在一步一步来讨论特权级的比较: 1. CPL表示的是程序或者说任务的当前特权级,它不属于某个段,当前的程序或任务不可能同时表现出2种特权级,那么CS和SS的第0位和第1位应该总是相同 的。尝试将RPL异于CPL的数据段选择子装入SS会引起异常。另外后面在论述跳转的时候可以看到,它们总是相同的。 2.DPL表示的是某个段或门的特权级,当程序要访问段A的时候,会将CPL和段A的DPL作比较,以确定程序是否有权限访问段A。 3.RPL表示选择子是否有权限访问其所指向的段。选择子指向一个段描述符,段描述符指向一个段,段的特权级由段描述符中的DPL决定。当程序需要访问段 A的时候,需要先通过段A的选择子加载段A的段描述符,然后才能访问段A,在这个过程中CPU会先将段A选择子中的RPL和段A描述符中的DPL作比较, 确定选择子是否有权限访问其所指向的段,成功后才是CPL和段A的DPL的比较。 RPL、 DPL之间的比较规则与CPL、DPL之间的比较规则一致。举个例子解释前面这句话的意思:比如当前程序要访问一个数据段A,那么CPL不能大于段A的 DPL,否则失败(后面会讲到这一点)。这是CPL和段A的DPL的比较规则,那么同样段A的RPL、DPL也遵循这样的规则,也就是段A的RPL不能大 于段A的DPL。 RPL和CPL不会进行比较。 4.不管什么情况下,相同特权级之间的访问总是不会错的,所以后面的讨论中通常会忽略相同特权级之间的比较。 5.保护模式下代码段中可以存放数据,但数据段中不能存放代码(跳转不过去)。所以代码段既可以获取代码段中的数据(被读的代码段属性需要可读,就算是获 取自身段内数据也需要可读),也可以获取数据段中的数据(数据段总是可读的),还可以跳转到其他代码段。而数据段除了被读取,什么都做不了。 6.代码段分为一致代码段和非一致代码段,这会对特权级比较产生影响,是否一致由段描述符的第42位决定。数据段和代码不同,它总是非一致的。代码段只在 作为被访问一方(或者说目标代码段)时一致性才会对特权级比较产生影响,在作为访问一方时没有区别。是否一致代码段的区别是,在段间跳转过程中,如果目标 代码段是一个特权级更高的一致代码段,那么跳转成功,并且CPL不会改变(CS和SS都不会变),于是CPL异于目标代码段的RPL(CPL数值更大,特 权级更低);如果目标代码段是一个特权级更高的非一致代码段,那么跳转是会失败的,此时需要使用调用门。另外,如果目标代码段是一个特权级更低的代码段 (不论是否一致),那么跳转总是会失败的,除非使用RETF跳转。 7.如果当前程序要访问一个数据段A,那么CPL不能大于段A的DPL,否则失败。也就是说当前程序不能访问特权级更高的数据段。CPL可以小于段A的 DPL,也就是说当前程序可以访问特权级更低的数据段。当前程序对调用门和TSS的访问规则与此一致。 8.特权级的检查是在选择子被装入段寄存器的时候进行的。先看相对简单一点的数据段A选择子装入DS的情况(装入ES,FS,GS类似,这里以DS为例说明):段A的DPL必须大于RPL,同时还必须大于CPL(第7点),否则产生异常,段A的RPL不和CPL进行比较。 另外,当程序向低特权级跳转时,会检查CPL和DS指向的段的DPL,如果DPL小于CPL,那么DS会被加载空描述符的选择子。 9.数据段A选择子装入SS的情况(只有数据段选择子才能装入SS,代码段选择子不能装入SS,不管是否可读):段A的RPL和DPL都必须和CPL相等,否则失败。 来看下为什么会这样。CPU要保证CS和SS中的第0和第1位(CPL)一致,所以段A的RPL必须等于CPL。CPU还要保证当前程序的特权级和当前使用的堆栈段的特权级一致,所以段A的DPL要等于CPL。 10.代码段A选择子装入DS的情况(装入ES,FS,GS类似,这里以DS为例说明):如果段A不可读,装入失败。如果段A是可读的非一致代码段,那么 CPL、段A的RPL都必须等于段A的DPL,否则装入就会失败。如果段A是可读的一致代码段,那么CPL、段A的RPL都可以大于段A的DPL,但不能 小于段A的DPL。 代码段A用段超越前缀CS读取自身段内数据的时候,虽然没有选择子的装入过程,但CPU会检查段A是否可读,如果不可读会产生异常。 11.最后是代码段的段间跳转。段间跳转可以用JMP、CALL、RETF和调用门。从高特权级到低特权级只能用RETF;从低特权级到高特权级非一致代 码段,只能用调用门,而且还必须是用CALL指令来使用调用门;从低特权级到高特权级一致代码段JMP、CALL、调用门都可以。 |