CPU不仅仅在程序访问数据段和堆栈段的时候进行权限级别检查,当程序控制权转换的时候也会进行权限级别检查。程序控制权转换的情况很多,各种情况下检查的方式以及涉及到的检查项都是不同的。这篇文章主要描述了各种代码控制权转换过程中涉及到的各种检查并且配以相应的示例,示例代码是根据《Task》中的代码修改的,托管在https://github.com/activesys/learning_cpu/tree/master/x86/protection_5

程序控制权转换

很多指令都可以引起代码控制权的转换,例如call, jmp, int, lcall, ljmp, sysenter, sysexit以及syscall, sysret等等,但是不同的指令会引起不同类型的控制权转换,即使相同的指令接不同类型的选择子也会引起不同类型的控制权转换,总结起来有下面几种:

  • 短跳转:这种跳转是在段内的控制权转换,不进行权限级别检查。
  • 长跳转:这种跳转是段间的控制权转换,进行权限级别检查,这篇文章主要关注的就是这类控制权转换。
  • 中断和异常:中断和异常引起的控制权转换在学习中断和异常的时候再描述。
  • 任务切换:任务切换引起的控制权转换已经在《Task》中描述了。
  • sysenter,sysexit以及syscall,sysret指令实现的快速系统调用引起的控制权转换。

程序控制权转换时的权限检查

在lcall或者ljmp指令后面接代码段选择子来实现段间的程序控制权转换,这个时候CPU要实施权限级别检查,检查涉及到CPL, RPL, DPL以及代码段描述符中的C位。C标志位的不同导致了代码段分为nonconforming和conforming,针对这两种类型的代码段的权限级别检查也是不同的。

nonconforming

在跳转到nonconforming代码段的时候,CPU要求CPL == DPL && CPL >= RPL。当段选择子成功的加载到%cs之后,CPL并不改变。这样看来要访问nonconforming代码段必须是同级别的代码,即使是高权限级别代码访问低权限级别代码也是不行的。

为了验证对nonconforming代码段访问过程中的权限级别检查,我们必须添加两个nonconforming代码段,一个DPL==0,一个DPL==3,同时还有这两个段的“配套设置”:数据段,堆栈段和作为屏幕输出的扩展段:

# test code data
# attr = 0x4092(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=0010)
.equ TEST_CODE_DPL0_DATA_BASE, 0x0000
.equ TEST_CODE_DPL0_DATA_LIMIT, 0xffff
.equ TEST_CODE_DPL0_DATA_ATTR, 0x4092
.equ TEST_CODE_DPL0_DATA_SELECTOR, 0x58 # test code stack
# attr = 0x4092(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=0010)
.equ TEST_CODE_DPL0_STACK_BASE, 0xc200
.equ TEST_CODE_DPL0_STACK_LIMIT, 0xffff
.equ TEST_CODE_DPL0_STACK_ATTR, 0x4092
.equ TEST_CODE_DPL0_STACK_SELECTOR, 0x60
.equ TEST_CODE_DPL0_STACK_INIT_ESP, 0xd000 # test code video
# attr = 0x4092(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=0010)
.equ TEST_CODE_DPL0_VIDEO_BASE, 0x0b8000
.equ TEST_CODE_DPL0_VIDEO_LIMIT, 0xffff
.equ TEST_CODE_DPL0_VIDEO_ATTR, 0x4092
.equ TEST_CODE_DPL0_VIDEO_SELECTOR, 0x68 # test code data
# attr = 0x40f2(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=0010)
.equ TEST_CODE_DPL3_DATA_BASE, 0x0000
.equ TEST_CODE_DPL3_DATA_LIMIT, 0xffff
.equ TEST_CODE_DPL3_DATA_ATTR, 0x40f2
.equ TEST_CODE_DPL3_DATA_SELECTOR, 0x73 # test code stack
# attr = 0x40f2(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=0010)
.equ TEST_CODE_DPL3_STACK_BASE, 0xc200
.equ TEST_CODE_DPL3_STACK_LIMIT, 0xffff
.equ TEST_CODE_DPL3_STACK_ATTR, 0x40f2
.equ TEST_CODE_DPL3_STACK_SELECTOR, 0x7b
.equ TEST_CODE_DPL3_STACK_INIT_ESP, 0xd000 # test code video
# attr = 0x40f2(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=0010)
.equ TEST_CODE_DPL3_VIDEO_BASE, 0x0b8000
.equ TEST_CODE_DPL3_VIDEO_LIMIT, 0xffff
.equ TEST_CODE_DPL3_VIDEO_ATTR, 0x40f2
.equ TEST_CODE_DPL3_VIDEO_SELECTOR, 0x83 # nonconforming code DPL==0
# attr = 0x4098(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=1000)
.equ NONCONFORMING_CODE_DPL0_BASE, 0xc000
.equ NONCONFORMING_CODE_DPL0_LIMIT, 0xffff
.equ NONCONFORMING_CODE_DPL0_ATTR, 0x4098
.equ NONCONFORMING_CODE_DPL0_SELECTOR, 0x88 # nonconforming code DPL==3
# attr = 0x40f8(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=1000)
.equ NONCONFORMING_CODE_DPL3_BASE, 0xc040
.equ NONCONFORMING_CODE_DPL3_LIMIT, 0xffff
.equ NONCONFORMING_CODE_DPL3_ATTR, 0x40f8
.equ NONCONFORMING_CODE_DPL3_SELECTOR, 0x93

