Meltdown и Spectre
Хотя в настоящее время я в основном известен сетями и распределенными системами на уровне приложений, первую часть своей карьеры я посвятил работе с операционными системами и гипервизорами. Я глубоко увлечен деталями низкого уровня того, как работают современные процессоры и системное программное обеспечение. Когда были объявлены недавние уязвимости “Meltdown и Spectre” , я рылся в доступной информации и хотел узнать больше.
Уязвимости поразительны… Я бы сказал, что они одно из самых важных открытий в области компьютерных наук за последние 10–20 лет. Смягчения также трудно понять, и точную информацию о них трудно найти. Это неудивительно, учитывая их “Критическую природу”. Для устранения этих уязвимостей потребовались месяцы секретной работы со стороны всех основных поставщиков процессоров, операционных систем и облачных вычислений. Удивляет тот факт, что всё держалось в секрете в течение 6 месяцев, когда над ними, вероятно, работали буквально сотни людей.
Хотя с момента их разглашения многое было написано о “Meltdown и Spectre”, я так и не увидел информацию приемлемого уровня, посвященного уязвимостям исмягчениям. В этой статье я попытаюсь исправить это, предоставив краткое введение в аппаратную и программную среду, необходимую для понимания уязвимостей, обсуждение самих уязвимостей, а также текущих мер по их снижению.
Важное примечание : поскольку я не работал непосредственно над смягчениями, не работаю в Intel, Microsoft, Google, Amazon, Red Hat и т. д., Некоторые детали, которые я собираюсь предоставить, могут быть не совсем точными. Я собрал воедино эту статью на основе моих знаний о том, как работают эти системы, общедоступной документации / обсуждений, опубликованных в LKML и xen-devel . Был бы рад, чтобы меня исправили, если какой-либо из этих постов будет неточным, хотя я сомневаюсь, что это произойдет в ближайшее время, учитывая, сколько из этого предмета все еще рассматривается NDA. (Подсказать что-либо автору можно здесь: https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2 )
Введение
В этом разделе я приведу некоторые сведения, необходимые для понимания уязвимостей. Раздел содержит большое количество деталей, которые предназначены для читателей с ограниченным пониманием компьютерного оборудования и системного программного обеспечения.
Виртуальная память
Виртуальная память - это метод, используемый во всех операционных системах с 1970-х годов. Он обеспечивает уровень абстракции между схемой адресации памяти, которую видит большинство программ, и физическими устройствами, поддерживающими эту память (ОЗУ, диски и т. п.). На надлежащем уровне это позволяет приложениям использовать больше памяти, чем на самом деле имеет машина.Это обеспечивает мощную абстракцию, которая облегчает многие задачи программирования.
Рис.1 Виртуальная память
На рисунке 1 показан упрощенный компьютер с 400 байтами памяти, разложенными на «страницы» по 100 байтов (реальные компьютеры используют степень двух, обычно 4096). Компьютер имеет два процесса, каждый с 200 байтами памяти на 2 страницах каждый. Процессы могут запускать один и тот же код, используя фиксированные адреса в диапазоне байтов 0–199, однако они поддерживаются дискретной физической памятью, так что они не влияют друг на друга. Хотя современные ОС и компьютеры используют виртуальную память существенно более сложным образом, чем то, что представлено в этом примере, основная предпосылка, представленная выше, сохраняется во всех случаях. Операционные системы абстрагируют адреса, которые приложения видят от физических ресурсов, которые их поддерживают.
Преобразование виртуальных адресов в физические является такой распространенной операцией на современных компьютерах, что, если бы во всех случаях приходилось использовать ОС, компьютер работал бы невероятно медленно. Современное аппаратное обеспечение ЦП предоставляет устройство, называемое трансляцией Lookaside Buffer (TLB), которое кэширует недавно использованные отображения. Это позволяет центральным процессорам выполнять преобразование адресов непосредственно в аппаратном обеспечении большую часть времени.
Рис. 2 Перевод виртуальной памяти
На рисунке 2 показан поток преобразования адресов:
Программа выбирает виртуальный адрес.
Процессор пытается перевести его, используя TLB. Если адрес найден, используется перевод.
Если адрес не найден, ЦП обращается к набору «таблиц страниц» для определения соответствия. Таблицы страниц - это набор страниц физической памяти, предоставляемых операционной системой в месте, где аппаратное обеспечение может их найти (например, регистр CR3 на оборудовании x86). Таблицы страниц отображают виртуальные адреса на физические адреса, а также содержат метаданные, такие как разрешения.
Если таблица страниц содержит отображение, она возвращается, кэшируется в TLB и используется для поиска. Если таблица страниц не содержит сопоставления, в ОС возникает «ошибка страницы». Отказ страницы - это особый вид прерывания, который позволяет ОС получить контроль и определить, что делать в случае отсутствия или недопустимого отображения. Например, ОС может завершить программу. Это может также выделить некоторую физическую память и отобразить ее в процессе. Если обработчик сбоев страницы продолжает выполнение, TLB будет использовать новое отображение.
Рис. 3 Отображение виртуальной памяти пользователя/ядра
На рисунке 3 показано несколько более реалистичное представление о том, как выглядит виртуальная память в современном компьютере (до распада - подробнее об этом ниже). В данном наборе настроек у нас есть следующие функции:
Память ядра показана красным. Он содержится в диапазоне физических адресов 0–99. Память ядра - это специальная память, к которой должна иметь доступ только операционная система. Пользовательские программы не должны иметь доступа к нему.
Пользовательская память показана серым цветом.
Нераспределенная физическая память показана синим цветом.
В этом примере мы начинаем видеть некоторые полезные функции виртуальной памяти. Прежде всего:
Пользовательская память в каждом процессе находится в виртуальном диапазоне 0–99, но поддерживается другой физической памятью.
Память ядра в каждом процессе находится в виртуальном диапазоне 100–199, но поддерживается той же физической памятью.
Как я кратко упомянул в предыдущем разделе, каждая страница имеет соответствующие биты прав доступа. Даже если память ядра отображается в каждом пользовательском процессе, когда процесс выполняется в пользовательском режиме, он не может получить доступ к памяти ядра. Если процесс попытается сделать это, он вызовет сбой страницы, после чего операционная система прекратит его. Однако, когда процесс выполняется в режиме ядра (Например, во время системного вызова), то процессор разрешит доступ.
На этом этапе я отмечу, что этот тип двойного сопоставления (каждый процесс, в котором ядро подключено непосредственно к нему) уже более тридцати лет является стандартной практикой проектирования операционной системы по соображениям производительности (системные вызовы очень распространены, и это займет много времени. Время переназначения ядра или пространства пользователя при каждом переходе).
Топология кеша процессора
Рис. 4 Топология ядра, процессора, пакета и кэша.
Следующей справочной информацией, необходимой для понимания уязвимостей, является топология процессора и кэша современных процессоров. На рисунке 4 показана общая топология, которая является общей для большинства современных процессоров. Он состоит из следующих компонентов:
Основной единицей выполнения является «поток ЦП», «аппаратный поток» или «гиперпоток». Каждый поток ЦП содержит набор регистров и возможность выполнять поток машинного кода, очень похожий на программный поток.
Потоки ЦП содержатся в «ядре ЦП». Большинство современных ЦП содержат по два потока на ядро.
Современные процессоры обычно содержат несколько уровней кэш-памяти . Уровни кэша ближе к потоку ЦП меньше, быстрее и дороже. Чем дальше от процессора и ближе к основной памяти, тем больше, медленнее и дешевле кэш.
Типичный современный дизайн ЦП использует кэш L1 / L2 на ядро. Это означает, что каждый поток ЦП в ядре использует одни и те же кэши.
Несколько «ядер ЦП» содержатся в «пакете ЦП». Современные ЦП могут содержать более 30 ядер (60 потоков) или более в каждом пакете.
Все ядра ЦП в пакете обычно используют кэш L3.
Пакеты ЦП помещаются в «сокеты». Большинство потребительских компьютеров имеют один сокет, в то время как многие серверы центров обработки данных имеют несколько сокетов.
Спекулятивное исполнение
Рис. 5 Современный процессор исполнения CPU
Последним фрагментом справочной информации, необходимой для понимания уязвимостей, является современная методика ЦП, известная как «спекулятивное выполнение». На рисунке 5 показана общая схема механизма выполнения внутри современного ЦП.
Основной вывод заключается в том, что современные процессоры невероятно сложны и не просто выполняют машинные инструкции по порядку. Каждый поток ЦП имеет сложный механизм конвейерной обработки, способный выполнять команды не по порядку. Причина этого связана с кэшированием. Как я уже говорил в предыдущем разделе, каждый процессор использует несколько уровней кэширования. Каждая ошибка в кэше добавляет значительное время задержки к выполнению программы. Чтобы смягчить это, процессоры способны выполнять ahead и out of order в ожидании загрузки памяти. Это известно как спекулятивное исполнение. Следующий фрагмент кода демонстрирует это.
В предыдущем фрагменте представьте, что array1_size недоступен в кэше, но адрес array1 -. Процессор может угадать (предположить), что x меньше, array1_size продолжит и выполнит вычисления внутри оператора if. После array1_size считывания из памяти процессор может определить, правильно ли он угадан. Если это так, он может продолжать экономить кучу времени. Если это не так, он может отбросить спекулятивные вычисления и начать все сначала. Это не хуже, чем если бы он ждал в первую очередь.
Другой тип спекулятивного исполнения известен как косвенное предсказание ветвления. Это очень распространено в современных программах из-за виртуальной диспетчеризации.
Способ, которым предыдущий фрагмент реализован в машинном коде, состоит в том, чтобы загрузить «Таблицу виртуальных методов» или «таблицу виртуальной отправки» из области памяти, которая obj указывает на, и затем вызвать ее. Поскольку эта операция настолько распространена, современные процессоры имеют различные внутренние кэши и часто угадывают (спекулируют), куда пойдет косвенная ветвь и продолжат выполнение в этот момент. Опять же, если процессор угадает правильно, он может продолжать экономить кучу времени. Если это не так, он может отбросить спекулятивные вычисления и начать все сначала.
Наконец то уязвимости
Теперь, рассмотрев всю справочную информацию, мы можем погрузиться в уязвимости.
Мошенническая загрузка данных
Первая уязвимость, известная как Meltdown, удивительно проста в объяснении и почти тривиальна в использовании. Код эксплойта примерно выглядит следующим образом:
Давайте рассмотрим каждый шаг выше, опишем, что он делает, и как это приводит к возможности чтения памяти всего компьютера из пользовательской программы .
В первой строке выделен «массив зондов». Это память в нашем процессе, которая используется в качестве побочного канала для извлечения данных из ядра. Как это будет сделано, скоро станет понятно.
После выделения злоумышленник следит за тем, чтобы ни одна памятm в массиве зондов не была кэширована. Существуют различные способы сделать это, самый простой из которых включает в себя специфичные для процессора инструкции по очистке области памяти из кэша.
Затем злоумышленник начинает читать байт из адресного пространства ядра. Помните, из нашего предыдущего обсуждения виртуальной памяти и таблиц страниц, что все современные ядра обычно отображают все виртуальное адресное пространство ядра в пользовательский процесс. Операционные системы полагаются на тот факт, что каждая запись таблицы страниц имеет настройки разрешений, и что программы пользовательского режима не имеют доступа к памяти ядра. Любой такой доступ приведет к ошибке страницы. Это действительно то, что в конечном итоге произойдет на шаге 3.
Тем не менее , современные процессоры также выполняют спекулятивное выполнение и будут выполняться перед ошибочной инструкцией. Таким образом, шаги 3–5 могут выполняться в конвейере ЦП до возникновения ошибки. На этом этапе байт памяти ядра (в диапазоне от 0 до 255) умножается на размер страницы системы, который обычно составляет 4096.
На этом этапе умноженный байт памяти ядра затем используется для чтения из массива зондов в фиктивное значение. Умножение байта на 4096 позволяет избежать того, что функция ЦП, называемая «prefetcher», будет читать больше данных, чем мы хотим, в кэш.
На этому шагу ЦП осознал свою ошибку и откатился к шагу 3. Однако результаты спекулятивных инструкций по- прежнему видны в кэше . Атакующий использует функциональные возможности операционной системы, чтобы перехватить ошибочную инструкцию и продолжить выполнение (например, обрабатывая SIGFAULT).
На шаге 7 злоумышленник выполняет итерацию и видит, сколько времени занимает чтение каждого из 256 возможных байтов в массиве зондов, которые могли быть проиндексированы памятью ядра. Процессор загрузит одно из расположений в кэш, и это местоположение будет загружаться значительно быстрее, чем все остальные расположения (которые должны быть прочитаны из основной памяти). Это расположение является значением байта в памяти ядра .
Используя описанную выше технику и тот факт, что для современных операционных систем является стандартной практикой сопоставление всей физической памяти в виртуальное адресное пространство ядра, злоумышленник может прочитать всю физическую память компьютера .
Теперь вы можете задаться вопросом: «Вы сказали, что у таблиц страниц есть биты прав доступа. Как могло случиться, что код пользовательского режима смог спекулятивно получить доступ к памяти ядра? » . Причина в том, что это ошибка в процессорах Intel. На мой взгляд, нет веских причин, производительности или иного, чтобы это было возможно. Напомним, что весь доступ к виртуальной памяти должен осуществляться через TLB. Во время спекулятивного выполнения легко можно проверить, что кэшированное отображение имеет разрешения, совместимые с текущим уровнем привилегий. Аппаратное обеспечение Intel просто не делает этого. Другие производители процессоров выполняют проверку разрешений и блокируют спекулятивное выполнение. Таким образом, насколько нам известно, Meltdown является уязвимостью только для Intel .
Изоляция таблицы страниц ядра (KPTI)
Напомним, что в разделе о виртуальной памяти я описал, что все современные операционные системы используют технику, в которой память ядра отображается в адресное пространство виртуальной памяти процесса каждого пользовательского режима. Это из соображений производительности и простоты. Это означает, что когда программа выполняет системный вызов, ядро готово к использованию без дальнейшей работы. Исправление для Meltdown - больше не выполнять это двойное отображение
Рис. 6 Изоляция страницы таблицы ядра
На рисунке 6 показана методика, называемая изоляцией таблицы страниц ядра (KPTI). Это в основном сводится к тому, что память ядра не отображается в программе, когда она выполняется в пространстве пользователя. Если сопоставление отсутствует, спекулятивное выполнение больше невозможно и немедленно приведет к ошибке.
В дополнение к усложнению менеджера виртуальной памяти (VMM) операционной системы, без помощи аппаратного обеспечения этот метод также значительно замедлит рабочие нагрузки, которые делают большое количество переходов из режима пользователя в режим ядра, из-за того, что таблицы страниц должны быть модифицированы при каждом переходе, и TLB должен быть сброшен (учитывая, что TLB может удерживать устаревшие отображения).
Более новые процессоры x86 имеют функцию, известную как ASID (идентификатор адресного пространства) или PCID (идентификатор контекста процесса), которую можно использовать для того, чтобы существенно снизить эту задачу (в ARM и других микроархитектурах эта функция использовалась годами). PCID позволяет идентификатору быть связанным с записью TLB и затем сбрасывать только записи TLB с этим идентификатором. Использование PCID делает KPTI дешевле, но все же не бесплатным.
Таким образом, Meltdown - чрезвычайно серьезная и простая в использовании уязвимость. К счастью, он имеет относительно простое решение, которое уже было развернуто всеми основными поставщиками ОС, поскольку некоторые рабочие нагрузки будут работать медленнее, пока будущее оборудование не будет явно разработано для описанного разделения адресного пространства.
"Призрачная" уязвимость
Призрак разделяет некоторые свойства Meltdown и состоит из двух вариантов. В отличие от Meltdown, Spectre значительно сложнее в использовании, но затрагивает почти все современные процессоры, произведенные за последние двадцать лет. По сути, Spectre - это атака на современный процессор и операционной системы против конкретной уязвимости безопасности.
Обход проверки границ ( 1-ый вариант Spectre )
Первый вариант Specter известен как «обход проверки границ». Это продемонстрировано в следующем фрагменте кода (это тот же фрагмент кода, который я использовал для введения спекулятивного выполнения выше).
В предыдущем примере предположим следующую последовательность событий:
Атакующий контролирует x.
array1_size не кэшируется
array1 кэшируется
Процессор догадывается, что x меньше array1_size. (Процессоры используют различные запатентованные алгоритмы и эвристики, чтобы определить, следует ли спекулировать, именно поэтому детали атаки для Spectre различаются у разных производителей и моделей процессоров.)
Процессор выполняет тело оператора if в ожидании array1_size загрузки, воздействуя на кэш аналогично Meltdown.
Затем злоумышленник может определить фактическое значение с array1[x]помощью одного из различных методов. (Более подробную информацию о атаках на вывод из кэша см. В исследовательской статье .(В ближайшее время попытаюсь перевести))
Spectre значительно сложнее использовать, чем Meltdown, потому что эта уязвимость не зависит от повышения привилегий. Злоумышленник должен убедить ядро запустить код и спекулировать неправильно. Как правило, злоумышленник должен отравить механизм спекуляции и обмануть его, чтобы он угадал неправильно. Тем не менее, исследователи показали несколько доказательств подвига концепции.
Я хочу повторить, что действительно невероятное обнаружение этого подвига . Я лично не считаю это недостатком дизайна процессора, Meltdown как таковой. Я считаю это фундаментальным открытием о том, как современные аппаратные и программные средства работают вместе. Тот факт, что кэши ЦП могут использоваться косвенно для изучения шаблонов доступа, известен давно. Тот факт, что кэши ЦП могут использоваться в качестве побочного канала для выгрузки памяти компьютера, поражает как концептуально, так и его последствиями.
Целевая инъекция ветви (Вариант Spectre 2)
Напомним, что непрямое ветвление очень распространено в современных программах. Вариант Spectre 2 использует косвенное предсказание ветвлений, чтобы отравить процессор в умозрительное выполнение в область памяти, которую он никогда бы не выполнил иначе. Если выполнение этих инструкций может оставить в кэше состояние, которое может быть обнаружено с помощью атак на кэш, злоумышленник может затем сбросить всю память ядра. Подобно варианту 1 «Spectre», вариант 2 «Spectre» использовать гораздо сложнее, чем «Meltdown», однако исследователи продемонстрировали работоспособные варианты реализации варианта 2.
Смягчение Spectre значительно интереснее, чем смягчение последствий Meltdown. На самом деле, в академической статье «Spectre» написано, что в настоящее время нет известных мер по снижению риска. Похоже, что параллельно с академической работой Intel (и, возможно, других производители процессоров), а также основных поставщиков ОС и облачных вычислений в течение нескольких месяцев активно работали над разработкой мер по смягчению последствий. В этом разделе я расскажу о различных мерах по смягчению последствий, которые были разработаны и развернуты. Это раздел, который мне больше всего нравится, так как получить точную информацию невероятно сложно, поэтому я собираю информацию из разных источников.
Статический анализ и ограждение (вариант 1 смягчения)
Единственный известный вариант (ограничение обхода проверки границ) - это статический анализ кода для определения последовательностей кода, которые могут контролироваться злоумышленником, чтобы помешать предположениям. Уязвимые кодовые последовательности могут иметь команду сериализации, например lfence вставку, которая останавливает умозрительное выполнение до тех пор, пока не будут выполнены все инструкции вплоть до ограждения. При вставке инструкций необходимо соблюдать осторожность, так как слишком большое их количество может серьезно повлиять на производительность.
Retpoline (2-ой вариант смягчения)
Первый вариант 2-го Spectre (инъекция в целевую ветвь) был разработан Google и известен как «Retpoline». Мне неясно, был ли он разработан изолированно Google или Google в сотрудничестве с Intel. Я бы предположил, что он был экспериментально разработан Google, а затем проверен аппаратными инженерами Intel, но я не уверен. Подробности о подходе «Retpoline» можно найти в статье Google по этой теме . Я суммирую их здесь (я примыкаю к некоторым деталям, включая недоделку, которые описаны в статье).
Retpoline полагается на тот факт, что вызов и возврат из функций и связанные с ними операции со стеком настолько распространены в компьютерных программах, что процессоры сильно оптимизированы для их выполнения. (Если вы не знакомы с тем, как работает стек в отношении вызова и возврата из функций, этот пост является хорошим учебником.) В двух словах, когда выполняется «вызов», адрес возврата помещается в стек. «Retpoline» отключает адрес возврата и продолжает выполнение. Спекулятивное аппаратное обеспечение запомнит запрошенный адрес возврата и спекулятивно продолжит выполнение в этот момент.
Конструкция Retpoline заменяет косвенный переход в область памяти, хранящуюся в регистре r11:
Вместе с:
Давайте посмотрим, что предыдущий ассемблерный код делает по одному шагу за раз, и как он смягчает внедрение цели ветвления.
На этом этапе код вызывает ячейку памяти, которая известна во время компиляции, поэтому это жестко закодированное смещение, а не косвенное. Это помещает адрес возврата capture_spec в стек.
Адрес возврата при вызове перезаписывается фактической целью перехода.
Возврат осуществляется по реальной цели.
Когда процессор спекулятивно выполнится, он вернется в бесконечный цикл! Помните, что процессор будет спекулировать, пока загрузка памяти не будет завершена. В этом случае, предположение, было манипулировано, чтобы быть захвачены в бесконечный цикл , который не имеет каких - либо побочных эффектов , которые являются наблюдаемыми для атакующего. Когда процессор в конечном итоге выполняет реальный возврат, он прервет спекулятивное выполнение, которое не имело никакого эффекта.
На мой взгляд, это действительно гениальное смягчение. Слава инженерам, которые разработали его. Недостатком этого смягчения является то, что требуется перекомпиляция всего программного обеспечения так, чтобы непрямые ответвления были преобразованы в ответвления Retpoline . Для облачных сервисов, таких как Google, которые владеют всем стеком, перекомпиляция не имеет большого значения. Для других это может быть очень важно или невозможно.
IBRS, STIBP и IBPB (2-ой вариант смягчения)
Похоже, что одновременно с разработкой Retpoline Intel (и AMD в некоторой степени) яростно работали над изменениями аппаратного обеспечения, чтобы смягчить атаки с использованием инъекций. В качестве обновлений микрокода ЦП поставляются три новые аппаратные функции:
Косвенная спекуляция с ограниченным доступом (IBRS)
Однопоточные косвенные предсказатели ветвления (STIBP)
Барьерный предсказатель непрямого ветвления (IBPB)
Ограниченная информация о новых функциях микрокода доступна в Intel здесь . Я смог примерно собрать воедино эти новые функции, прочитав приведенную выше документацию и посмотрев исправления ядра Linux и гипервизора Xen. Из моего анализа каждая функция потенциально используется следующим образом:
IBRS одновременно очищает кэш предсказания ветвления между уровнями привилегий (от пользователя к ядру) и отключает предсказание ветвления в потоке одноуровневого ЦП. Напомним, что каждое ядро ЦП обычно имеет два потока. Похоже, что на современных процессорах аппаратное предсказание ветвления распределяется между потоками. Это означает, что код режима пользователя не только может отравить предиктор ветвления перед вводом кода ядра, но и код, выполняющийся в одноименном потоке ЦП, также может отравить его. Включение IBRS в режиме ядра по существу предотвращает любое предыдущее выполнение в пользовательском режиме и любое выполнение в потоке одноуровневого ЦП от влияния на прогноз ветвления.
STIBP, по-видимому, является подмножеством IBRS, которое просто отключает предсказание ветвления в потоке одноуровневого ЦП. Насколько я могу судить, основной вариант использования этой функции состоит в том, чтобы не допустить отравления потоком одного и того же процессора предиктора ветвления при одновременном запуске двух разных процессов пользовательского режима (или виртуальных машин) на одном и том же ядре ЦП. Честно говоря, мне сейчас не совсем ясно, когда следует использовать STIBP.
Похоже, что IBPB очищает кэш предсказания ветвления для кода, работающего с тем же уровнем привилегий. Это может использоваться при переключении между двумя программами пользовательского режима или двумя виртуальными машинами, чтобы гарантировать, что предыдущий код не мешает коду, который должен выполняться (хотя без STIBP я считаю, что код, выполняющийся в потоке одноуровневого ЦП, все еще может отравить предсказатель ветви).
На момент написания этой статьи основными мерами, которые я вижу в реализации уязвимости внедрения на уровне филиала, являются как Retpoline, так и IBRS. Предположительно, это самый быстрый способ защитить ядро от программ пользовательского режима или гипервизор от гостей виртуальных машин. В будущем я бы ожидал, что STIBP и IBPB будут развернуты в зависимости от уровня паранойи различных программ пользовательского режима, мешающих друг другу.
Стоимость IBRS также, как представляется, очень сильно различается в зависимости от архитектуры ЦП, поскольку новые процессоры Intel Skylake относительно дешевы по сравнению со старыми процессорами. В Lyft мы наблюдали приблизительно 20% -ное замедление при определенных нагрузках на системные вызовы в экземплярах AWS C4, когда были приняты меры по снижению риска. Я бы предположил, что Amazon развернул IBRS и, возможно, также Retpoline , но я не уверен. Похоже, что Google, возможно, только развернул Retpoline в своем облаке.
Со временем я ожидал бы, что процессоры в конечном итоге перейдут на модель «всегда включен» IBRS, где аппаратное обеспечение по умолчанию очищает разделение предикторов ветвления между потоками ЦП и корректно сбрасывает состояние при изменении уровня привилегий. Единственная причина, по которой это не было бы сделано сегодня, - это очевидные затраты производительности при установке этой функциональности на уже выпущенные микроархитектуры с помощью обновлений микрокода.
Заключение
Очень редко результаты исследований в корне меняют способ сборки и работы компьютеров. Meltdown и Spectre сделали именно это. Эти результаты существенно изменят дизайн аппаратного и программного обеспечения в течение следующих 7–10 лет (следующий цикл аппаратного обеспечения ЦП), поскольку разработчики принимают во внимание новую реальность возможностей утечки данных через побочные каналы кэша.
Между тем, результаты Meltdown и Spectre и связанные с ними меры будут иметь существенные последствия для пользователей компьютеров на долгие годы. В ближайшем будущем меры по снижению воздействия будут оказывать существенное влияние на производительность в зависимости от рабочей нагрузки и конкретного оборудования. Это может потребовать изменений в работе некоторых инфраструктур (например, в Lyft мы агрессивно переносим некоторые рабочие нагрузки на экземпляры AWS C5 из-за того, что IBRS работает значительно быстрее на процессорах Skylake, а новый гипервизор Nitro доставляет прерывания непосредственно гостям, использующим SR -IOV и APICv, удаление многих выходов виртуальной машины для IO тяжелых рабочих нагрузок). Пользователи настольных компьютеров также не застрахованы из-за проверок атаки браузера с использованием JavaScript, которые ОС и поставщики браузеров работают над тем, чтобы смягчить их. Кроме того, из-за сложности уязвимостей почти наверняка исследователи в области безопасности найдут новые эксплойты, не охваченные текущими мерами защиты, которые необходимо будет исправлять.
Хотя я люблю работать в Lyft и чувствую, что работа, которую мы выполняем в области инфраструктуры микросервисных систем, является одной из наиболее эффективных работ, выполняемых в отрасли сейчас, подобные события заставляют меня скучать по работе с операционными системами и гипервизорами. Я чрезвычайно завидую героической работе, проделанной за последние шесть месяцев огромным количеством людей в области исследования и смягчения уязвимостей. Я бы хотел быть частью этого!
Это был мой первый перевод статьей для XSS, а предложил мне её weaver . И того более 4.000 слов в данной статье, которые я попытался перевести для вас.
Если вы хотите меня поддержать, то можете задонатить мне на QIWI кошелёк 79054863063 на поездку в Польшу с классом (Да, я ещё школьник) или же купить Nord VPN в моей теме: https://xss.pro/threads/30205/
Оригинальная статья: https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2
Text and Picture translate by neopaket
Хотя в настоящее время я в основном известен сетями и распределенными системами на уровне приложений, первую часть своей карьеры я посвятил работе с операционными системами и гипервизорами. Я глубоко увлечен деталями низкого уровня того, как работают современные процессоры и системное программное обеспечение. Когда были объявлены недавние уязвимости “Meltdown и Spectre” , я рылся в доступной информации и хотел узнать больше.
Уязвимости поразительны… Я бы сказал, что они одно из самых важных открытий в области компьютерных наук за последние 10–20 лет. Смягчения также трудно понять, и точную информацию о них трудно найти. Это неудивительно, учитывая их “Критическую природу”. Для устранения этих уязвимостей потребовались месяцы секретной работы со стороны всех основных поставщиков процессоров, операционных систем и облачных вычислений. Удивляет тот факт, что всё держалось в секрете в течение 6 месяцев, когда над ними, вероятно, работали буквально сотни людей.
Хотя с момента их разглашения многое было написано о “Meltdown и Spectre”, я так и не увидел информацию приемлемого уровня, посвященного уязвимостям исмягчениям. В этой статье я попытаюсь исправить это, предоставив краткое введение в аппаратную и программную среду, необходимую для понимания уязвимостей, обсуждение самих уязвимостей, а также текущих мер по их снижению.
Важное примечание : поскольку я не работал непосредственно над смягчениями, не работаю в Intel, Microsoft, Google, Amazon, Red Hat и т. д., Некоторые детали, которые я собираюсь предоставить, могут быть не совсем точными. Я собрал воедино эту статью на основе моих знаний о том, как работают эти системы, общедоступной документации / обсуждений, опубликованных в LKML и xen-devel . Был бы рад, чтобы меня исправили, если какой-либо из этих постов будет неточным, хотя я сомневаюсь, что это произойдет в ближайшее время, учитывая, сколько из этого предмета все еще рассматривается NDA. (Подсказать что-либо автору можно здесь: https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2 )
Введение
В этом разделе я приведу некоторые сведения, необходимые для понимания уязвимостей. Раздел содержит большое количество деталей, которые предназначены для читателей с ограниченным пониманием компьютерного оборудования и системного программного обеспечения.
Виртуальная память
Виртуальная память - это метод, используемый во всех операционных системах с 1970-х годов. Он обеспечивает уровень абстракции между схемой адресации памяти, которую видит большинство программ, и физическими устройствами, поддерживающими эту память (ОЗУ, диски и т. п.). На надлежащем уровне это позволяет приложениям использовать больше памяти, чем на самом деле имеет машина.Это обеспечивает мощную абстракцию, которая облегчает многие задачи программирования.
Рис.1 Виртуальная память
На рисунке 1 показан упрощенный компьютер с 400 байтами памяти, разложенными на «страницы» по 100 байтов (реальные компьютеры используют степень двух, обычно 4096). Компьютер имеет два процесса, каждый с 200 байтами памяти на 2 страницах каждый. Процессы могут запускать один и тот же код, используя фиксированные адреса в диапазоне байтов 0–199, однако они поддерживаются дискретной физической памятью, так что они не влияют друг на друга. Хотя современные ОС и компьютеры используют виртуальную память существенно более сложным образом, чем то, что представлено в этом примере, основная предпосылка, представленная выше, сохраняется во всех случаях. Операционные системы абстрагируют адреса, которые приложения видят от физических ресурсов, которые их поддерживают.
Преобразование виртуальных адресов в физические является такой распространенной операцией на современных компьютерах, что, если бы во всех случаях приходилось использовать ОС, компьютер работал бы невероятно медленно. Современное аппаратное обеспечение ЦП предоставляет устройство, называемое трансляцией Lookaside Buffer (TLB), которое кэширует недавно использованные отображения. Это позволяет центральным процессорам выполнять преобразование адресов непосредственно в аппаратном обеспечении большую часть времени.
Рис. 2 Перевод виртуальной памяти
На рисунке 2 показан поток преобразования адресов:
Программа выбирает виртуальный адрес.
Процессор пытается перевести его, используя TLB. Если адрес найден, используется перевод.
Если адрес не найден, ЦП обращается к набору «таблиц страниц» для определения соответствия. Таблицы страниц - это набор страниц физической памяти, предоставляемых операционной системой в месте, где аппаратное обеспечение может их найти (например, регистр CR3 на оборудовании x86). Таблицы страниц отображают виртуальные адреса на физические адреса, а также содержат метаданные, такие как разрешения.
Если таблица страниц содержит отображение, она возвращается, кэшируется в TLB и используется для поиска. Если таблица страниц не содержит сопоставления, в ОС возникает «ошибка страницы». Отказ страницы - это особый вид прерывания, который позволяет ОС получить контроль и определить, что делать в случае отсутствия или недопустимого отображения. Например, ОС может завершить программу. Это может также выделить некоторую физическую память и отобразить ее в процессе. Если обработчик сбоев страницы продолжает выполнение, TLB будет использовать новое отображение.
Рис. 3 Отображение виртуальной памяти пользователя/ядра
На рисунке 3 показано несколько более реалистичное представление о том, как выглядит виртуальная память в современном компьютере (до распада - подробнее об этом ниже). В данном наборе настроек у нас есть следующие функции:
Память ядра показана красным. Он содержится в диапазоне физических адресов 0–99. Память ядра - это специальная память, к которой должна иметь доступ только операционная система. Пользовательские программы не должны иметь доступа к нему.
Пользовательская память показана серым цветом.
Нераспределенная физическая память показана синим цветом.
В этом примере мы начинаем видеть некоторые полезные функции виртуальной памяти. Прежде всего:
Пользовательская память в каждом процессе находится в виртуальном диапазоне 0–99, но поддерживается другой физической памятью.
Память ядра в каждом процессе находится в виртуальном диапазоне 100–199, но поддерживается той же физической памятью.
Как я кратко упомянул в предыдущем разделе, каждая страница имеет соответствующие биты прав доступа. Даже если память ядра отображается в каждом пользовательском процессе, когда процесс выполняется в пользовательском режиме, он не может получить доступ к памяти ядра. Если процесс попытается сделать это, он вызовет сбой страницы, после чего операционная система прекратит его. Однако, когда процесс выполняется в режиме ядра (Например, во время системного вызова), то процессор разрешит доступ.
На этом этапе я отмечу, что этот тип двойного сопоставления (каждый процесс, в котором ядро подключено непосредственно к нему) уже более тридцати лет является стандартной практикой проектирования операционной системы по соображениям производительности (системные вызовы очень распространены, и это займет много времени. Время переназначения ядра или пространства пользователя при каждом переходе).
Топология кеша процессора
Рис. 4 Топология ядра, процессора, пакета и кэша.
Следующей справочной информацией, необходимой для понимания уязвимостей, является топология процессора и кэша современных процессоров. На рисунке 4 показана общая топология, которая является общей для большинства современных процессоров. Он состоит из следующих компонентов:
Основной единицей выполнения является «поток ЦП», «аппаратный поток» или «гиперпоток». Каждый поток ЦП содержит набор регистров и возможность выполнять поток машинного кода, очень похожий на программный поток.
Потоки ЦП содержатся в «ядре ЦП». Большинство современных ЦП содержат по два потока на ядро.
Современные процессоры обычно содержат несколько уровней кэш-памяти . Уровни кэша ближе к потоку ЦП меньше, быстрее и дороже. Чем дальше от процессора и ближе к основной памяти, тем больше, медленнее и дешевле кэш.
Типичный современный дизайн ЦП использует кэш L1 / L2 на ядро. Это означает, что каждый поток ЦП в ядре использует одни и те же кэши.
Несколько «ядер ЦП» содержатся в «пакете ЦП». Современные ЦП могут содержать более 30 ядер (60 потоков) или более в каждом пакете.
Все ядра ЦП в пакете обычно используют кэш L3.
Пакеты ЦП помещаются в «сокеты». Большинство потребительских компьютеров имеют один сокет, в то время как многие серверы центров обработки данных имеют несколько сокетов.
Спекулятивное исполнение
Рис. 5 Современный процессор исполнения CPU
Последним фрагментом справочной информации, необходимой для понимания уязвимостей, является современная методика ЦП, известная как «спекулятивное выполнение». На рисунке 5 показана общая схема механизма выполнения внутри современного ЦП.
Основной вывод заключается в том, что современные процессоры невероятно сложны и не просто выполняют машинные инструкции по порядку. Каждый поток ЦП имеет сложный механизм конвейерной обработки, способный выполнять команды не по порядку. Причина этого связана с кэшированием. Как я уже говорил в предыдущем разделе, каждый процессор использует несколько уровней кэширования. Каждая ошибка в кэше добавляет значительное время задержки к выполнению программы. Чтобы смягчить это, процессоры способны выполнять ahead и out of order в ожидании загрузки памяти. Это известно как спекулятивное исполнение. Следующий фрагмент кода демонстрирует это.
Код:
if (x < array1_size) {
y = array2[array1[x] * 256];
}
В предыдущем фрагменте представьте, что array1_size недоступен в кэше, но адрес array1 -. Процессор может угадать (предположить), что x меньше, array1_size продолжит и выполнит вычисления внутри оператора if. После array1_size считывания из памяти процессор может определить, правильно ли он угадан. Если это так, он может продолжать экономить кучу времени. Если это не так, он может отбросить спекулятивные вычисления и начать все сначала. Это не хуже, чем если бы он ждал в первую очередь.
Другой тип спекулятивного исполнения известен как косвенное предсказание ветвления. Это очень распространено в современных программах из-за виртуальной диспетчеризации.
Код:
class Base {
public:
virtual void Foo() = 0;
};
class Derived : public Base {
public:
void Foo() override { … }
};
Base* obj = new Derived;
obj->Foo();
Способ, которым предыдущий фрагмент реализован в машинном коде, состоит в том, чтобы загрузить «Таблицу виртуальных методов» или «таблицу виртуальной отправки» из области памяти, которая obj указывает на, и затем вызвать ее. Поскольку эта операция настолько распространена, современные процессоры имеют различные внутренние кэши и часто угадывают (спекулируют), куда пойдет косвенная ветвь и продолжат выполнение в этот момент. Опять же, если процессор угадает правильно, он может продолжать экономить кучу времени. Если это не так, он может отбросить спекулятивные вычисления и начать все сначала.
Наконец то уязвимости
Теперь, рассмотрев всю справочную информацию, мы можем погрузиться в уязвимости.
Мошенническая загрузка данных
Первая уязвимость, известная как Meltdown, удивительно проста в объяснении и почти тривиальна в использовании. Код эксплойта примерно выглядит следующим образом:
Код:
uint8_t* probe_array = new uint8_t[256 * 4096];
2. // ... Make sure probe_array is not cached
3. uint8_t kernel_memory = *(uint8_t*)(kernel_address);
4. uint64_t final_kernel_memory = kernel_memory * 4096;
5. uint8_t dummy = probe_array[final_kernel_memory];
6. // ... catch page fault
7. // ... determine which of 256 slots in probe_array is cached
Давайте рассмотрим каждый шаг выше, опишем, что он делает, и как это приводит к возможности чтения памяти всего компьютера из пользовательской программы .
В первой строке выделен «массив зондов». Это память в нашем процессе, которая используется в качестве побочного канала для извлечения данных из ядра. Как это будет сделано, скоро станет понятно.
После выделения злоумышленник следит за тем, чтобы ни одна памятm в массиве зондов не была кэширована. Существуют различные способы сделать это, самый простой из которых включает в себя специфичные для процессора инструкции по очистке области памяти из кэша.
Затем злоумышленник начинает читать байт из адресного пространства ядра. Помните, из нашего предыдущего обсуждения виртуальной памяти и таблиц страниц, что все современные ядра обычно отображают все виртуальное адресное пространство ядра в пользовательский процесс. Операционные системы полагаются на тот факт, что каждая запись таблицы страниц имеет настройки разрешений, и что программы пользовательского режима не имеют доступа к памяти ядра. Любой такой доступ приведет к ошибке страницы. Это действительно то, что в конечном итоге произойдет на шаге 3.
Тем не менее , современные процессоры также выполняют спекулятивное выполнение и будут выполняться перед ошибочной инструкцией. Таким образом, шаги 3–5 могут выполняться в конвейере ЦП до возникновения ошибки. На этом этапе байт памяти ядра (в диапазоне от 0 до 255) умножается на размер страницы системы, который обычно составляет 4096.
На этом этапе умноженный байт памяти ядра затем используется для чтения из массива зондов в фиктивное значение. Умножение байта на 4096 позволяет избежать того, что функция ЦП, называемая «prefetcher», будет читать больше данных, чем мы хотим, в кэш.
На этому шагу ЦП осознал свою ошибку и откатился к шагу 3. Однако результаты спекулятивных инструкций по- прежнему видны в кэше . Атакующий использует функциональные возможности операционной системы, чтобы перехватить ошибочную инструкцию и продолжить выполнение (например, обрабатывая SIGFAULT).
На шаге 7 злоумышленник выполняет итерацию и видит, сколько времени занимает чтение каждого из 256 возможных байтов в массиве зондов, которые могли быть проиндексированы памятью ядра. Процессор загрузит одно из расположений в кэш, и это местоположение будет загружаться значительно быстрее, чем все остальные расположения (которые должны быть прочитаны из основной памяти). Это расположение является значением байта в памяти ядра .
Используя описанную выше технику и тот факт, что для современных операционных систем является стандартной практикой сопоставление всей физической памяти в виртуальное адресное пространство ядра, злоумышленник может прочитать всю физическую память компьютера .
Теперь вы можете задаться вопросом: «Вы сказали, что у таблиц страниц есть биты прав доступа. Как могло случиться, что код пользовательского режима смог спекулятивно получить доступ к памяти ядра? » . Причина в том, что это ошибка в процессорах Intel. На мой взгляд, нет веских причин, производительности или иного, чтобы это было возможно. Напомним, что весь доступ к виртуальной памяти должен осуществляться через TLB. Во время спекулятивного выполнения легко можно проверить, что кэшированное отображение имеет разрешения, совместимые с текущим уровнем привилегий. Аппаратное обеспечение Intel просто не делает этого. Другие производители процессоров выполняют проверку разрешений и блокируют спекулятивное выполнение. Таким образом, насколько нам известно, Meltdown является уязвимостью только для Intel .
Изоляция таблицы страниц ядра (KPTI)
Напомним, что в разделе о виртуальной памяти я описал, что все современные операционные системы используют технику, в которой память ядра отображается в адресное пространство виртуальной памяти процесса каждого пользовательского режима. Это из соображений производительности и простоты. Это означает, что когда программа выполняет системный вызов, ядро готово к использованию без дальнейшей работы. Исправление для Meltdown - больше не выполнять это двойное отображение
Рис. 6 Изоляция страницы таблицы ядра
На рисунке 6 показана методика, называемая изоляцией таблицы страниц ядра (KPTI). Это в основном сводится к тому, что память ядра не отображается в программе, когда она выполняется в пространстве пользователя. Если сопоставление отсутствует, спекулятивное выполнение больше невозможно и немедленно приведет к ошибке.
В дополнение к усложнению менеджера виртуальной памяти (VMM) операционной системы, без помощи аппаратного обеспечения этот метод также значительно замедлит рабочие нагрузки, которые делают большое количество переходов из режима пользователя в режим ядра, из-за того, что таблицы страниц должны быть модифицированы при каждом переходе, и TLB должен быть сброшен (учитывая, что TLB может удерживать устаревшие отображения).
Более новые процессоры x86 имеют функцию, известную как ASID (идентификатор адресного пространства) или PCID (идентификатор контекста процесса), которую можно использовать для того, чтобы существенно снизить эту задачу (в ARM и других микроархитектурах эта функция использовалась годами). PCID позволяет идентификатору быть связанным с записью TLB и затем сбрасывать только записи TLB с этим идентификатором. Использование PCID делает KPTI дешевле, но все же не бесплатным.
Таким образом, Meltdown - чрезвычайно серьезная и простая в использовании уязвимость. К счастью, он имеет относительно простое решение, которое уже было развернуто всеми основными поставщиками ОС, поскольку некоторые рабочие нагрузки будут работать медленнее, пока будущее оборудование не будет явно разработано для описанного разделения адресного пространства.
"Призрачная" уязвимость
Призрак разделяет некоторые свойства Meltdown и состоит из двух вариантов. В отличие от Meltdown, Spectre значительно сложнее в использовании, но затрагивает почти все современные процессоры, произведенные за последние двадцать лет. По сути, Spectre - это атака на современный процессор и операционной системы против конкретной уязвимости безопасности.
Обход проверки границ ( 1-ый вариант Spectre )
Первый вариант Specter известен как «обход проверки границ». Это продемонстрировано в следующем фрагменте кода (это тот же фрагмент кода, который я использовал для введения спекулятивного выполнения выше).
Код:
if (x <array1_size) {
y = array2 [array1 [x] * 256];
}
В предыдущем примере предположим следующую последовательность событий:
Атакующий контролирует x.
array1_size не кэшируется
array1 кэшируется
Процессор догадывается, что x меньше array1_size. (Процессоры используют различные запатентованные алгоритмы и эвристики, чтобы определить, следует ли спекулировать, именно поэтому детали атаки для Spectre различаются у разных производителей и моделей процессоров.)
Процессор выполняет тело оператора if в ожидании array1_size загрузки, воздействуя на кэш аналогично Meltdown.
Затем злоумышленник может определить фактическое значение с array1[x]помощью одного из различных методов. (Более подробную информацию о атаках на вывод из кэша см. В исследовательской статье .(В ближайшее время попытаюсь перевести))
Spectre значительно сложнее использовать, чем Meltdown, потому что эта уязвимость не зависит от повышения привилегий. Злоумышленник должен убедить ядро запустить код и спекулировать неправильно. Как правило, злоумышленник должен отравить механизм спекуляции и обмануть его, чтобы он угадал неправильно. Тем не менее, исследователи показали несколько доказательств подвига концепции.
Я хочу повторить, что действительно невероятное обнаружение этого подвига . Я лично не считаю это недостатком дизайна процессора, Meltdown как таковой. Я считаю это фундаментальным открытием о том, как современные аппаратные и программные средства работают вместе. Тот факт, что кэши ЦП могут использоваться косвенно для изучения шаблонов доступа, известен давно. Тот факт, что кэши ЦП могут использоваться в качестве побочного канала для выгрузки памяти компьютера, поражает как концептуально, так и его последствиями.
Целевая инъекция ветви (Вариант Spectre 2)
Напомним, что непрямое ветвление очень распространено в современных программах. Вариант Spectre 2 использует косвенное предсказание ветвлений, чтобы отравить процессор в умозрительное выполнение в область памяти, которую он никогда бы не выполнил иначе. Если выполнение этих инструкций может оставить в кэше состояние, которое может быть обнаружено с помощью атак на кэш, злоумышленник может затем сбросить всю память ядра. Подобно варианту 1 «Spectre», вариант 2 «Spectre» использовать гораздо сложнее, чем «Meltdown», однако исследователи продемонстрировали работоспособные варианты реализации варианта 2.
Смягчение Spectre значительно интереснее, чем смягчение последствий Meltdown. На самом деле, в академической статье «Spectre» написано, что в настоящее время нет известных мер по снижению риска. Похоже, что параллельно с академической работой Intel (и, возможно, других производители процессоров), а также основных поставщиков ОС и облачных вычислений в течение нескольких месяцев активно работали над разработкой мер по смягчению последствий. В этом разделе я расскажу о различных мерах по смягчению последствий, которые были разработаны и развернуты. Это раздел, который мне больше всего нравится, так как получить точную информацию невероятно сложно, поэтому я собираю информацию из разных источников.
Статический анализ и ограждение (вариант 1 смягчения)
Единственный известный вариант (ограничение обхода проверки границ) - это статический анализ кода для определения последовательностей кода, которые могут контролироваться злоумышленником, чтобы помешать предположениям. Уязвимые кодовые последовательности могут иметь команду сериализации, например lfence вставку, которая останавливает умозрительное выполнение до тех пор, пока не будут выполнены все инструкции вплоть до ограждения. При вставке инструкций необходимо соблюдать осторожность, так как слишком большое их количество может серьезно повлиять на производительность.
Retpoline (2-ой вариант смягчения)
Первый вариант 2-го Spectre (инъекция в целевую ветвь) был разработан Google и известен как «Retpoline». Мне неясно, был ли он разработан изолированно Google или Google в сотрудничестве с Intel. Я бы предположил, что он был экспериментально разработан Google, а затем проверен аппаратными инженерами Intel, но я не уверен. Подробности о подходе «Retpoline» можно найти в статье Google по этой теме . Я суммирую их здесь (я примыкаю к некоторым деталям, включая недоделку, которые описаны в статье).
Retpoline полагается на тот факт, что вызов и возврат из функций и связанные с ними операции со стеком настолько распространены в компьютерных программах, что процессоры сильно оптимизированы для их выполнения. (Если вы не знакомы с тем, как работает стек в отношении вызова и возврата из функций, этот пост является хорошим учебником.) В двух словах, когда выполняется «вызов», адрес возврата помещается в стек. «Retpoline» отключает адрес возврата и продолжает выполнение. Спекулятивное аппаратное обеспечение запомнит запрошенный адрес возврата и спекулятивно продолжит выполнение в этот момент.
Конструкция Retpoline заменяет косвенный переход в область памяти, хранящуюся в регистре r11:
Код:
jmp *%r11
Код:
call set_up_target; (1)
capture_spec: (4)
pause;
jmp capture_spec;
set_up_target:
mov %r11, (%rsp); (2)
ret; (3)
Давайте посмотрим, что предыдущий ассемблерный код делает по одному шагу за раз, и как он смягчает внедрение цели ветвления.
На этом этапе код вызывает ячейку памяти, которая известна во время компиляции, поэтому это жестко закодированное смещение, а не косвенное. Это помещает адрес возврата capture_spec в стек.
Адрес возврата при вызове перезаписывается фактической целью перехода.
Возврат осуществляется по реальной цели.
Когда процессор спекулятивно выполнится, он вернется в бесконечный цикл! Помните, что процессор будет спекулировать, пока загрузка памяти не будет завершена. В этом случае, предположение, было манипулировано, чтобы быть захвачены в бесконечный цикл , который не имеет каких - либо побочных эффектов , которые являются наблюдаемыми для атакующего. Когда процессор в конечном итоге выполняет реальный возврат, он прервет спекулятивное выполнение, которое не имело никакого эффекта.
На мой взгляд, это действительно гениальное смягчение. Слава инженерам, которые разработали его. Недостатком этого смягчения является то, что требуется перекомпиляция всего программного обеспечения так, чтобы непрямые ответвления были преобразованы в ответвления Retpoline . Для облачных сервисов, таких как Google, которые владеют всем стеком, перекомпиляция не имеет большого значения. Для других это может быть очень важно или невозможно.
IBRS, STIBP и IBPB (2-ой вариант смягчения)
Похоже, что одновременно с разработкой Retpoline Intel (и AMD в некоторой степени) яростно работали над изменениями аппаратного обеспечения, чтобы смягчить атаки с использованием инъекций. В качестве обновлений микрокода ЦП поставляются три новые аппаратные функции:
Косвенная спекуляция с ограниченным доступом (IBRS)
Однопоточные косвенные предсказатели ветвления (STIBP)
Барьерный предсказатель непрямого ветвления (IBPB)
Ограниченная информация о новых функциях микрокода доступна в Intel здесь . Я смог примерно собрать воедино эти новые функции, прочитав приведенную выше документацию и посмотрев исправления ядра Linux и гипервизора Xen. Из моего анализа каждая функция потенциально используется следующим образом:
IBRS одновременно очищает кэш предсказания ветвления между уровнями привилегий (от пользователя к ядру) и отключает предсказание ветвления в потоке одноуровневого ЦП. Напомним, что каждое ядро ЦП обычно имеет два потока. Похоже, что на современных процессорах аппаратное предсказание ветвления распределяется между потоками. Это означает, что код режима пользователя не только может отравить предиктор ветвления перед вводом кода ядра, но и код, выполняющийся в одноименном потоке ЦП, также может отравить его. Включение IBRS в режиме ядра по существу предотвращает любое предыдущее выполнение в пользовательском режиме и любое выполнение в потоке одноуровневого ЦП от влияния на прогноз ветвления.
STIBP, по-видимому, является подмножеством IBRS, которое просто отключает предсказание ветвления в потоке одноуровневого ЦП. Насколько я могу судить, основной вариант использования этой функции состоит в том, чтобы не допустить отравления потоком одного и того же процессора предиктора ветвления при одновременном запуске двух разных процессов пользовательского режима (или виртуальных машин) на одном и том же ядре ЦП. Честно говоря, мне сейчас не совсем ясно, когда следует использовать STIBP.
Похоже, что IBPB очищает кэш предсказания ветвления для кода, работающего с тем же уровнем привилегий. Это может использоваться при переключении между двумя программами пользовательского режима или двумя виртуальными машинами, чтобы гарантировать, что предыдущий код не мешает коду, который должен выполняться (хотя без STIBP я считаю, что код, выполняющийся в потоке одноуровневого ЦП, все еще может отравить предсказатель ветви).
На момент написания этой статьи основными мерами, которые я вижу в реализации уязвимости внедрения на уровне филиала, являются как Retpoline, так и IBRS. Предположительно, это самый быстрый способ защитить ядро от программ пользовательского режима или гипервизор от гостей виртуальных машин. В будущем я бы ожидал, что STIBP и IBPB будут развернуты в зависимости от уровня паранойи различных программ пользовательского режима, мешающих друг другу.
Стоимость IBRS также, как представляется, очень сильно различается в зависимости от архитектуры ЦП, поскольку новые процессоры Intel Skylake относительно дешевы по сравнению со старыми процессорами. В Lyft мы наблюдали приблизительно 20% -ное замедление при определенных нагрузках на системные вызовы в экземплярах AWS C4, когда были приняты меры по снижению риска. Я бы предположил, что Amazon развернул IBRS и, возможно, также Retpoline , но я не уверен. Похоже, что Google, возможно, только развернул Retpoline в своем облаке.
Со временем я ожидал бы, что процессоры в конечном итоге перейдут на модель «всегда включен» IBRS, где аппаратное обеспечение по умолчанию очищает разделение предикторов ветвления между потоками ЦП и корректно сбрасывает состояние при изменении уровня привилегий. Единственная причина, по которой это не было бы сделано сегодня, - это очевидные затраты производительности при установке этой функциональности на уже выпущенные микроархитектуры с помощью обновлений микрокода.
Заключение
Очень редко результаты исследований в корне меняют способ сборки и работы компьютеров. Meltdown и Spectre сделали именно это. Эти результаты существенно изменят дизайн аппаратного и программного обеспечения в течение следующих 7–10 лет (следующий цикл аппаратного обеспечения ЦП), поскольку разработчики принимают во внимание новую реальность возможностей утечки данных через побочные каналы кэша.
Между тем, результаты Meltdown и Spectre и связанные с ними меры будут иметь существенные последствия для пользователей компьютеров на долгие годы. В ближайшем будущем меры по снижению воздействия будут оказывать существенное влияние на производительность в зависимости от рабочей нагрузки и конкретного оборудования. Это может потребовать изменений в работе некоторых инфраструктур (например, в Lyft мы агрессивно переносим некоторые рабочие нагрузки на экземпляры AWS C5 из-за того, что IBRS работает значительно быстрее на процессорах Skylake, а новый гипервизор Nitro доставляет прерывания непосредственно гостям, использующим SR -IOV и APICv, удаление многих выходов виртуальной машины для IO тяжелых рабочих нагрузок). Пользователи настольных компьютеров также не застрахованы из-за проверок атаки браузера с использованием JavaScript, которые ОС и поставщики браузеров работают над тем, чтобы смягчить их. Кроме того, из-за сложности уязвимостей почти наверняка исследователи в области безопасности найдут новые эксплойты, не охваченные текущими мерами защиты, которые необходимо будет исправлять.
Хотя я люблю работать в Lyft и чувствую, что работа, которую мы выполняем в области инфраструктуры микросервисных систем, является одной из наиболее эффективных работ, выполняемых в отрасли сейчас, подобные события заставляют меня скучать по работе с операционными системами и гипервизорами. Я чрезвычайно завидую героической работе, проделанной за последние шесть месяцев огромным количеством людей в области исследования и смягчения уязвимостей. Я бы хотел быть частью этого!
Это был мой первый перевод статьей для XSS, а предложил мне её weaver . И того более 4.000 слов в данной статье, которые я попытался перевести для вас.
Если вы хотите меня поддержать, то можете задонатить мне на QIWI кошелёк 79054863063 на поездку в Польшу с классом (Да, я ещё школьник) или же купить Nord VPN в моей теме: https://xss.pro/threads/30205/
Оригинальная статья: https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2
Text and Picture translate by neopaket