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. JavaScript学习笔记(二)原型

    JavaScript不包含传统的类继承模型,而是使用prototype原型模型.JavaScript使用原型链的继承方式. function Foo() { this.value = 42; } Fo ...

  2. Sublime Text2使用技巧

    推荐Lucifr和JerryQu的几篇博文: Sublime Text 2 入门及技巧 via: http://lucifr.com/139225/sublime-text-2-tricks-and- ...

  3. 鼠标拖放div 实现

    Javascript的mousemove事件类型是一个实时响应的事件,当鼠标指针的位置发生变化时(至少移动1个像素),就会触发mousemove事件.该事件响应的灵敏度主要参考鼠标指针移动速度的快慢, ...

  4. PHP5中__call、__get、__set、__clone、__sleep、__wakeup的用法

    __construct(),__destruct(),__call(),__callStatic(),__get(),__set(),__isset(),__unset(),__sleep(),__w ...

  5. Mozilla推荐的CSS书写顺序

    //显示属性displaylist-stylepositionfloatclear //自身属性widthheightmarginpaddingborderbackground //文本属性color ...

  6. hibernate的3种状态

    hibernate的三种状态是瞬态.持久态.脱管态 瞬态:新new来的对象称为瞬态. 持久态:处于该状态的对象在数据库中有一条对应的记录,并拥有一个持久标识. 脱管态:当与某持久对象的session关 ...

  7. C#,json字符串转换成Json对象

    将JSON的请求参数转化为C#可序列化对象! JSON请求参数: "{\"id\":1,"name":"张三","dep ...

  8. 飘逸的python - 两种with语句实现方法

    第一种是实现上下文管理器协议,即魔法方法__enter__和__exit__. class Foo: def __enter__(self): print 'in' def __exit__(self ...

  9. devexpress中用ChartControl生成柱状图

    在界面中拖入一个ChartControl控件,然后添加一个simplebutton控件.在simplebutton控件的click事件中加入如下代码: private void button1_Cli ...

  10. 根据自己的需要适度使用Web开发框架

    软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多.Web开发框架能够帮我们大大减少工作量,但是我们应该如何正确看待Web开发框架,并且如何去使用他们呢? 对框架的依赖 ...