长模式下的段权限检查机制

📅 2026/8/5 8:50:40
长模式下的段权限检查机制
在x86_64的长模式Long Mode下段机制发生了根本性的简化段基址和段限长检查被弱化但权限检查即特权级检查仍然至关重要。长模式下的段权限检查实际上只围绕一个核心规则数据段的访问权限检查被大幅简化但代码段和控制转移的权限检查依然严格。一、长模式下的关键变化与保护模式对比特性保护模式32位长模式64位段基址可任意指定CS/SS/DS/ES 基址强制为0FS/GS可通过MSR独立设置段限长有效基于Limit字段CS/SS/DS/ES 段限长被忽略视为全64位地址空间分段检查每个内存访问都检查基址限长仅对FS/GS进行基址/限长检查其他段跳过权限检查DPL/RPL严格检查仍然严格检查特别是代码段和控制转移段类型检查完整检查代码/数据/系统段简化为代码段CS和数据段DS/SS/ES两类CALL/JMP门支持任务门、调用门等不再支持任务门/调用门使用中断门/陷阱门替代二、长模式下的段权限检查核心机制1. 当前特权级CPL的确定CPU通过CS段选择子的RPL请求特权级字段来确定当前特权级CPL而不是通过段描述符中的DPL。具体地在长模式下CS.RPL 0内核态ring 0CS.RPL 3用户态ring 3注意长模式只支持ring 0和ring 3ring 1和ring 2在长模式下没有实际用途但段描述符中的DPL仍然允许设置为1或2只是操作系统通常不使用。2. 数据段访问权限检查DS/SS/ES长模式下数据段的权限检查简化为仅检查CPL与段描述符DPL的关系条件结果CPL DPL数值上0 ≤ 3不对数值越小特权越高允许访问CPL DPL触发#GP通用保护异常具体规则CPL0内核可访问任何DPL的数据段DPL0/1/2/3CPL3用户只能访问DPL3的数据段不能访问DPL0/1/2示例; 用户态代码CS.RPL 3 mov rax, [0x12345678] ; 数据段默认为DS用户态DS指向DPL3的段→ 允许 mov rax, [fs:0x10] ; FS基址独立权限检查基于GS的段描述符通常也是DPL3→ 允许如果用户态试图加载一个DPL0的内核数据段到DS会触发#GP。3. 代码段访问与控制转移检查这是长模式下权限检查最复杂的部分涉及代码段切换和控制转移指令JMP、CALL、RET、IRET等。a. 直接转移JMP/CALL到同一代码段在同一代码段内跳转不涉及CPL变化只检查目标地址是否在当前段限长内但段限长被忽略所以总是允许。到不同代码段的跳转通过JMP/CALL远跳转在长模式下直接远跳转使用JMP FAR被禁止只能通过中断门/陷阱门或系统调用SYSCALL/SYSRET实现跨段转移。b. 通过中断门/陷阱门的控制转移异常和中断这是长模式中最常见的跨段转移方式。CPU通过IDT中断描述符表中的门描述符来转移控制权限检查条件当前特权级CPL必须等于中断门的DPL或者数值上小于DPL即特权级更高不对于中断门要求CPL DPL这里需要澄清用户态CPL3可以通过int 0x80触发一个DPL3的中断门例如Linux的系统调用。内核态CPL0触发任何中断通常内核中断门的DPL0。CPL 目标代码段的DPL不对于中断门目标代码段的DPL通常是0内核代码段但中断门本身有DPL权限检查的是CPL必须 中断门的DPL数值上0 3内核可触发任何中断3 3用户可触发DPL3的中断。目标段描述符的DPL代码段的DPL决定了目标代码段运行的特权级通常是0。示例Linux系统调用int 0x80用户态CPL3执行int 0x80。IDT中的0x80中断门的DPL3允许用户态触发。目标代码段__KERNEL_CS的DPL0内核态。权限检查CPL(3) 中断门DPL(3)→ 允许转移到内核代码段CPL变为0。c. 系统调用SYSCALL/SYSRET长模式下的系统调用不经过中断门直接使用SYSCALL指令它绕过了一般的段权限检查但依然通过MSRIA32_STAR、IA32_LSTAR指定目标代码段和栈并在执行时自动切换CPL从3到0返回时通过SYSRET切换回用户态从0到3。d. 返回指令RET/IRETIRET从中断返回会检查返回目标代码段的CPL是否与栈上的CS选择子的RPL一致如果不一致例如内核返回到用户态会触发#GP。RET远返回在长模式下RETF远返回是不允许的会被忽略或触发#GP。三、长模式下的段权限检查流程状态机┌─────────────────┐ │ 当前 CPL │ CS.RPL (从段选择子读取) │ (0 或 3) │ └────────┬────────┘ │ ▼ ┌───────────────────┐ │ 数据访问DS/SS/ES │ │ 检查CPL 目标段描述符的 DPL │ └────────┬──────────┘ │ ▼ ┌───────────────────┐ │ 代码转移JMP/CALL/IRET │ │ 1. 检查中断门/陷阱门的 DPL │ │ 2. 检查目标代码段的 DPL │ │ 3. 检查栈段SS的 DPL │ └────────┬──────────┘ │ ▼ ┌────────────────────┐ │ 返回IRET │ │ 检查返回目标 CS 的 RPL 与当前 CPL │ └────────────────────┘四、段寄存器在长模式下的实际角色段寄存器基址段限长权限检查角色CS0固定忽略确定CPL通过RPL字段代码执行权限SS0固定忽略特权级切换时确定是否切换栈通过IRETDS0固定忽略数据访问权限检查CPL DPLES0固定忽略同DSFS / GS独立MSR设置可设置仍进行完整的基址/限长检查用于线程局部存储、CPU核心数据五、实际代码示例// 尝试在内核态加载用户态数据段x86_64 static inline void load_user_ds(void) { asm volatile ( mov %0, %%ds\n : : r (__USER_DS) // 用户态数据段选择子DPL3 : memory ); } // 如果在内核CPL0中执行此函数会触发 #GP // 因为 CPL(0) 用户态DS的DPL(3)不CPL0 DPL3应该允许 // 实际上内核可以加载用户态DS因为0 3但访问内存时DS的基址为0且权限检查基于CPL与DS.DPL的关系。 // 但出于安全考虑Linux内核通常不这样做。六、总结长模式下的段权限检查规则操作检查内容失败结果加载数据段DS/SS/ESCPL 段描述符.DPL且段类型 数据段#GP加载代码段CS仅通过中断门/陷阱门或SYSCALL间接加载不能直接LOAD#GP中断/异常处理CPL 中断门.DPL且目标代码段.DPL 0通常#GP/#DFIRET返回栈上CS.RPL 目标CPL且CPL 目标栈段.DPL#GPSYSCALL进入无检查硬件直接切换#GP如果MSR配置错误SYSRET返回要求目标CS.RPL 3 且 目标栈段.DPL 3#GP七、常见误解澄清“长模式下段寄存器被弃用了”→ 不准确。CS和SS仍然用于特权级管理FS和GS仍然用于TLS/CPU核心数据。“长模式下段限长检查被完全忽略”→ 仅对CS、DS、SS、ES而言。FS和GS仍然有基址和限长通过MSR设置。“长模式下CPL只能从CS选择子读取”→ 正确。段描述符的DPL只用于数据段权限检查不用于确定CPL。