在玩了一会儿程序集之后,我发现您可以将 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
事实证明,您只需将 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
和wrssq
指令,您可以修改影子堆栈以使您的更改与实际堆栈相匹配。我用Intel SDE测试了那些修改过的 thunk ,它们确实使控制流错误消失了。
那是个好消息。这是坏消息:
- 与 不同
endbr64
,我在 thunk 中使用的 CET 指令不是不支持 CET 的 CPU 上的 NOP(它们导致SIGILL
)。这意味着您需要两组不同的 thunk,并且您需要使用 CPU 调度来根据 CET 是否可用来选择正确的。
- Using retpolines at all means that you're no longer doing any indirect branches, so while you'll still get the benefit of SS, you've completely negated IBT. I suppose you could work around this by making
__x86_indirect_thunk_rax
check for the presence of the endbr64
instruction, but that's really inelegant and would probably be really slow.