Что такое retpoline и как он работает?

Чтобы смягчить раскрытие памяти ядра или кросс-процесса (Spectre), ядро ​​Linux 1 будет скомпилировано с новой опцией, -mindirect-branch=thunk-extern, введенной в gcc для выполнения косвенных вызовов с помощью так называемого retpoline.

Это, кажется, новый термин, поскольку поиск Google появляется только в очень недавнем использовании (как правило, все в 2018 году).

Что такое retpoline и как это предотвращает недавние атаки раскрытия информации ядра?


1 Это не Linux, однако - аналогичная или идентичная конструкция, по-видимому, используется как часть стратегий предотвращения на других ОС.

Ответ 1

В статье, упомянутой sgbj в комментариях, написанных Google, Пол Тернер объясняет следующее более подробно, но я вкратце расскажу об этом:

Насколько я могу собрать это воедино из ограниченной информации на данный момент, ретполин является возвратным батутом, который использует бесконечный цикл, который никогда не выполняется, чтобы не допустить спекуляции ЦП о цели косвенного прыжка.

Базовый подход можно увидеть в ветке ядра Andi Kleen, решающей эту проблему:

Он вводит новый вызов __x86.indirect_thunk который загружает цель вызова, чей адрес памяти (который я назову ADDR) хранится в верхней части стека, и выполняет переход, используя инструкцию RET. Затем сам блок вызывается с помощью макроса NOSPEC_JMP/CALL, который использовался для замены многих (если не всех) косвенных вызовов и переходов. Макрос просто помещает цель вызова в стек и корректно устанавливает адрес возврата, если необходимо (обратите внимание на нелинейный поток управления):

.macro NOSPEC_CALL target
    jmp     1221f            /* jumps to the end of the macro */
1222:
    push    \target          /* pushes ADDR to the stack */
    jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
    call    1222b            /* pushes the return address to the stack */
.endm

Размещение call в конце необходимо, чтобы, когда косвенный вызов был завершен, поток управления продолжался за использованием макроса NOSPEC_CALL, поэтому его можно было использовать вместо обычного call

Сам Thunk выглядит следующим образом:

    call retpoline_call_target
2:
    lfence /* stop speculation */
    jmp 2b
retpoline_call_target:
    lea 8(%rsp), %rsp 
    ret

Поток управления может немного запутаться, поэтому позвольте мне уточнить:

  • call помещает текущий указатель инструкции (метка 2) в стек.
  • lea добавляет 8 к указателю стека, фактически отбрасывая последнее переданное четырехугольное слово, которое является последним адресом возврата (для метки 2). После этого вершина стека снова указывает на реальный адрес возврата ADDR.
  • ret переходит на *ADDR и сбрасывает указатель стека на начало стека вызовов.

В конце концов, все это поведение практически эквивалентно переходу прямо к *ADDR. Единственное преимущество, которое мы получаем, состоит в том, что предиктор ветвления, используемый для операторов возврата (Return Stack Buffer, RSB), при выполнении инструкции call предполагает, что соответствующий оператор ret перейдет к метке 2.

Часть после метки 2 фактически никогда не выполняется, это просто бесконечный цикл, который теоретически заполнил бы конвейер команд инструкциями JMP. Использование LFENCE, PAUSE или, в более общем случае, инструкции, приводящей к остановке конвейера команд, не позволяет ЦП тратить энергию и время на это умозрительное выполнение. Это потому, что в случае, если вызов retpoline_call_target будет возвращаться нормально, LFENCE будет следующей инструкцией, которая будет выполнена. Это также то, что предсказатель ветвления будет предсказывать на основе исходного адреса возврата (метка 2).

Цитировать из руководства по архитектуре Intel:

Инструкции, следующие за LFENCE, могут быть извлечены из памяти до LFENCE, но они не будут выполняться, пока LFENCE не завершится.

Однако обратите внимание, что в спецификации никогда не упоминается, что LFENCE и PAUSE приводят к остановке конвейера, поэтому я читаю немного между строк здесь.

