Об уязвимостях RIDL и «переигровке» нагрузок - PullRequest
2 голосов
/ 17 мая 2019

Я пытаюсь понять класс уязвимости RIDL .

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

Связанный документ в основном сфокусирован на LFB.

Я не понимаю, почему процессор будет удовлетворять нагрузку с устаревшими данными в LFB.
Я могу себе представить, что если нагрузка попадает в L1d, она внутренне «воспроизводится», пока L1d не перенесет данные в LFB, сигнализирующий ядру OoO прекратить «воспроизведение» (так как считанные данные теперь действительны).

Однако я не уверен, что на самом деле означает «повтор».
Я думал, что нагрузки были отправлены на порт с возможностью загрузки, а затем записаны в буфере загрузки (в MOB) и в конечном итоге удерживались по мере необходимости, пока их данные не станут доступны (как сигнализируется L1).
Так что я не уверен, как «воспроизведение» вступает в игру, кроме того, чтобы RIDL работал, каждая попытка «воспроизвести» загрузку также должна разблокировать зависимые инструкции.
Это кажется мне странным, поскольку ЦПУ необходимо будет отслеживать, какие инструкции воспроизводить после правильного завершения загрузки.

В документе по RIDL этот код используется в качестве примера (к сожалению, мне пришлось вставить его как изображение, поскольку макет PDF не позволил мне его скопировать):

RIDL snippet

Единственная причина, по которой это может сработать, это то, что ЦП сначала удовлетворит нагрузку в строке 6 устаревшими данными, а затем воспроизведет ее.
Это подтверждается несколькими строками ниже:

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

Но я бы ожидал, что ЦП проверит адрес загрузки перед пересылкой данных в LFB (или любой другой внутренний буфер).
Если процессор фактически не выполняет загрузку несколько раз, пока не обнаружит, что загруженные данные теперь действительны (то есть воспроизводятся).
Но, опять же, почему каждая попытка разблокирует зависимые инструкции?

Как именно работает механизм воспроизведения, если он вообще существует, и как он взаимодействует с уязвимостями RIDL?

Ответы [ 2 ]

3 голосов
/ 18 мая 2019

Я не думаю, что повторные загрузки с RS связаны с атаками RIDL.Поэтому вместо того, чтобы объяснять, что такое повторы загрузки (ответ @ Питера - хорошая отправная точка для этого), я расскажу о том, что, по моему мнению, происходит, основываясь на моем понимании информации, представленной в документе RIDL, анализ Intel этих уязвимостей и соответствующих патентов.

Буферы заполнения строки - это аппаратные структуры в кэше L1D, используемые для хранения запросов памяти, которые отсутствуют в кэше, и запросов ввода-вывода, пока они не будут обслуживаться.Кэшируемый запрос обслуживается, когда требуемая строка кеша заполняется в массив данных L1D.Комбинирующая запись запись обслуживается, когда возникает любое из условий высвобождения буфера, объединяющего запись (как описано в руководстве).Запрос UC или I / O обслуживается, когда он отправляется в кэш L2 (что происходит как можно скорее).

См. Рис. 4 бумаги RIDL .Эксперимент, используемый для получения этих результатов, работает следующим образом:

  • Поток-жертва записывает известное значение в одну ячейку памяти.Тип памяти ячейки памяти: WB, WT, WC или UC.
  • Поток-жертва считывает одну и ту же ячейку памяти в цикле.За каждой операцией загрузки следует MFENCE, и дополнительно указывается CLFLUSH.Из бумаги мне не ясно порядок CLFLUSH относительно двух других инструкций, но это, вероятно, не имеет значения.MFENCE сериализует операцию очистки строки кэша, чтобы увидеть, что происходит, когда каждая загрузка пропускается в кэше.Кроме того, MFENCE уменьшает конкуренцию между двумя логическими ядрами на портах L1D, что повышает пропускную способность атакующего.
  • Поток злоумышленника, работающий на логическом ядре одного уровня, выполняет код, показанный в листинге 1, впетля.Адрес, используемый в строке 6, может быть любым.Единственное, что имеет значение, это то, что загрузка в строке 6 либо дает сбой, либо вызывает просмотр страницы, для которого требуется помощь по микрокоду (для установки бита доступа в записи таблицы страниц).Для просмотра страницы также необходимо использовать LFB, и большинство LFB распределяются между логическими ядрами.

Мне не ясно, что представляет ось Y на рисунке 4.Насколько я понимаю, он представляет количество строк из скрытого канала, которые были извлечены в иерархию кэша (строка 10) в секунду, где индекс строки в массиве равен значению, записанному жертвой.