实现两个nonconforming的代码都放在code.s中:

###############################################################
# nonconforming code DPL == 0
_nonconforming_code_dpl0:
xorl %eax, %eax
movl $TEST_CODE_DPL0_DATA_SELECTOR, %eax
movw %ax, %ds
movl $TEST_CODE_DPL0_STACK_SELECTOR, %eax
movw %ax, %ss
movl $TEST_CODE_DPL0_STACK_INIT_ESP, %esp
movl $TEST_CODE_DPL0_VIDEO_SELECTOR, %eax
movw %ax, %es
movl $NULL_SELECTOR, %eax
movw %ax, %fs
movw %ax, %gs movl $_nonconforming_code_dpl0_msg, %esi
movl $_nonconforming_code_dpl0_msg_len, %ecx
movl $TEST_CODE_VIDEO_OFFSET, %edx
call _code_echo jmp . .space 0x40-(.-_start), 0x00 ###############################################################
# nonconforming code DPL == 3
_nonconforming_code_dpl3:
xorl %eax, %eax
movl $TEST_CODE_DPL3_DATA_SELECTOR, %eax
movw %ax, %ds
movl $TEST_CODE_DPL3_STACK_SELECTOR, %eax
movw %ax, %ss
movl $TEST_CODE_DPL3_STACK_INIT_ESP, %esp
movl $TEST_CODE_DPL3_VIDEO_SELECTOR, %eax
movw %ax, %es
movl $NULL_SELECTOR, %eax
movw %ax, %fs
movw %ax, %gs movl $_nonconforming_code_dpl3_msg, %esi
movl $_nonconforming_code_dpl3_msg_len, %ecx
movl $TEST_CODE_VIDEO_OFFSET, %edx
call _code_echo jmp . .space 0x80-(.-_start), 0x00

code.s中还添加了用于两个nonconforming代码段输出的消息数据:

###############################################################
# message data
_nonconforming_code_dpl0_msg:
.ascii "In nonconforming code segment, DPL == 0."
_nonconforming_code_dpl0_msg_end:
.equ _nonconforming_code_dpl0_msg_len, _nonconforming_code_dpl0_msg_end - _nonconforming_code_dpl0_msg
_nonconforming_code_dpl3_msg:
.ascii "In nonconforming code segment, DPL == 3."
_nonconforming_code_dpl3_msg_end:
.equ _nonconforming_code_dpl3_msg_len, _nonconforming_code_dpl3_msg_end - _nonconforming_code_dpl3_msg