Теперь вернемся к исходному вопросу: раскрытие информации о памяти ядра возможно благодаря комбинации двух идей:

  • Даже если спекулятивное выполнение должно быть свободным от побочных эффектов, если спекуляция была неправильной, спекулятивное выполнение все еще влияет на иерархию кэша. Это означает, что, когда загрузка памяти выполняется спекулятивно, это все же могло привести к удалению строки кэша. Это изменение в иерархии кеша может быть идентифицировано путем тщательного измерения времени доступа к памяти, которая сопоставлена с тем же набором кеша.
    Вы даже можете потерять некоторые биты произвольной памяти, когда адрес источника чтения памяти сам считывался из памяти ядра.

  • Косвенный предсказатель ветвления процессоров Intel использует только самые младшие 12 битов исходной команды, поэтому легко отравить все 2 ^ 12 возможных историй предсказания с адресами памяти, управляемыми пользователем. Затем они могут, когда в ядре прогнозируется косвенный переход, спекулятивно выполняться с привилегиями ядра. Используя побочный канал тайминга кеша, вы можете утечь произвольную память ядра.

ОБНОВЛЕНИЕ: В списке рассылки ядра идет постоянное обсуждение, которое наводит меня на мысль, что retpolines не полностью смягчают проблемы предсказания ветвлений, как, например, когда буфер возврата стека (RSB) работает пусто, более поздние архитектуры Intel (Skylake+) отступите к уязвимому целевому буферу ветвления (BTB):

Ретполин как стратегия смягчения меняет косвенные ответвления на возвраты, чтобы избежать использования прогнозов, исходящих от BTB, так как они могут быть отравлены злоумышленником. Проблема с Skylake+ заключается в том, что недостаточное значение RSB возвращается к использованию предсказания BTB, которое позволяет злоумышленнику получить контроль над спекуляцией.

Ответ 2

Ретполин предназначен для защиты от эксплойта с целевой инъекцией (CVE-2017-5715). Это атака, когда косвенная инструкция ветвления в ядре используется для принудительного выполнения произвольного фрагмента кода. Выбранный код является "гаджетом", который так или иначе полезен для злоумышленника. Например, можно выбрать код, чтобы утечка данных ядра влияла на кеш. Retpoline предотвращает этот эксплойт, просто заменяя все косвенные инструкции ветвления инструкцией возврата.

Я думаю, что ключ к retpoline - это просто часть "ret", которая заменяет непрямую ветвь инструкцией возврата, так что CPU использует предиктор стека возврата вместо эксплуатируемого предиктора ветвления. Если вместо этого использовать простую инструкцию push и return, то код, который будет спекулятивно выполняться, будет кодом, в который функция в конечном итоге вернется, а не каким-нибудь гаджетом, полезным для злоумышленника. Основным преимуществом батутной части, похоже, является поддержание стека возврата, поэтому, когда функция действительно возвращает своего вызывающего, это предсказывается правильно.

Основная идея внедрения цели в ветвь проста. Он использует тот факт, что ЦП не записывает полный адрес источника и места назначения ветвей в своих целевых буферах ветвей. Таким образом, злоумышленник может заполнить буфер, используя переходы в своем собственном адресном пространстве, что приведет к предсказанным попаданиям при выполнении определенного косвенного перехода в адресном пространстве ядра.

Обратите внимание, что retpoline не предотвращает раскрытие информации о ядре напрямую, а только предотвращает использование косвенных инструкций ветвления для спекулятивного выполнения гаджета, который раскрыл бы информацию. Если злоумышленник может найти другие способы для умозрительного выполнения гаджета, то ретполина не предотвратит атаку.

В статье " Атаки призрака: использование спекулятивного исполнения " Пола Кохера, Даниэля Генкина, Даниэля Грусса, Вернера Хааса, Майка Гамбурга, Морица Липпа, Стефана Мангарда, Томаса Прешера, Майкла Шварца и Ювала Ярома дается следующий обзор того, как могут использоваться косвенные ветки.:

Использование косвенных веток. Рисуя из ориентированного на возврат программирования (ROP), в этом методе злоумышленник выбирает гаджет из адресного пространства жертвы и влияет на жертву для умозрительного выполнения гаджета. В отличие от ROP, злоумышленник не полагается на уязвимость в коде жертвы. Вместо этого злоумышленник обучает целевой буфер ветвления (BTB) неправильно предсказывать переход от косвенной инструкции ветвления к адресу гаджета, что приводит к спекулятивному выполнению гаджета. В то время как спекулятивно выполненные инструкции отменяются, их влияние на кеш не отменяется. Эти эффекты могут использоваться гаджетом для утечки конфиденциальной информации. Мы покажем, как при тщательном выборе гаджета этот метод можно использовать для считывания произвольной памяти жертвы.

Чтобы неправильно настроить BTB, злоумышленник находит виртуальный адрес гаджета в адресном пространстве жертвы, а затем выполняет косвенные переходы по этому адресу. Это обучение выполняется из адресного пространства злоумышленников, и не имеет значения, что находится по адресу гаджета в адресном пространстве злоумышленников; все, что требуется, - это то, что ветвь, используемая для обучения ветвей, использует один и тот же виртуальный адрес назначения. (На самом деле, пока злоумышленник обрабатывает исключения, атака может работать, даже если в виртуальном адресе гаджета в адресном пространстве злоумышленника не сопоставлен код.) Также нет необходимости в полном совпадении адреса источника ветки, используемого для обучения, и адрес целевого ветки. Таким образом, злоумышленник обладает значительной гибкостью в настройке обучения.

Запись в блоге, озаглавленная " Чтение привилегированной памяти с побочным каналом" командой Project Zero в Google, предоставляет еще один пример того, как внедрение целевого объекта ветвления можно использовать для создания рабочего эксплойта.

Ответ 3

Этот вопрос был задан некоторое время назад, и заслуживает более нового ответа.

Резюме:

Последовательности "Retpoline" - это программная конструкция, которая позволяет изолировать непрямые ветки от спекулятивного исполнения. Это может применяться для защиты чувствительных двоичных файлов (таких как реализации операционной системы или гипервизора) от атак внедрения целевых ветвей против их косвенных ветвей.

Слово " RET Полине " является контаминация слов "возвращение" и "батут", так же, как улучшение " отн Полине " был придуман от "относительного вызова" и "трамплин". Это батутная конструкция, построенная с использованием операций возврата, которая также образно гарантирует, что любое связанное с ней умозрительное выполнение будет бесконечно "подпрыгивать".

Чтобы предотвратить раскрытие памяти ядром или межпроцессным процессом (атака Spectre), ядро Linux [1] будет скомпилировано с новой опцией -mindirect-branch=thunk-extern введенной в gcc для выполнения косвенных вызовов через так называется ретполин.

[1] Однако, это не специфично для Linux - похоже, что аналогичная или идентичная конструкция используется как часть стратегий смягчения в других ОС.

Использование этого параметра компилятора защищает только от Spectre V2 в уязвимых процессорах, для которых требуется обновление микрокода, необходимое для CVE-2017-5715. Он будет "работать" с любым кодом (не только с ядром), но стоит атаковать только код, содержащий "секреты".

Похоже, что это новый изобретенный термин, поскольку поиск в Google обнаруживает только очень недавнее использование (как правило, все в 2018 году).

Компилятор LLVM имеет переключатель -mretpoline с 4 января 2018 года. Это дата, когда уязвимость была впервые опубликована. GCC выпустила свои патчи 7 января 2018 года.

Дата CVE предполагает, что уязвимость была "обнаружена" в 2017 году, но она затрагивает некоторые процессоры, изготовленные за последние два десятилетия (таким образом, она, вероятно, была обнаружена давно).

Что такое ретполин и как он предотвращает недавние атаки на раскрытие информации в ядре?