Если ячейка памяти имеет тип WB, когда поток жертвы записывает известное значение в ячейку памяти, строка будет заполнена в кеше L1D.Если ячейка памяти имеет тип WT, когда поток-жертва записывает известное значение в ячейку памяти, строка не будет заполнена в кеше L1D.Однако при первом чтении из строки оно будет заполнено.Таким образом, в обоих случаях и без CLFLUSH большинство загрузок из потока жертвы попадут в кеш.

Когда строка кеша для запроса загрузки достигает кеша L1D, она сначала записывается в LFB, выделенный для запроса.Запрашиваемая часть строки кэша может быть непосредственно передана в буфер загрузки из LFB, не дожидаясь заполнения строки в кэше.Согласно описанию уязвимости MFBDS, в определенных ситуациях устаревшие данные из предыдущих запросов могут быть перенаправлены в загрузочный буфер, чтобы удовлетворить загрузку.В случаях WB и WT (без очистки) данные жертвы записываются не более чем в 2 разных LFB.Страница, идущая из потока атакующего, может легко перезаписать данные жертвы в LFB, после чего поток никогда не найдет их там.Все запросы на загрузку, которые попадают в кэш L1D, не проходят через LFB;для них есть отдельный путь, который мультиплексируется с путем от LFB.Тем не менее, есть некоторые случаи, когда устаревшие данные (шум) от LFB спекулятивно пересылаются на логическое ядро ​​злоумышленника, что, вероятно, происходит от просмотра страниц (и, возможно, от обработчиков прерываний и аппаратных средств предварительной выборки).

Это интересноотметить, что частота пересылки устаревших данных в случаях WB и WT намного ниже, чем во всех других случаях.Это может быть объяснено тем фактом, что пропускная способность жертвы в этих случаях намного выше, и эксперимент может прекратиться раньше.

Во всех других случаях (WC, UC и все типы со сбросом) каждая загрузка пропускаетсяв кеше, и данные должны быть извлечены из основной памяти в буфер загрузки через LFB.Происходит следующая последовательность событий:

  1. Доступы жертвы, попавшие в TLB, поскольку они находятся на одной и той же действительной виртуальной странице.Физический адрес получается из TLB и предоставляется L1D, который выделяет LFB для запроса (из-за пропуска), а физический адрес записывается в LFB вместе с другой информацией, которая описывает запрос загрузки.На данный момент запрос от жертвы находится на рассмотрении в LFB.Поскольку жертва выполняет MFENCE после каждой загрузки, в LFB может быть не более одной невыполненной нагрузки в любой заданный цикл от жертвы.
  2. Злоумышленник, работающий на логическом ядре одноуровневого узла, выполняет загрузкузапрос к L1D и TLB.Каждая загрузка относится к несопоставленной пользовательской странице, поэтому это приведет к ошибке.Когда он пропускает TLB, MMU сообщает буферу загрузки, что загрузка должна быть заблокирована до завершения преобразования адреса.Согласно пункту 26 патента и другим патентам Intel, именно так обрабатываются промахи TLB.Преобразование адресов все еще выполняется, загрузка заблокирована.
  3. Запрос на загрузку от жертвы получает свою строку кэша, которая записывается в LFB с покрытием для загрузки.Часть строки, запрошенная загрузкой, пересылается в MOB, и в то же время строка записывается в кэш L1D.После этого LFB может быть снят с покрытия, но ни одно из полей не очищается (кроме поля, которое указывает, что оно свободно).В частности, данные все еще находятся в LFB.Затем жертва отправляет еще один запрос на загрузку, который также отсутствует в кэше, либо потому, что он не кэшируется, либо потому, что строка кэша была очищена.
  4. Процесс трансляции адреса загрузки атакующего завершен.MMU определяет, что неисправность должна возникать из-за отсутствия физической страницы.Однако неисправность не возникает, пока нагрузка не сработает (когда она достигнет вершины ROB).Неверные переводы не кэшируются в MMU на процессорах Intel.MMU все еще должен сообщить MOB, что перевод завершен, и в этом случае устанавливает ошибочный код в соответствующей записи в ROB.Кажется, что когда ROB видит, что один из мопов имеет действительный код ошибки / помощи, он отключает все проверки, связанные с размерами и адресами этих мопов (и, возможно, все последующие мопы в ROB).Эти проверки больше не имеют значения.Предположительно, отключение этих проверок экономит динамическое потребление энергии.Логика выбытия знает, что когда нагрузка вот-вот выйдет из строя, в любом случае произойдет сбой.В то же время, когда MOB сообщается, что перевод завершен, он, как обычно, воспроизводит загрузку атакующего.На этот раз, однако, некоторый недопустимый физический адрес предоставляется в кэш L1D.Обычно физический адрес необходимо сравнивать со всеми запросами, ожидающими в LFB от одного и того же логического ядра, чтобы убедиться, что логическое ядро ​​видит самые последние значения.Это делается до или параллельно с поиском кэша L1D.Физический адрес не имеет большого значения, потому что логика сравнения отключена.Тем не менее, результаты всех сравнений ведут себя так, как будто результат указывает на успех.Если есть хотя бы один выделенный LFB, физический адрес будет соответствовать некоторому выделенному LFB.Поскольку существует невыполненный запрос от жертвы, и поскольку секрет жертвы, возможно, уже был записан в тот же LFB из предыдущих запросов, та же часть строки кэша, которая технически содержит устаревшие данные и в этом случае (устаревшие данные являетсясекрет), будет перенаправлен атакующему.Обратите внимание, что злоумышленник имеет контроль над смещением в строке кэша и количеством байтов, чтобы получить, но он не может контролировать, какой LFB.Размер строки кэша составляет 64 байта, поэтому только 6 младших разрядов виртуального адреса нагрузки атакующего имеют значение вместе с размером загрузки.Затем злоумышленник использует данные для индексации в своем массиве, чтобы раскрыть секрет, используя атаку на стороне кеш-канала.Такое поведение также объясняет MSBDS, где, очевидно, проверки размера данных и STOP UOP отключены (т. Е. Проверки проходят тривиально).
  5. Позже, неисправная / вспомогательная нагрузка достигает вершины ROB.Нагрузка не снимается, и трубопровод промывается.В случае неисправной нагрузки возникает неисправность.В случае вспомогательной загрузки выполнение возобновляется с той же самой инструкции загрузки, но с помощью, чтобы установить требуемые флаги в структурах подкачки.
  6. Эти шаги повторяются.Но злоумышленник не всегда может раскрыть секрет жертвы.Как видите, должно случиться так, что запрос загрузки от злоумышленника попадет в выделенную запись LFB, которая содержит секрет.LFB, выделенные для обхода страниц и аппаратных средств предварительной выборки, могут затруднить успешную атаку.

