Что такое ретполин и как он работает?

244

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

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

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


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

BeeOnRope
источник
6
Интересная статья поддержки от Google.
sgbj
2
о, так это произносится как / ˌtræmpəˈlin / (американский) или / ˈtræmpəˌliːn / (британский)
Уолтер Тросс
2
Вы могли бы упомянуть, что это ядро Linux, хотя gccуказывает на это! Я не узнал lkml.org/lkml/2018/1/3/780 как на сайте списка рассылки ядра Linux, даже когда я туда заглянул (и мне предоставили снимок, так как он был отключен).
PJTraill
@PJTraill - добавлен тег ядра Linux
RichVel
@PJTraill - хорошая мысль, я обновил текст вопроса. Обратите внимание, что я впервые увидел его в ядре Linux из-за его относительно открытого процесса разработки, но, без сомнения, те же или аналогичные методы используются в качестве мер по смягчению воздействия на весь спектр операционных систем с открытым и закрытым исходным кодом. Так что я не считаю это специфичным для Linux, но ссылка, безусловно, есть.
BeeOnRope

Ответы:

158

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

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

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

Он вводит новый __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, которое позволяет атакующему контролировать спекуляции.

Тобиас Рибизель
источник
Я не думаю, что инструкция LFENCE важна, вместо этого в реализации Google используется инструкция PAUSE. support.google.com/faqs/answer/7625886 Обратите внимание, что в документации, которую вы цитируете, сказано: «не будет выполнено», не будет «не будет выполнено спекулятивно».
Росс Ридж
1
С этой страницы часто задаваемых вопросов Google: «Инструкции паузы в наших спекулятивных циклах выше не требуются для корректности. Но это означает, что непроизводительное спекулятивное выполнение занимает меньше функциональных единиц на процессоре». Так что это не подтверждает ваш вывод о том, что LFENCE являются для них ключевыми.
Росс Ридж
@RossRidge Я частично согласен, для меня это выглядит как две возможные реализации бесконечного цикла, которые намекают ЦПУ на то, чтобы не спекулятивно выполнять код, следующий за PAUSE / LFENCE. Однако, если LFENCE был выполнен спекулятивно и не откатился, потому что предположение было верным, это противоречило бы утверждению, что оно будет выполнено только после завершения загрузки памяти. (В противном случае весь набор инструкций, которые были выполнены спекулятивно, пришлось бы откатить и выполнить снова, чтобы выполнить спецификации)
Тобиас Рибизель
1
Это имеет то преимущество , что push/ retчто делает не разбалансировать обратный адрес стека предсказателя. Есть один неверный прогноз (переход к тому, что используется lfenceдо фактического обратного адреса), но использование callмодификации + rspуравновешивает это ret.
Питер Кордес
1
упс, преимущество над push / ret(в моем последнем комментарии). re: ваше редактирование: потеря значения RSB должна быть невозможной, потому что ретполин включает в себя call. Если бы приоритетное ядро ​​переключало контекст там, мы возобновили бы выполнение с RSB, начатым из callв планировщик. Но, возможно, обработчик прерываний может закончиться с достаточным количеством rets, чтобы очистить RSB.
Питер Кордес
46

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

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

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

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

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

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

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

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

Росс Ридж
источник
9

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

Резюме :

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

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

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

[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: «Эксплойт должен успешно влиять на эту косвенную ветвь, чтобы спекулятивно ошибочно прогнозировать и выполнять гаджет. Этот гаджет, выбранный эксплойтом, пропускает секретные данные через побочный канал, как правило, с использованием таймера».

обкрадывать
источник