通过权限级别检查

万事俱备了,可以跳转到nonconforming代码段了,首先在CPL==0时跳转到DPL==0的nonconforming代码段,在kernel.s中加入长跳转代码:

    lcall $NONCONFORMING_CODE_DPL0_SELECTOR, $0x00

运行结果:



从运行的结果可以看出从kernel代码跳转到了DPL==0的nonconforming代码段。

接下来试验一下CPL==3的时候跳转到DPL==3的nonconforming代码段,在user.s中加入长跳转:

    lcall $NONCONFORMING_CODE_DPL3_SELECTOR, $0x00

运行结果:

从结果上看控制权是从kernel代码转移到user代码,这时候CPL==3,然后通过lcall转移到了nonconforming代码段。

没有通过权限级别检查

上面的例子都是通过的权限级别检查的,再来看看不能通过检查的情况,也就是当CPL!=DPL的时候,首先在CPL==0的代码中访问DPL==3的nonconforming代码段,在kernel.s加入lcall长跳转:

    lcall $NONCONFORMING_CODE_DPL3_SELECTOR, $0x00

运行结果:

结果是在kernel中触发了#GP。

再来看看在CPL==3的代码中访问DPL==0的nonconforming代码段,在user.s中调用lcall长跳转:

    lcall $NONCONFORMING_CODE_DPL0_SELECTOR, $0x00

运行结果:


从运行结果中可以看出在CPL==3的时候访问DPL==0的nonconforming代码段触发了#GP。

conforming

控制权切换到conforming代码段的时候进行的权限级别检查与nonconforming是不同的,conforming代码段描述中的DPL表示的是能够访问该代码段的最高权限级别,例如DPL==0,那么CPL==0~3都可以访问,但是如果DPL==3,那么只有CPL==3的代码段才可以访问。控制权转移到conforming代码段之后CPL并不改变,例如从CPL==3的代码段转换到DPL==0的conforming代码段,转换之后CPL仍然是3。

为了验证切换到conforming代码段时进行的权限级别检查,我们添加了三个代码段描述符:

# conforming code DPL==0
# attr = 0x409c(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=1100)
.equ CONFORMING_CODE_DPL0_BASE, 0xc080
.equ CONFORMING_CODE_DPL0_LIMIT, 0xffff
.equ CONFORMING_CODE_DPL0_ATTR, 0x409c
.equ CONFORMING_CODE_DPL0_SELECTOR, 0x98 # conforming code DPL==3
# attr = 0x40fc(G=0,D/B=1,L=0,AVL=0,P=1,DPL=11,S=1,TYPE=1100)
.equ CONFORMING_CODE_DPL3_BASE, 0xc0c0
.equ CONFORMING_CODE_DPL3_LIMIT, 0xffff
.equ CONFORMING_CODE_DPL3_ATTR, 0x40fc
.equ CONFORMING_CODE_DPL3_SELECTOR, 0xa3 # conforming code DPL==0, CPL==3
# attr = 0x409c(G=0,D/B=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=1100)
.equ CONFORMING_CODE_DPL0_CPL3_BASE, 0xc100
.equ CONFORMING_CODE_DPL0_CPL3_LIMIT, 0xffff
.equ CONFORMING_CODE_DPL0_CPL3_ATTR, 0x409c
.equ CONFORMING_CODE_DPL0_CPL3_SELECTOR, 0xa8

一个DPL==0,一个DPL==3,还有一个是为了在CPL==3的时候访问的DPL==0的代码段。相应的在code.s中有三段conforming代码:

###############################################################
# conforming code DPL == 0
_conforming_code_dpl0:
xorl %eax, %eax
movl $TEST_CODE_DPL0_DATA_SELECTOR, %eax
movw %ax, %ds
movl $TEST_CODE_DPL0_STACK_SELECTOR, %eax
movw %ax, %ss
movl $TEST_CODE_DPL0_STACK_INIT_ESP, %esp
movl $TEST_CODE_DPL0_VIDEO_SELECTOR, %eax
movw %ax, %es
movl $NULL_SELECTOR, %eax
movw %ax, %fs
movw %ax, %gs movl $_conforming_code_dpl0_msg, %esi
movl $_conforming_code_dpl0_msg_len, %ecx
movl $TEST_CODE_VIDEO_OFFSET, %edx
call _code_echo jmp . .space 0xc0-(.-_start), 0x00 ###############################################################
# conforming code DPL == 3
_conforming_code_dpl3:
xorl %eax, %eax
movl $TEST_CODE_DPL3_DATA_SELECTOR, %eax
movw %ax, %ds
movl $TEST_CODE_DPL3_STACK_SELECTOR, %eax
movw %ax, %ss
movl $TEST_CODE_DPL3_STACK_INIT_ESP, %esp
movl $TEST_CODE_DPL3_VIDEO_SELECTOR, %eax
movw %ax, %es
movl $NULL_SELECTOR, %eax
movw %ax, %fs
movw %ax, %gs movl $_conforming_code_dpl3_msg, %esi
movl $_conforming_code_dpl3_msg_len, %ecx
movl $TEST_CODE_VIDEO_OFFSET, %edx
call _code_echo jmp . .space 0x0100-(.-_start), 0x00 ###############################################################
# conforming code DPL == 0, CPL == 3
_conforming_code_dpl0_cpl3:
xorl %eax, %eax
movl $TEST_CODE_DPL3_DATA_SELECTOR, %eax
movw %ax, %ds
movl $TEST_CODE_DPL3_STACK_SELECTOR, %eax
movw %ax, %ss
movl $TEST_CODE_DPL3_STACK_INIT_ESP, %esp
movl $TEST_CODE_DPL3_VIDEO_SELECTOR, %eax
movw %ax, %es
movl $NULL_SELECTOR, %eax
movw %ax, %fs
movw %ax, %gs movl $_conforming_code_dpl0_cpl3_msg, %esi
movl $_conforming_code_dpl0_cpl3_msg_len, %ecx
movl $TEST_CODE_VIDEO_OFFSET, %edx
call _code_echo jmp .

通过权限级别检查

首先来看看在CPL==0级别的代码切换到DPL==0的conforming代码段的情况,在kernel.s中加入如下代码:

    lcall $CONFORMING_CODE_DPL0_SELECTOR, $0x00

运行结果:

从结果中可以看出成功的切换到了DPL==0的conforming代码段。

再来看看从CPL==3的代码段切换至DPL==3的conforming代码段,在user.s中加入如下代码:

    lcall $CONFORMING_CODE_DPL3_SELECTOR, $0x00

运行结果:

从结果中可以看出从CPL==3的代码段成功的切换到了DPL==3的conforming代码段。

没有通过权限级别检查

conforming代码段描述符中的DPL表示的是能够访问该代码段的CPL的最高权限,那么在CPL==0的代码段中访问DPL==3的conforming代码段必然会触发异常,为了做这个验证,在kernel.s加入如下代码:

    lcall $CONFORMING_CODE_DPL3_SELECTOR, $0x00

运行结果如你我所愿:

我们再来试验一下在CPL==3的情况下访问DPL==0的conforming代码段,在user.s中加入下面代码:

    lcall $CONFORMING_CODE_DPL0_SELECTOR, $0x00

运行结果:

