前言
去年我在论坛上发过AMD64体系的系统调用机制,而今年的二月,AMD发布了有关增强系统调用的白皮书。
这篇白皮书英文名为AMD Supervisor Entry Extensions,即超威半导体设计的进入内核模式的扩展。
注意,该机制截止本文发表仅在白皮书阶段,打了草稿而已,还未发布过支持这种机制的CPU。
早期不足
早期的AMD64的系统调用体系是基于syscall
和sysret
指令的,这两条指令不会自动地将CPU的所有状态切换到内核模式需要使用的状态,比如rsp
寄存器,gs
段等等。
此外,syscall
和sysret
指令虽然可以通过SFMASK
这个MSR实现原子性地屏蔽外部中断,但无法屏蔽不可屏蔽中断(NMI)。
AMD64体系下也存在异常重入的情况。举个例子,安全异常#SX
(Security Exception)之内再次触发#SX
可以导致异常的循环,而不是陷入双重故障,即#DF
异常。(#SX
异常是一种AMD系处理器才有的异常,目前的AMD系处理器的#SX
异常只有一种触发的情况:当VM_CR
这个MSR的R_INIT
位(即Redirect INIT)置位后,处理器的INIT
信号不再是初始化处理器状态,而是会被转换为#SX
异常进入中断处理函数。)
增强系统调用
这个增强系统调用(Enhanced syscall
/sysret
)被简写为ESC,即Enhanced System Call。由于ESC常用于键盘上Escape键的缩写,故称之为逃跑机制。
ESC在AMD64体系中的EFER
MSR中新增了ESCE
位,表明是否启用ESC机制。
ESC还增加了STSTAR
这个MSR。该MSR是syscall
指令进入内核时指定的rsp
寄存器的值,不再需要内核自行切换rsp
了。AMD没有在白皮书里明说是不是启用了ESC机制才会用到这个STSTAR
。从逻辑上说,如果不启用ESC就会使用STSTAR
的话,会造成栈切换逻辑的混乱,系统调用入口会假设rsp
指向用户模式栈,不了解ESC的OS内核不会设置STSTAR
的值,从而在syscall
指令后导致rsp
的值不可预测。那么很明显,STSTAR
应该是仅在启用了ESC机制后才会使用。
当ESC启用的时候,syscall
指令会额外执行以下行为:
- 自动将
gs
的段基址与KernelGsBase
MSR的值相互切换,相当于自动执行了swapgs
指令。
- 将
STSTAR
的值加载到rsp
寄存器里。刚才提过了。
- 如果处理器启用了内核模式的影栈(即
S_CET
MSR中的第零位被置位),则将PL0_SSP
MSR的值加载到ssp
影栈寄存器。但不会自动地将其Busy
位置位。这一条结合RSSS机制使用效果最佳。
- 执行
syscall
指令会在新的栈上填充syscall
指令执行时的下一条指令的rip
寄存器,以及当时的cs
和ss
段选择子和rsp
寄存器的值。
- 当
syscall
指令位于中断阴影之中时(Interrupt Shadow)时,挂起的调试异常会被弃置。所谓的中断阴影,指的是特定条件导致中断被暂时屏蔽,直到执行了下一条指令才会解除屏蔽的现象。这包括了我之前提到过的CVE-2018-8897漏洞中的mov ss,xx
或者pop ss
指令造成的中断屏蔽。那很明显,ESC机制的这一条就是冲着CVE-2018-8897漏洞去的。
当ESC启用的时候,sysret
指令会执行以下额外的行为:
- 根据
syscall
额外行为的第四条,从内核栈上还原用户模式rsp
和rip
寄存器的值。
- 根据
syscall
额外行为的第一条,自动切换gs
的段基址。
注意,处理器不会在sysret
指令中根据内核栈上的内容切换cs
和ss
的段选择子,而是老样子根据STAR
寄存器来切换cs
和ss
的段选择子。
内核模式保留影栈
内核模式保留影栈(Reserved Supervisor Shadow Stacks,简写为RSSS)是一种增强切换影栈的机制。
RSSS机制在S_CET
MSR中新增了一个RSSSE
位用于启用RSSS机制。
在RSSS机制下,用wrmsr
指令对PL0_SSP
,PL1_SSP
,PL2_SSP
这三个MSR写入时会自动地将之前的影栈令牌的Busy
位复位,将新的影栈令牌的Busy
位置位。
也就是说,在RSSS机制下wrmsr
写入这三个寄存器的行为暗含了一套clrssbsy
和setssbsy
指令的操作。
如果复位旧影栈的Busy
位时出错了,会抛出相关异常。比方说访问不合法的页会抛出#PF
异常。但如果Busy
位本来就是复位的,则不会抛出异常。
如果置位新影栈的Busy
位时出错了,会抛出#CP
异常,压入栈的异常码会表明是setssbsy
错误。同时会将该MSR清零。
如果写入这三个MSR的值是零,则处理器只有写入的行为,不再有clrssbsy
和setssbsy
的操作。
当启用了内核影栈时,权限跃迁会自动加载相应的MSR的值到ssp
寄存器中,但不再会自动地将Busy
位置位。
重入保护
重入保护(Re-Entrancy Protection)被简写为RP。由于RP常用于人品(拼音Ren Pin或Reputation Point声望值)的缩写,故称之为人品机制。
RP机制在EFER
MSR中新增了一个RPE
位标识是否启用RP机制。
异常行为
RP机制新增了一个EXCP_IN_PROG
(即Exception In Progress)这个MSR,用于标记哪个异常正在进行。
由于现行AMD64体系的用于异常的中断向量仅限于0-31这32个入口,故可以猜测这个EXCP_IN_PROG
MSR的高32位是保留的。
RP机制在IDT的段性质中指定第7位为RP位。如图所示:
当该异常发生时,如果启用了RP机制且对应的中断入口的RP位置位时,那么EXCP_IN_PROG
MSR的对应位就会被置位并进入异常。如果这个对应位已经被置位了,则会转换为#DF
异常(双重故障)。如果此时产生的异常本来就是一个#DF
异常,则转换为三重故障(Triple-Fault),处理器直接进入关机状态。
注意只有异常的IDT入口的RP位是有意义的。换言之向量值为32-255的中断是没有重入保护的。
异常帧
RP机制下的压入栈的异常帧的结构也有变化,如下图所示:
可以看到这里新增了两个字节,分别是Exception Information和Exception Vector。前者表示异常的一些信息,其位域在图中也标记出来了;而后者则表示异常的向量值。
值得注意的是,保存向量值并非毫无意义。其作用是为了让iret
指令正确地对EXCP_IN_PROG
MSR进行复位。
不可屏蔽中断
x86体系下的不可屏蔽中断(NMI)用的是IDT的2号中断。虽然NMI不是异常,但是在启用RP机制时,NMI也会把EXCP_IN_PROG
MSR中的第二位置位。
值得注意的是,NMI自身具有屏蔽NMI的能力。换言之,处理器在处理NMI时会暂时屏蔽后来的NMI直到执行了iret
指令后才会解除对NMI的屏蔽。这就造成了一个问题,如果NMI的过程中发生异常,而一般情况下异常也是通过iret
指令实现返回的,那么NMI过程中的异常就会导致异常处理结束时就会导致处理器没有处理完NMI时就解除了对后来NMI的屏蔽。这就会造成OS处理NMI的逻辑错乱,而RP机制正好也弥补了这一缺陷。
当RP机制启用时,处理器在处理NMI时对于NMI的屏蔽不再是仅取决于是否执行了iret
指令,而是依据EXCP_IN_PROG
MSR的第二位。如果OS有特殊需要,可以提前将该位复位以解除NMI屏蔽。
机器检查异常
由于机器检查异常(Machine-Check Exception,简写为#MC
)会将MCG_STAT
MSR中的MCIP
位,在一定程度上#MC
是不可重入的。#MC
的重入会直接导致处理器进入关机状态,和三重故障(Triple-Fault)的效果相似。
如果将IDT中#MC
的RP位置位,则重入的#MC
会像其他异常一样被转换到#DF
。
其他特技
这一套扩展体系还包括了一些其他的非常有用的特技。
跨段加载栈特技
在AMD64体系中,iret
和retf
指令会加载一个新的rsp
的值。在原先的体系中,如果返回的ss
段选择子是一个16位的段,则返回时只修改rsp
的低16位。
这就导致系统软件有必要修改rsp
的高位,否则会泄露OS的一些信息。
新处理器的体系确保整个64位的rsp
的值都会被修改,而非依据ss
段修改rsp
。
启用这个特技不需要特别设置。
调试异常屏蔽特技
当异常、软件中断、远调用、调用门发生于中断阴影中时,#DB
异常会被自动弃置,而不再是屏蔽。如此一来,调试异常不再会导致权限跃迁的时候中断阴影导致调试异常处理函数识别错误的异常时权限。这是针对CVE-2018-8897漏洞加的特技。
启用该特技无需特别设置,但弃置的#DB
异常不针对syscall
指令。需要启用ESC机制才能针对syscall
指令堵上CVE-2018-8897漏洞。
部分加载FS/GS
在支持ESC机制的处理器上,处理器新增了PARTIAL_FS_LOAD
和PARTIAL_GS_LOAD
两个MSR。系统软件可以对这两个MSR的低16位写入fs
或gs
段选择子。
写入会使得处理器检查段选择子的合法性并加载fs
/gs
的段选择子、段性质和段限,但不会加载段基址。
可以把这两个新增的MSR理解为FSBASE
和GSBASE
这两个MSR的对立面。
结语
个人认为AMD64出的白皮书里的规划还是相当不错的,尤其是在我去年详解过CVE-2018-8897漏洞的背景下,读到这份白皮书的我直呼AMD YES!
需要补充的是,除了“跨段加载栈特技”和“调试异常屏蔽特技”之外,其他的都是仅限于64位模式的。(没错,虽然AMD处理器的32位模式也支持syscall
指令,ESC机制也是仅64位模式可用)