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

Assembly 是否可以暂时抑制单个ret指令的英特尔CET,或以其他方式使用retpolines?,assembly,x86-64,intel,spectre,Assembly,X86 64,Intel,Spectre,Intel CET(控制流强制技术)由两部分组成:SS(阴影堆栈)和IBT(间接分支跟踪)。如果由于某种原因需要间接地分支到不能放置endbr64的地方,可以使用notrack抑制单个jmp或call指令的IBT。对于单个ret指令,是否有等效的方法来抑制SS 对于上下文,我正在考虑这将如何与retpoline交互,retpoline的关键控制流或多或少类似于push real\u target;叫雷托林;流行垃圾;ret。如果没有一种方法可以抑制该ret的SS,那么当CET启用时,是否有其他方

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


对于上下文,我正在考虑这将如何与retpoline交互,retpoline的关键控制流或多或少类似于
push real\u target;叫雷托林;流行垃圾;ret
。如果没有一种方法可以抑制该
ret
的SS,那么当CET启用时,是否有其他方法可以使retpolines工作?如果没有,我们有什么选择?我们是否需要为所有内容维护两套二进制软件包,一套用于需要重新部署的旧CPU,另一套用于支持CET的新CPU?如果英特尔最终证明是错误的,而我们最终仍然需要在他们的新CPU上重新部署呢?我们必须放弃CET才能使用它们吗?

在玩了一段组装之后,我发现你可以将Retpoline与CET一起使用,但这并不理想。这是怎么做的。作为参考,请考虑这个C代码:

外部无效(*fp)(无效);
int f(无效){
fp();
返回0;
}
结果如下:

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,它们确实消除了控制流错误

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

  • endbr64
    不同,我在thunks中使用的CET指令不是不支持CET的CPU上的NOP(它们导致
    SIGILL
    )。这意味着您需要两组不同的Thunk,并且您需要使用CPU调度来选择正确的Thunk,这取决于CET是否可用
  • 使用retpolines意味着你不再做任何间接分支,所以虽然你仍然可以从SS中获益,但你完全否定了IBT。我想您可以通过让
    \uuuux86\uinternal\uthunk\urax
    检查
    endbr64
    指令的存在来解决这个问题,但这确实不雅观,而且可能非常慢

  • R11被调用阻塞,不用于arg传递;对于
    incsspq
    ,您应该能够使用它而不是RDI,即使在像thunks这样的透明包装器中也是如此。(x86-64 SysV有一个不用于传递/返回任何内容的调用clobbered reg的一个原因就是包装器/thunk有一个scratch reg,例如用于惰性动态链接。)@PeterCordes我考虑过这一点,但GCC用于函数调用,就像它用于,我不能100%确定它不会假设r11是通过它们保存下来的。啊,我明白了。我想这就是为什么GCC在它的thunk中使用
    lea
    而不是
    add
    ,所以它甚至不会破坏标志。它确实已经踩到了红色区域,所以它不能在叶子函数中任意使用它作为
    开关。如果您修改GCC以发出此消息,希望您能告诉它它会重击R11。@PeterCordes Linux内核提供了它自己的Thunk和一些调用,但是编译器仍然会生成一些对它们的调用,并且调用方决定将哪个寄存器用于目标地址。(而且,
    GENERATE_THUNK
    就在它的使用位置上方定义。)啊,对了。如果他们纯粹使用内联asm,他们只需要为scratch reg使用一个伪
    “=r”
    输出,或者一个固定的选项,并且不需要为每个可能的选项发出定义。由于Linux是一个独立的内核,它需要定义通常在libgcc中的东西,GCC可以发出对这些东西的引用。