怎么会触发异常了呢?按照conforming代码段的权限级别检查规则,这个测试应该是成功的,具体原因在哪里呢?其实这个#GP不是代码控制权转换过程中的权限检查产生的,而是进入conforming代码段之后加载段寄存器时的权限检查产生的,因为conforming代码段的控制权转换过程中,CPL不变,所以进入conforming代码段之后CPL仍然是3,但是要加载的代码段,堆栈段等段都是DPL==0的,这样就触发了#GP。

还记得最开始的时候我们准备了一段DPL==0,CPL==3的conforming代码段吗,现在可以派上用场了,它与DPL==0的conforming代码段的区别就是内部加载的段寄存器都是DPL==3的段,这样就不会触发#GP了。为了验证我们的猜测,在user.s中加入如下代码:

    lcall $CONFORMING_CODE_DPL0_CPL3_SELECTOR, $0x00

运行结果如你我所愿:

call gate

到目前为止似乎不能够在不同权限级别之间进行切换,因为无论是nonconforming代码段还是conforming代码段,在发生控制权转换的过程中CPL都是不变的。为了实现权限级别切换,CPU提供了Call-Gate描述符。但是权限切换是有严格要求的,不是所有的情况都能够实现权限切换。

先来看看call-gate机制:

这是Intel官方文档中关于call-gate机制的描述,长跳转指令通过门选择子以及偏移量来选择门描述符,这里的偏移量CPU不会使用,但是必须提供,所以可以是任何值。门描述符中有段选择子以及偏移量,通过段选择子获得段描述符其中的段基址,然后与门描述符中的偏移量一同计算出实际的代码段。

这样通过call-gate来进行控制权转换的过程中进行权限级别检查时涉及的标志位就是:

  • CPL
  • call-gate的RPL
  • 门描述符的DPL
  • 目标代码段描述符的DPL
  • 目标代码段描述符的C标志位

通过call-gate访问代码段的时候lcall和ljmp导致的权限检查是不同的:

这是Intel官方文档中给出的通过call-gate访问代码段的时候的权限检查规则,当访问nonconforming代码段的时候lcall和ljmp的检查规则是不一致的。

从表中可以看出只有lcall命令可以从低权限级别访问高权限级别的nonconforming代码段,对于conforming代码段,lcall和ljmp都可以实现从低权限级别到高权限级别的访问。但是只有lcall从低权限级访问高权限级别的nonconforming代码段的时候才会发生CPL改变,CPL变成nonconforming代码段的DPL,访问conforming代码段CPL仍然是不变的。

原来在user.s中访问DPL==0的nonconforming代码段是会触发#GP的,现在可以通过call-gate实现这样的访问。为了实现call-gate机制要在GDT中添加一个call-gate描述符:

# nonconforming code call gate
# attr = 0x00ec(G=0,D/B=0,L=0,AVL=0,P=1,DPL=11,S=0,TYPE=1100)
.equ NONCONFORMING_CALL_GATE_BASE, 0x88
.equ NONCONFORMING_CALL_GATE_LIMIT, 0x00
.equ NONCONFORMING_CALL_GATE_ATTR, 0x00ec
.equ NONCONFORMING_CALL_GATE_SELECTOR, 0xb0

它指向了DPL==0的nonconforming代码段。在user.s中call-gate选择子来访问nonconforming代码段:

    lcall $NONCONFORMING_CALL_GATE_SELECTOR, $0x00

运行结果:

从运行结果可以看出,代码实现了从CPL==3的代码段转换到了DPL==0的nonconforming代码段,如果不使用call-gate机制是会触发#GP的,这说明了call-gate的作用。

参考

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》
《自己动手写操作系统》
《x86/x64体系探索与编程》

