Можно ли временно подавить Intel CET для одной инструкции ret или иным образом использовать с ней ретполины? - PullRequest
3 голосов
/ 17 июня 2020

Intel CET (технология принудительного выполнения потока управления) состоит из двух частей: SS (теневой стек) и IBT (косвенное отслеживание переходов). Если вам нужно косвенно перейти к тому месту, где по какой-то причине вы не можете поместить endbr64, вы можете подавить IBT для одной инструкции jmp или call с помощью notrack. Есть ли эквивалентный способ подавить SS для одной инструкции ret?

Для контекста, я думаю о том, как это будет взаимодействовать с ретполиниями, которые поток управления ключами более или менее похож на push real_target; call retpoline; pop junk; ret. Если нет способа подавить SS для этого ret, то есть ли другой способ для работы ретполинов при включенном CET? Если нет, то какие варианты у нас будут? Нужно ли будет поддерживать два набора бинарных пакетов для всего: один для старых процессоров, которым нужны ретполины, и один для новых процессоров, поддерживающих CET? А как насчет того, чтобы Intel ошиблась, и нам все еще нужны ретполины на их новых процессорах? Придется ли нам отказаться от CET, чтобы использовать их?

1 Ответ

0 голосов
/ 18 июня 2020

Немного поиграв со сборкой, я обнаружил, что вы можете использовать ретполины с 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

Оказывается, вы можете сделать это работать, просто изменяя преобразователи, чтобы они выглядели так:

__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 , и они действительно устранили ошибки потока управления go.

Это были хорошие новости. Вот плохие новости:

  1. В отличие от endbr64, инструкции CET, которые я использовал в преобразователях, не являются NOP на процессорах, которые не поддерживают CET (они приводят к SIGILL). Это означает, что вам понадобятся два разных набора преобразователей, и вам нужно будет использовать диспетчерскую диспетчеризацию ЦП, чтобы выбрать нужные, в зависимости от того, доступен ли CET.
  2. Использование ретполинов вообще означает, что вас больше нет выполняя любые непрямые ветки, поэтому, хотя вы по-прежнему получаете преимущества SS, вы полностью отрицаете IBT. Я полагаю, вы могли бы обойти это, сделав __x86_indirect_thunk_rax проверку на наличие инструкции endbr64, но это действительно неэлегантно и, вероятно, будет очень медленным.
...