唐凌 发表于 2021-8-1 20:01:32

【处理器】AMD64体系的逃跑机制和保留SSS机制以及人品机制

本帖最后由 tangptr@126.com 于 2021-8-2 13:48 编辑


# 前言
去年我在论坛上发过(https://www.0xaa55.com/thread-25993-1-1.html),而今年的二月,AMD发布了有关[增强系统调用的白皮书](https://www.amd.com/system/files/TechDocs/57115.pdf)。
这篇白皮书英文名为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`指令会额外执行以下行为:
1. 自动将`gs`的段基址与`KernelGsBase`MSR的值相互切换,相当于自动执行了`swapgs`指令。
2. 将`STSTAR`的值加载到`rsp`寄存器里。刚才提过了。
3. 如果处理器启用了内核模式的影栈(即`S_CET`MSR中的第零位被置位),则将`PL0_SSP`MSR的值加载到`ssp`影栈寄存器。但不会自动地将其`Busy`位置位。这一条结合RSSS机制使用效果最佳。
4. 执行`syscall`指令会在新的栈上填充`syscall`指令执行时的下一条指令的`rip`寄存器,以及当时的`cs`和`ss`段选择子和`rsp`寄存器的值。
5. 当`syscall`指令位于中断阴影之中时(Interrupt Shadow)时,挂起的调试异常会被弃置。所谓的中断阴影,指的是特定条件导致中断被暂时屏蔽,直到执行了下一条指令才会解除屏蔽的现象。这包括了我之前提到过的(https://www.0xaa55.com/thread-26223-1-1.html)中的`mov ss,xx`或者`pop ss`指令造成的中断屏蔽。那很明显,ESC机制的这一条就是冲着CVE-2018-8897漏洞去的。

当ESC启用的时候,`sysret`指令会执行以下额外的行为:
1. 根据`syscall`额外行为的第四条,从内核栈上还原用户模式`rsp`和`rip`寄存器的值。
2. 根据`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`异常会被自动弃置,而不再是屏蔽。如此一来,调试异常不再会导致权限跃迁的时候中断阴影导致调试异常处理函数识别错误的异常时权限。这是针对(https://www.0xaa55.com/thread-26223-1-1.html)加的特技。
启用该特技无需特别设置,但弃置的`#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漏洞](https://www.0xaa55.com/thread-26223-1-1.html)的背景下,读到这份白皮书的我直呼AMD YES!
需要补充的是,除了“跨段加载栈特技”和“调试异常屏蔽特技”之外,其他的都是仅限于64位模式的。(没错,虽然AMD处理器的32位模式也支持`syscall`指令,ESC机制也是仅64位模式可用)

完玩 发表于 2022-1-10 14:23:50

666, 楼主阅读白皮书很认真啊,我都看不下去:L
页: [1]
查看完整版本: 【处理器】AMD64体系的逃跑机制和保留SSS机制以及人品机制