在玩了一下程序集之后,我发现你can将 retpolines 与 CET 一起使用,但不太理想。就是这样。作为参考,请考虑以下 C 代码:
extern void (*fp)(void);
int f(void) {
fp();
return 0;
}
编译它gcc -mindirect-branch=thunk -mfunction-return=thunk -O3 https://godbolt.org/z/Q88aS5产生这个:
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
事实证明,您只需将 thunk 修改为如下所示即可实现此目的:
__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
, and wrssq
根据说明,您可以修改影子堆栈以匹配对真实堆栈的更改。我测试了那些修改后的重击英特尔SDE https://software.intel.com/content/www/us/en/develop/articles/intel-software-development-emulator.html,它们确实使控制流错误消失了。
这是个好消息。这是坏消息:
- Unlike
endbr64
,我在 thunk 中使用的 CET 指令在不支持 CET 的 CPU 上不是 NOP(它们会导致SIGILL
)。这意味着您需要两组不同的 thunk,并且需要使用 CPU 调度来根据 CET 是否可用来选择正确的 thunk。
- 使用 retpolines 意味着您不再执行任何间接分支,因此虽然您仍然可以获得 SS 的好处,但您已经完全否定了 IBT。我想你可以通过以下方式解决这个问题
__x86_indirect_thunk_rax
检查是否存在endbr64
指令,但这真的很不优雅,而且可能会很慢。