• CPU段访问控制:特权级(RPL CPL DPL)和代码段一致性


      最近笔者回顾CPU硬件的段访问控制机制,重新看到了代码段一致性问题。虽然目前操作系统没有应用分段机制,但了解其运行原理仍然具有吸引力XD。本片文章,我们就来理清CPU段访问控制中,代码段一致性的概念。

    Privilege Level 特权级

    首先,应当分清三个特权级的基本概念:

    • DPL (Descriptor Privilege Level 段描述符特权级): 定义于**段描述符 (Segment Descriptor)**中,对于不同的段,DPL的意义不尽相同

      • Data Segments 数据段:访问该段的最低特权等级。如果程序特权级过低,则不可访问该段
      • Stack Segment 栈段:访问该段的特权等级(需要相同,否则触发general-protection fault #GP
      • Non-conforming Code Segments 非一致性代码段:Direct Call(直接跳转,不经过 call gate)时,caller需要的特权等级(必须相同
      • Call gate & TSS:访问该Call gate / TSS 的最低特权等级,低权限程序无权使用高权限服务。
      • invoking Code Segments via Call Gates 通过调用门调用的代码段:访问该段的最高等级,这里与一般设置不同。事实上,通过调用门调用代码,通常是低特权级程序请求高特权服务时使用,这个设计也符合以上应用需求。详见下一节。
    • RPL (Requested Privilege Level 请求特权级):定义于段选择子中(最后两位),表示当前发起请求的特权级(以何种身份发起请求)。可以用于防止低权限程序借用高权限代码破坏高权限段

    • CPL (Current Privilege Level 当前特权级): 定义于当前程序的CS中,即CPL = CS.RPL。表示当前运行程序的特权级。

    特权级检查

    特权级检查,在将Segment Selector放入Segment Register时进行。按照上述规则执行检查

    如果检查不通过,通常会产生异常:General-protection fault (#GP)

    关于跳转中各种情况的分类解析,详见下节:控制权转移

    控制权转移

    有多种途径进行控制权的转移:

    • 应用程序执行的指令: JMP CALL RET INT n IRET
    • 来自硬件的事件:中断 interrupt异常 exception

    在跨段的跳转中,会执行特权级检查,跳转主要有以下几种情形:

    direct jump

    当通过CALL 或者 JMP 直接跳转到另一个代码段时,会检查CPL、目标段描述符的DPL、目标段选择子(Segment Selector)的RPL。此时按照Target Segment Descriptor的C标志位(Conforming 一致性),可分为两种情况:

    • 一致性代码段(Conforming Code Segment):CPL 必须大于等于DPL(低权限代码借用高权限代码段)。即使跳转到DPL较高(数字小)的一致性代码段时CPL也不会改变因此不会进行堆栈切换(Stack-Switch)

      可以将一些不需要系统保护的部分API(数学函数、Exception Handlers等)设置在Conforming Segment中,由于CPL不改变,避免其影响高特权级数据

    • 非一致性代码段(non-Conforming Code Segment):CPL一定与Target DPL相同,且RPL小于等于CPL (特权级更高,请求方必须以高权限访问该段)(如果RPL比CPL更低(大于),则表示请求来自低特权级程序,访问失败)
      C P L = D P L R P L ≤ C P L CPL=DPL\\ RPL\le CPL CPL=DPLRPLCPL
      一般情况下,大部分其他Code Segment都应当设定为非一致性的

    Call Gate

    **Gate(门)**是一种系统段,它的Segment Descriptor 称为 gate descriptor。通常有四种门:Call Gates 调用门Trap Gates 陷门Interrupt Gates 中断门Task Gates 任务门。这里只讨论Call Gates。

    typedef struct _CALL_GATE
    {
    	USHORT OffsetLow;
    	USHORT Selector;
    	UCHAR NumberOfArguments:5;
    	UCHAR Reserved:3;
    	UCHAR Type:5; // 01100 in i386, 00100 in i286
    	UCHAR Dpl:2;
    	UCHAR Present:1;
    	USHORT OffsetHigh;
    }CALL_GATE,*PCALL_GATE;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    JMPCALL指令的目标段寄存器存放的选择子是Call Gate时,视为使用调用门(忽略偏移,在Gate Descriptor中已经指定)。

    经由Call gate调用API时,CPL和RPL都要小于等于(特权级高于)Call gate的DPL

    Call 使用调用门时,CPL和RPL都要大于等于Call gate对应Segment的DPL(非Call Gate的DPL)。如果调用者权限更高,则不能通过调用门进行调用;JMP使用时,若target segment是non-conforming segment,则target DPL必须与CPL相同。

    按照目标代码段区分:

    • 一致性代码段:
      C P L ≥ t a r g e t   D P L CPL \ge target\ DPL CPLtarget DPL
      CPL不改变,没有特权级转换。

    • 非一致代码段:

      • JMP跳转
        C P L = t a r g e t   D P L R P L ≤ C P L CPL = target\ DPL \\ RPL \le CPL CPL=target DPLRPLCPL
        事实上,由于RPL被清零,条件二一定被满足。

        跳转后,CPL不变,没有特权级变化

      • CALL跳转
        C P L ≥ t a r g e t D P L R P L ≤ C P L CPL \ge target DPL \\ RPL \le CPL CPLtargetDPLRPLCPL
        事实上,由于RPL被清零,条件二一定被满足。

        由低等级程序通过调用门请求访问高等级代码。

        跳转后,CPL=Target DPL产生特权级变化。(这是目前唯一产生变化的跳转方式)。

    由程序中返回

    使用RET指令进行跨段返回时,会检查返回目标的Segment Descriptor中的DPL是否大于等于当前CPL(权限较低),否则说明堆栈中数据错误。

    CPU使用栈存储的返回CS中的RPL进行权限判断,检查是否需要改变权限。如果需要改变权限,则同时进行堆栈切换。所以,处理器会先将CS:IP载入,再载入SS:SP,同时,CPU还会恢复其他段寄存器的权限,并将指向更高权限的段的选择子置为空。

    Real World

    从设计原理上,RPL只是提供了一个CPU和操作系统之间的协议:CPU负责检查特权级、操作系统负责RPL的正确性。内核知道用户态请求操作的段选择子,也就能检查/修改对应的RPL,保证其与需要的权限相同。

    通常,我们不会使用分段的保护机制,硬件架构建议将所有段的RPL设为0,这样可以通过所有RPL判断,不会进行这部分检查。

  • 相关阅读:
    新能源国标接入随想
    一统江湖:毫米波雷达开发手册之大话线谱估计
    C语言进阶文件操作
    程序员缺乏经验的 7 种表现,你中了几个?
    双注入法/开路短路法
    系统集成项目管理工程师认证高频考点:编制项目范围管理计划
    springboot+mybatis-plus+element ui生成二维码
    1.10 - 总线
    【图像分割】基于改进粒子算法优化阈值实现图像分割附matlab代码
    再畅通工程(最小生成树)
  • 原文地址:https://blog.csdn.net/Zheng__Huang/article/details/127710913