Сначала несколько определений:

  • Батут - Иногда упоминаемые как косвенные векторы прыжков батуты - это области памяти, содержащие адреса, указывающие на процедуры обработки прерываний, процедуры ввода-вывода и т.д. Выполнение прыгает в батут, а затем сразу же выпрыгивает или отскакивает, отсюда и термин батут. GCC традиционно поддерживает вложенные функции, создавая исполняемый батут во время выполнения, когда берется адрес вложенной функции. Это небольшой фрагмент кода, который обычно находится в стеке, в кадре стека содержащей функции. Батут загружает статический цепной регистр, а затем переходит к реальному адресу вложенной функции.

  • Thunk - Thunk - это подпрограмма, используемая для добавления дополнительных вычислений в другую подпрограмму. Thunks в основном используются для задержки вычисления до тех пор, пока не понадобится его результат, или для вставки операций в начало или конец другой подпрограммы.

  • Memoization - запоминаемая функция "запоминает" результаты, соответствующие некоторому набору определенных входных данных. Последующие вызовы с запомненными входами возвращают запомненный результат, а не пересчитывают его, тем самым устраняя первичную стоимость вызова с заданными параметрами из всех, кроме первого вызова функции с этими параметрами.

Грубо говоря, ретполин - это батут с возвратом в качестве снаряда, чтобы "испортить" запоминание в косвенном предсказателе ветвления.

Источник: retpoline включает инструкцию PAUSE для Intel, но инструкция AMD LFENCE необходима для AMD, так как на этом процессоре инструкция PAUSE не является командой сериализации, поэтому цикл pause/jmp будет использовать избыточную мощность, так как предполагается, что он ожидает возврата неправильно прогнозировать правильную цель.

У Arstechnica есть простое объяснение проблемы:

"Каждый процессор имеет архитектурное поведение (документированное поведение, описывающее, как работают инструкции и от которого программисты зависят при написании своих программ) и микроархитектурное поведение (поведение реальной реализации архитектуры). Они могут незначительно отличаться. Например, архитектурно, программа, которая загружает значение из определенного адреса в памяти, будет ждать, пока адрес не станет известен, прежде чем пытаться выполнить загрузку, однако, микроархитектурно процессор может попытаться спекулятивно угадать адрес, чтобы он мог начать загрузка значения из памяти (что происходит медленно) даже до того, как он абсолютно точно определит, какой адрес ему следует использовать.

Если процессор угадает неправильно, он проигнорирует предполагаемое значение и выполнит загрузку снова, на этот раз с правильным адресом. Архитектурно определенное поведение, таким образом, сохраняется. Но это ошибочное предположение нарушит другие части процессора, в частности содержимое кеша. Эти микроархитектурные нарушения могут быть обнаружены и измерены путем определения времени, необходимого для доступа к данным, которые должны (или не должны) быть в кеше, что позволяет вредоносной программе делать выводы о значениях, хранящихся в памяти ".

Из статьи Intel: " Ретполин: снижение уровня инъекций в ветких " (.PDF):

"Последовательность retpoline не позволяет спекулятивному выполнению процессоров использовать" косвенный предиктор ветвления "(один из способов прогнозирования потока программы), чтобы спекулировать по адресу, управляемому эксплойтом (удовлетворяющий элемент 4 из пяти элементов внедрения цели ветвления (вариант Spectre 2) ) использовать композицию, указанную выше).

Обратите внимание, что элемент 4: "Эксплойт должен успешно влиять на эту косвенную ветвь, чтобы спекулятивно ошибочно прогнозировать и выполнять гаджет. Этот гаджет, выбранный эксплойтом, пропускает секретные данные через побочный канал, как правило, из-за тайминга".

Ответ 4

Что такое ретполин [...]?

Это программная конструкция, предотвращающая спекулятивное выполнение инструкций, которые могут привести к утечке конфиденциальной информации.

[...] и как это предотвращает недавние атаки раскрытия информации ядра?

Retpolines предотвращают спекулятивное выполнение, вставляя "фиктивные" инструкции. Эти инструкции бесполезны для злоумышленника, потому что они не могут утечь конфиденциальную информацию. К сожалению, они добавляют немного накладных расходов на выполнение инструкций.

Примечание с одной стороны: информация о ядре не может быть получена с помощью этого метода, потому что мы можем перейти только к тем местам команд, которые действительны в пользовательском процессе.