Если нагрузка злоумышленника не сработала / не помогла, LFB получат действительные физический адрес от MMU и все проверки, необходимые для правильности, выполнены.Вот почему загрузка должна давать сбой / помощь.

Следующая цитата из статьи обсуждает, как выполнить атаку RIDL в том же потоке:

мы выполняем атаку RIDL без SMTзаписывая значения в нашем собственном потоке и наблюдая значения, которые мы просочились из того же потока.На рисунке 3 показано, что если мы не записываем значения («без жертвы»), мы пропускаем только нули, но когда жертва и злоумышленник работают в одном аппаратном потоке (например, в песочнице), мы пропускаем секретное значение практически во всех случаях..

Я думаю, что в этом эксперименте нет изменений уровня привилегий.Жертва и злоумышленник работают в одном и том же потоке ОС в одном и том же аппаратном потоке.При возврате от жертвы к злоумышленнику в LFB все еще могут оставаться невыполненные запросы (особенно из магазинов).Обратите внимание, что в документе RIDL KPTI включен во всех экспериментах (в отличие от бумаги Fallout).

В дополнение к утечке данных из LFB, MLPDS показывает, что данные также могут быть пропущены из буферов порта загрузки.К ним относятся буферы с разделением строк и буферы, используемые для нагрузок размером более 8 байт (которые, я думаю, необходимы, когда размер загрузочного элемента больше размера загрузочного порта, например, AVX 256b на SnB / IvBкоторые занимают порт на 2 цикла).

Случай WB (без промывки) из рисунка 5 также интересен.В этом эксперименте поток-жертва записывает 4 разных значения в 4 разные строки кэша вместо чтения из одной и той же строки кэша.На рисунке показано, что в случае WB только данные, записанные в последнюю строку кэша, просочились злоумышленнику.Объяснение может зависеть от того, различаются ли строки кэша на разных итерациях цикла, что, к сожалению, не ясно в статье.В документе говорится:

Для ББ без очистки есть сигнал только для последней строки кэша, что говорит о том, что ЦП выполняет объединение записи в одной записи LFB перед сохранением данных вcache.

Как объединить записи в разные строки кэша в одном LFB перед сохранением данных в кэше?Это имеет нулевой смысл.LFB может содержать одну строку кэша и один физический адрес.Просто невозможно объединить такие записи.Может случиться так, что записи WB записываются в LFB, выделенные для их запросов RFO.Когда недопустимый физический адрес передается в LFB для сравнения, данные всегда могут быть предоставлены из LFB, который был выделен последним.Это объясняет, почему утечка только значения, записанного четвертым хранилищем.

Информацию о мерах по снижению уровня MDS см. В Что такое новые атаки MDS и как их можно устранить? .В моем ответе обсуждаются только меры по снижению риска, основанные на обновлении микрокода Intel (не очень интересные «программные последовательности»).


