Intel CET(控制流执行技术)由两块组成。SS(影子堆栈)和IBT(间接分支跟踪)。如果你需要间接分支到某个地方,而你又不能把一个 endbr64
出于某种原因,你可以抑制单次的IBT。jmp
或 call
教学与 notrack
. 是否有同等的方法来抑制单人的SS。ret
指令?
对于上下文,我在想这将如何与retpolines互动,其中的关键控制流多少有点像 push real_target; call retpoline; pop junk; ret
. 如果没有办法压制SS的那一点 ret
那么,当启用CET时,是否有其他方法让retpolines工作?如果没有,我们有什么选择?我们是否需要为所有东西维护两套二进制包,一套用于需要retpolines的旧CPU,一套用于支持CET的新CPU?如果英特尔的结论是错误的,而我们最终还是需要在他们的新CPU上使用retpolines呢?我们是否要放弃CET来使用它们?
在玩了一下汇编后,我发现你 可以 使用retpolines与CET,但不太理想。下面是如何做的。作为参考,考虑一下这段C代码。
extern void (*fp)(void);
int f(void) {
fp();
return 0;
}
编译它的时候 gcc -mindirect-branch=thunk -mfunction-return=thunk -O3
会产生这样的结果。
f:
subq $8, %rsp
movq fp(%rip), %rax
call __x86_indirect_thunk_rax
xorl %eax, %eax
addq $8, %rsp
jmp __x86_return_thunk
__x86_return_thunk:
call .LIND1
.LIND0:
pause
lfence
jmp .LIND0
.LIND1:
lea 8(%rsp), %rsp
ret
__x86_indirect_thunk_rax:
call .LIND3
.LIND2:
pause
lfence
jmp .LIND2
.LIND3:
mov %rax, (%rsp)
ret
事实证明,只要把thunks修改成这样,就可以让它工作。
__x86_return_thunk:
call .LIND1
.LIND0:
pause
lfence
jmp .LIND0
.LIND1:
push %rdi
movl $1, %edi
incsspq %rdi
pop %rdi
lea 8(%rsp), %rsp
ret
__x86_indirect_thunk_rax:
call .LIND3
.LIND2:
pause
lfence
jmp .LIND2
.LIND3:
push %rdi
rdsspq %rdi
wrssq %rax, (%rdi)
pop %rdi
mov %rax, (%rsp)
ret
通过使用 incsspq
, rdsspq
和 wrssq
指令,你可以修改影子堆栈来匹配你对真实堆栈的修改。我在测试这些修改后的thunks时,使用了 英特尔SDE他们确实让控制流的错误消失了。
这是个好消息。这里是坏消息。
endbr64
在不支持CET的CPU上,我在thunks中使用的CET指令不是NOPs(它们的结果是 SIGILL
). 这意味着你需要两套不同的thunks,你需要使用CPU调度来根据CET是否可用来选择合适的thunks。__x86_indirect_thunk_rax
验明正身 endbr64
指令,但那真的很不优雅,而且可能会很慢。