Protection 5 ---- Priviliege Level Checking 2的更多相关文章

  1. Oracle12c版本中未归档隐藏参数

    In this post, I will give a list of all undocumented parameters in Oracle 12.1.0.1c. Here is a query ...

  2. Workflow_上传和下载Workflow编译方式(汇总)

    2014-12-27 Created By BaoXinjian

  3. Massively parallel supercomputer

    A novel massively parallel supercomputer of hundreds of teraOPS-scale includes node architectures ba ...

  4. 修改文件夹的protection level之后,哪个job会来执行re-stripe的操作呢?

    有下面的一些job可能参与其中的,他们的描述如下: AutoBalance,AutoBalanceLin - Balances free space in the cluster. The goal ...

  5. 使用System.IO.Combine(string path1, string path2, string path3)四个参数的重载函数提示`System.IO.Path.Combine(string, string, string, string)' is inaccessible due to its protection level

    今天用Unity5.5.1开发提取Assets目录的模块,使用时采用System.IO.Path.Combine(string, string, string, string)函数进行路径生成 明明是 ...

  6. Process Kill Technology && Process Protection Against In Linux

    目录 . 引言 . Kill Process By Kill Command && SIGNAL . Kill Process By Resource Limits . Kill Pr ...

  7. Linux进程自保护攻防对抗技术研究(Process Kill Technology && Process Protection Against In Linux)

    0. 引言 0x1: Linux系统攻防思想 在linux下进行"进程kill"和"进程保护"的总体思路有以下几个,我们围绕这几个核心思想展开进行研究 . 直接 ...

  8. Sensitive directory/file Integrity Monitoring and Checking

    catalogue . OSSEC . HashSentry: Host-Based IDS in Python . Afick . 检测流程 1. OSSEC OSSEC is an Open So ...

  9. Creating a CSRF protection with Spring 3.x--reference

    reference from:http://info.michael-simons.eu/2012/01/11/creating-a-csrf-protection-with-spring-3-1/ ...

随机推荐

  1. dede调取文章内容的第一张图片

    dede调用文章第一张图片(非缩略图)的实现方法 这篇文章主要是介绍dede调用文章第一张图片的实现代码,需要的朋友可以参考下 需要进行两个操作 第一步,修改include/extend.func.p ...

  2. Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 10 declared in library

    Error:Execution failed for task ':app:processDebugManifest'. > Manifest merger failed : uses-sdk: ...

  3. Android L 之 RecyclerView 、CardView 、Palette

    转: http://blog.csdn.net/xyz_lmn/article/details/38735117 <Material Design>提到,Android L版本中新增了Re ...

  4. Constructor Prototype Pattern 原型模式(PHP示例)

    当一个类大部分都是相同的只有部分是不同的时候,如果需要大量这个类的对象,每次都重复实例化那些相同的部分是开销很大的,而如果clone之前建立对象的那些相同的部分,就可以节约开销. 针对php的一种实现 ...

  5. c代码中调用c++,c++代码中调用c代码

    注意这里的c调用c++或者c++调用c的意思是.c文件中调用.cpp文件中的代码,或者相反 集成开发环境如vc++6.0或者vs都是通过文件后缀来区别当前要编译的是C代码还是C++代码,然后采用相应的 ...

  6. js打开新窗口的两种方式

    1.超链接<a href="http://www.jb51.net" title="脚本之家">Welcome</a>等效于js代码wi ...

  7. Delphi通过调用COM对象实现更改桌面壁纸

    从前我也是用SystemParametersInfo这API来改桌面壁纸的,问题多多,也不知道哪错了,就没深究下去.看了CSDN的帖子后,大彻大悟~~         在XP下,SystemParam ...

  8. delphi 实现微信开发

    大体思路: 1.用户向服务号发消息,(这里可以是个菜单项,也可以是一个关键词,如:注册会员.) 2.kbmmw web server收到消息,生成一个图文消息给微信,在图文消息中做好自己的url,在u ...

  9. A fatal error has been detected by the Java Runtime Environment:

    在Eclipse中运行项目 遇到如下错误: ## A fatal error has been detected by the Java Runtime Environment:## EXCEPTIO ...

  10. hdu2795线段树

    //=========================================== //segment tree //final version //by kevin_samuel(fenic ...