是否可以暂时抑制单个 ret 指令的英特尔 CET,或者以其他方式使用 retpolines?

Is it possible to temporarily suppress Intel CET for a single ret instruction, or otherwise use retpolines with it?

Intel CET(控制流执行技术)由两部分组成:SS(影子堆栈)和 IBT(间接分支跟踪)。如果您需要间接分支到由于某种原因不能放置 endbr64 的某个地方,您可以使用 notrack 抑制单个 jmpcall 指令的 IBT。对于单个 ret 指令,是否有等效的方法来抑制 SS?

对于上下文,我正在考虑这将如何与 retpolines 交互,retpolines 的关键控制流程或多或少类似于 push real_target; call retpoline; pop junk; ret。如果没有办法为 ret 抑制 SS,那么在启用 CET 时是否有其他方法可以让 retpolines 工作?如果没有,我们会有什么选择?我们是否需要为所有东西维护两套二进制包,一套用于需要 retpolines 的旧 CPU,一套用于支持 CET 的新 CPU?如果事实证明英特尔是错误的,我们最终仍然需要在他们的新 CPU 上进行 retpolines 怎么办?我们是否必须放弃 CET 才能使用它们?

玩了一会儿程序集后,我发现您可以将 retpolines 与 CET 一起使用,但这不太理想。这是如何做。作为参考,考虑这个 C 代码:

extern void (*fp)(void);

int f(void) {
    fp();
    return 0;
}

Compiling it with gcc -mindirect-branch=thunk -mfunction-return=thunk -O3 产生这个:

f:
        subq    , %rsp
        movq    fp(%rip), %rax
        call    __x86_indirect_thunk_rax
        xorl    %eax, %eax
        addq    , %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    , %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

通过使用 incsspqrdsspqwrssq 指令,您可以修改影子堆栈以匹配您对真实堆栈的更改。我用 Intel SDE 测试了那些修改过的 thunk,它们确实使控制流错误消失了。

这是个好消息。坏消息是:

  1. endbr64 不同,我在 thunk 中使用的 CET 指令不是 CPU 上不支持 CET 的 NOP(它们导致 SIGILL)。这意味着您需要两组不同的 thunk,并且您需要使用 CPU dispatch 来根据 CET 是否可用来选择正确的。
  2. 完全使用 retpolines 意味着您不再进行任何间接分支,因此虽然您仍然可以获得 SS 的好处,但您已经完全否定了 IBT。我想你可以通过让 __x86_indirect_thunk_rax 检查是否存在 endbr64 指令来解决这个问题,但这真的很不优雅,而且可能会很慢。