На следующем рисунке показаны уязвимые структуры, использующие спекуляцию данными.

enter image description here

2 голосов
/ 17 мая 2019

replay = снова отправляется с RS (планировщика) .(Это не полный ответ на весь ваш вопрос, только на часть о том, что такое повторы. Хотя я думаю, что это покрывает большую часть этого, включая разблокирование зависимых мопов.)

Оказывается, что кэш-памятьMiss Load не просто сидеть в буфере загрузки и пробуждать зависимые мопы, когда поступают данные.Планировщик должен повторно отправить load uop для фактического чтения данных и обратной записи в физический регистр.(И поместите его в сеть пересылки, где зависимые мопы смогут прочитать его в следующем цикле.)

Таким образом, промах L1 / попадание L2 приведет к удвоенному количеству отправленных загрузок.(Планировщик настроен оптимистично, а L2 - на ядре, поэтому ожидаемая задержка попадания L2 является фиксированной, в отличие от времени для ответа вне ядра. IDK, если планировщик продолжает оптимистично оценивать данные, поступающие в определенное время из L3.)


В документе RIDL представлены некоторые интересные доказательства того, что загруженные мопы действительно напрямую взаимодействуют с LFB, не ожидая размещения входящих данных в L1d и просто читая их оттуда.


Мы можем наблюдать на практике повторы легче всего при нагрузках с разделением строк кэша, потому что повторение этого повторения даже более тривиально, чем пропадание кэша, и требует меньше кода.Значения для uops_dispatched_port.port_2 и port_3 будут примерно в два раза выше для контура, который только разделяет нагрузки.(Я проверял это на практике на Skylake, используя, по сути, тот же цикл и процедуру тестирования, что и в Как я могу точно измерить невыровненную скорость доступа на x86_64 )

Вместо того, чтобы сигнализировать об успешном завершении обратнодля RS, нагрузка, которая обнаруживает разделение (возможно только после вычисления адреса), выполнит загрузку для первой части данных, поместив этот результат в буфер разделения 1 , который будет соединен с даннымииз 2-й строки кэша 2-й раз, когда моп отправляет.(Предполагая, что ни одно время не является пропуском кеша, в противном случае для этого тоже потребуются повторы.)


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

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


Существующие вопросы и ответы с доказательствами повторов UOP на процессорах Intel:


Сноска 1:

Мы знаем, что число ограниченоразделенных буферов;есть счетчик ld_blocks.no_sr для нагрузок, которые останавливаются из-за отсутствия таковой.Я предполагаю, что они находятся в порту загрузки, потому что это имеет смысл.Повторная отправка того же загрузочного мопа отправит его на тот же порт загрузки, потому что мопы назначаются портам во время выпуска / переименования.Хотя, возможно, существует общий пул разделенных буферов.


RIDL:

Оптимистическое планирование является частью механизма, который создает проблему.Более очевидная проблема - позволить выполнению более поздних мопов увидеть внутреннее значение «мусора» из LFB, как в Meltdown.

http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/ даже показывает, что при падении нагрузки в PPro обнаруживаются различные фрагменты микроархитектурного состояния, точно так же, как эта уязвимость, которая все еще существует в последних процессорах.

В Pentium Pro буквально понимают, что «значение нагрузки не имеет значения». Для всех запрещенных нагрузок единица загрузки завершает работу и выдает значение, и это значение представляется различными значениями, взятыми из разных частей процессора. Значение варьируется и может быть недетерминированным. Похоже, что ни одно из возвращаемых значений не является данными памяти, поэтому Pentium Pro, по-видимому, не уязвим к Meltdown.

Распознаваемые значения включают PTE для нагрузки (которая, по крайней мере, в последние годы, сама считается привилегированной информацией), 12-е самое последнее сохраненное значение (очередь хранения имеет 12 записей) и редко - сегмент дескриптор откуда-то.

(Более поздние процессоры, начиная с Core 2, предоставляют значение из кэша L1d; это и есть сама уязвимость Meltdown. Но PPro / PII / PIII не уязвим к Meltdown. По-видимому, уязвим для Вместо этого RIDL атакует.)

Так что это та же философия дизайна Intel, которая подвергает спекулятивное исполнение элементам микроархитектурного состояния.

Устранение нуля в аппаратном обеспечении должно быть легко исправлено; порт загрузки уже знает, что он не был успешным, поэтому маскирование данных загрузки в соответствии с успешным / неудачным выполнением должно, надеюсь, добавить только пару дополнительных задержек шлюза и быть возможным без ограничения тактовой частоты. (Если только последний этап конвейера в порту загрузки уже был критическим путем для частоты ЦП.)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...