Коротко о природе одной из половин Чипокалипсиса — без всякого кода.
Недавно открытые уязвимости в процессорах на слуху уже почти у каждого, даже отдаленного от IT-темы человека. Однако далеко не все понимают, как работают эти уязвимости, а объяснения на российских и зарубежных сайтах в основном нацелены на подготовленную аудиторию. Я же постараюсь совсем просто объяснить, как работает уязвимость известная как Meltdown.
Если вы не пугаетесь терминов и примерно знакомы с программированием, почитайте эту статью — там всё подробнее и профессиональнее.
Сразу попрошу знающих людей не относиться излишне критично к примерам. Автор в курсе, что реальные процессы работают не совсем так, как они описаны в этом тексте. Я опустил всё, что усложняет понимание, ничего не исказив и оставив возможность спроецировать эту модель на реальные алгоритмы работы процессоров.
Итак, уязвимость Meltdown нарушает одну условность, на которую полагаются все разработчики и администраторы ПО: центральный процессор (ЦП) полностью изолирует друг от друга выполняемые на нем программы. Этот принцип — фундамент всей информационной безопасности.
В используемой большинством компьютеров архитектуре x86 защищённый режим существует ещё с 80-х годов. Первой доработанной реализацией этой «фишки» мог похвастаться легендарный процессор Intel 80386 (i386).
Обычно в ЦП встроен блок управления памятью (MMU), который:
- контролирует доступ к ней;
- запоминает местонахождение выделенных под приложения участков памяти после того, как их определяет операционная система;
- может впоследствии отвечать на запросы процессора вроде «может ли конкретная программа работать с памятью по конкретному адресу»
Допустим, я запустил на своём компьютере программы «Калькулятор» и «Вирус» — без root-привилегий и прав администратора. Затем я ввёл в программе «Калькулятор» выражение «20+3» и получил ответ — «23».
Что произошло?
- Операционная система выделила программе «Калькулятор» ячейки памяти с первой по десятую, а программе «Вирус» — с двадцатой по тридцатую.
- «Калькулятор», получив вводные данные «20+3» просит процессор: «посчитай, сколько будет „20+3” и запиши ответ в ячейку памяти №1».
- Процессор считает „20+3” и обращается к блоку MMU: «имеет ли программа „калькулятор” доступ к ячейке памяти №1?».
- Получив положительный ответ, процессор продолжает выполнение задачи и просит оперативную память записать в ячейку №1 число 23.
- Программа «Вирус» хочет украсть из памяти то число, которое считали в «Калькуляторе», и спрашивает у процессора: «скажи, что лежит в ячейке памяти №1?».
- Процессор обращается к блоку MMU: «имеет ли программа „Вирус“ доступ к ячейке памяти №1?».
Но мы знаем, что программе «Вирус» принадлежат совсем другие ячейки, и она не имеет прав для такого запроса. Это знает и блок MMU, который даёт процессору отрицательный ответ. Выполнение команды прерывается, а «Вирус» получает сообщение вроде «ай-яй-яй, вам туда нельзя».
Разобравшись с безопасностью, взглянем на не менее важную характеристику процессора и памяти — скорость.
Допустим, программа «Калькулятор» очень часто спрашивает процессор о содержимом ячейки №1. Неужели процессор каждый раз запрашивает эту ячейку из оперативной памяти? Это очень долго, и чтобы работать быстрее, процессор сохраняет в кэш-памяти содержимое ячеек, про которые его часто спрашивают — для обеспечения скоростного доступа к ним.
Разумеется, он обратится к блоку MMU для проверки в следующий раз, когда «Калькулятор» в спросит о содержимом ячейки №1, но затем сразу отдаст ответ из своей кэш памяти, не обращаясь к оперативной. Этот способ очень ускоряет работу программ: процессор отвечает на их запросы почти мгновенно. Однако люди всегда хотят большего.
Алгоритм предсказания ветвлений
За страшным на первый взгляд названием скрывается незамысловатый принцип работы. Представим, что у нас есть программа, которая просит процессор выполнить команду: «X=100; если X не равен нулю, то отними от него единицу; повтори заново всё, начиная с „если“». Из текста ясно, что при выполнении процессору придётся сначала проверить значение X, и только потом отнимать единицу. И сделать все это ему придется сто раз подряд.
Условие будет выполняться сто раз подряд — пока Х не станет равен нулю. Но ведь для этого нужно каждый раз доставать Х из кэш-памяти сравнивать его с нулём. А ещё обращаться к MMU и запрашивать у него разрешение на чтение из ячейки памяти, в которой хранится Х. Это слишком долго.
А почему бы не забежать вперёд? Если X двадцать раз подряд не равен нулю, то и на двадцать первый, скорее всего, тоже не равен. Пока идут все проверки, лучше заранее посчитать „X-1“ и сэкономить время. Если условие не выполнится — просто не отдадим подготовленный результат
Так появился алгоритм предсказания ветвлений: процессор запоминает, какие условия часто выполняются и начинает сразу выполнять следующие команды, отдавая результат только после окончания проверки.
Круто? Конечно, ведь процессор теперь не простаивает, ожидая ответа от памяти и MMU, а успевает за это время посчитать результат.
Безопасно? Казалось бы, да. Даже условие не выполнилось, процессор просто отбросит предварительно полученный результат, а программа никогда о нём и не узнает.
В чём же подвох и просчёт инженеров?
Представим ситуацию: на компьютере запущены «Вирус» и «Калькулятор», который всё ещё хранит в своей первой ячейке «23».
- «Вирус» просит у процессора: «Запиши в ячейку памяти №20 число „2”».
- Процессор делает запрос блоку MMU, который отвечает, что «Вирус» имеет право на запись в ячейку 20 — ведь ему давались ячейки с двадцатой по тридцатую.
- «Вирус» сообщает процессору: «X=20; скажи, что лежит в ячейке памяти под номером X; повтори 100 раз».
- Процессор обращается к MMU с запросом о правах доступа, а затем отдает содержимое ячейки №20.
Спустя несколько «проходов» срабатывает алгоритм предсказания ветвлений: «мы уже достаточно много раз получили положительный ответ, когда спрашивали у MMU, имеет ли "вирус" право на доступ к ячейке №X, значит, для ускорения можно заранее готовить ответ, не дожидаясь проверки, а после окончания проверки сразу же его отдавать».
Тут и начинается самое интересное.
- «Вирус» задаёт процессору такую задачку: «X=1; прочитай содержимое ячейки, номер которой — значение ячейки №X». Вдумчиво перечитайте это несколько раз, чтобы понять.
- Процессор сначала спрашивает, имеет ли «вирус» доступ к ячейке №X.
- Ответ на этот вопрос уже сто раз был утвердительный, а значит процессор не дожидается окончания проверки, и читает содержимое ячейки №1 — в ней лежат наши «23» из «Калькулятора».
- Процессор обращается к ячейке №23, которая принадлежит «Вирусу» и читает ее содержимое.
- От MMU приходит ответ: у «Вируса» нет доступа к ячейке №1.
- Процессор сбрасывает все результаты своей деятельности, не выдавая вирусу ничего.
Но где же уязвимость? На моменте, выделенным жирным шрифтом. Процессор не отдал «Вирусу» никаких результатов вычислений, однако все же прочитал содержимое ячейки №23, а значит закинул его в кэш. Как «Вирусу» узнать, что номер ячейки был именно 23?
Просто спросить у процессора, что лежит во всех принадлежащих ему ячейках, замерив время ответа. Для ячейки №23 оно куда меньше, чем для остальных. Потому что процессор уже обращался к ней и сохранил её в кэше.
То есть в ячейке №1 лежит число «23». Так «Вирусу» удалось его украсть.
Это и есть Meltdown.
Теперь вопросы
В: Подвержены ли этой уязвимости смартфоны и планшеты?
О: Компания ARM — разработчик архитектуры большинства мобильных процессоров — заявила об уязвимости ядер Cortex A15, A57 и A72 (используются в гаджетах 2014-2016 годов), которые подвержены одному из вариантов Meltdown. Он сложнее воспроизводится и не может получить доступ к произвольным областям памяти. То есть, и да, и нет.
В: Правда ли, что процессоры AMD не подвержены Meltdown?
О: Хотя в AMD говорят, что на их процессорах нельзя использовать эту уязвимость, в теории это возможно — просто пока не получалось на практике. То есть, процессоры AMD защищены лучше аналогов от Intel, но это не точно: позже кто-нибудь может обнаружить Meltdown и у них.
В: А Intel? Там плохо вообще всё?
О: Да, эти процессоры подвержены Meltdown в полной мере — все выпускаемые и большинство используемых. Кроме линейки Intel Atom, выпущенных до 2013 года и в основном использовавшихся в нетбуках.
В: А что с отечественными процессорами?
О: Элвис и Эльбрус находятся в том же положении, что и процессоры AMD. Так же, как и Байкал-Т1. А вот Байкал-М построен на базе Cortex-A57, а значит всё-таки подвержен одному из вариантов Meltdown.
В: Как защититься?
О: Большинство поддерживаемых ОС уже защищены — достаточно просто обновиться до актуальной версии.
В: Я слышал, что эти обновления замедляют систему. Мой старый компьютер будет тормозить?
О: Замедление заметно лишь в узком кругу задач. Для интернета, текстовых редакторов и большинства игр не будет никакой разницы в производительности. Личный пример: обновив ядро Linux на своем стареньком нетбуке на базе Intel Atom N550 (который хоть и не уязвим к Meltdown, не добавлен в «исключения» патча), я не заметил замедления.
В: Эту закладку сделали проклятые ГБшники в обнимку с ФБРовцами?
О: Вряд ли. Современные ядра настолько сложны, что можно встроить лазейку в менее заметное место. Например отдельным модулем, который никто никогда бы не разглядел.
В: Может быть это подстроили производители процессоров, которые хотят поддержать спрос?
О: По этому поводу есть основательные сомнения: закладывать уязвимость, а потом 20 лет надеяться, что её никто не обнаружит — похоже на фантастику. К тому же им сейчас придётся наспех выпускать «исправленные» процессоры, а это сильно ударит по продажам текущего поколения. Да и репутация не восстанавливается по щелчку.
В: Хакер может использовать эту уязвимость на моём компьютере?
О: Да, достаточно просто зайти на сайт злоумышленника. И он сможет получить через ваш браузер получить доступ к вашим данным с сайта банка, открытого в соседней вкладке. Поэтому нужно однозначно обновлять браузер и операционную систему, особенно у вас процессор от Intel.
В: Но ведь есть ещё одна уязвимость — Spectre.
О: К сожалению, просто описать принцип ее действия так же едва ли получится. Хотя ей подвержены почти все процессоры, воспроизвести её в реальных условиях затруднительно — особенно не зная некоторых данных конкретного атакуемого устройства. Хакеры вряд ли будут к ней прибегать.
Надеюсь, было понятно. Готов ответить на ваши вопросы в комментариях :) Если вы заметили какие-то логические неточности, то тоже пишите в комменты.
Материал написан под редакцией Александра Фаста. Большое ему спасибо.
Спасибо, отлично написано
Большое гуманитарное спасибо 🖤
Я как-то разрабатывал салонную ролевую игру - по типу мафии - и одну из ранних редакций правил тестировал на знакомых. Один из них, спустя некоторое время, нашёл стратегию, которая при соблюдении некоторых условий гарантировала выигрыш одной из сторон. Стратегия была хитрая, мудреная, требовавшая филигранного отыгрыша, но она работала. В тот момент я почувствовал себя так же, как должно быть чувствуют сейчас себя производители процессоров.
Ну вообще да. Такая неочевидная наебка, что просто так ты не допрешь до этого, и вообще даже не подумаешь о том, что так можно.
Самое интересное: сколько ещё подобного может сейчас оставаться незамеченным и будет раскрыто в будущем. Лично я, можно сказать, идеалист, и не люблю поговорку "лучшее — враг хорошего", но, похоже, в данном случае она действует на все сто процентов: хотели круче, быстрее, в итоге усложнили все до такого состояния, когда багу очень много где есть спрятаться.
Хорошая новость в том, что люди после этого начали задумываться об отказе от архитектуры x86. Все же, она уже очень старая и там много всего тянется за собой ради совместимости.
Комментарий недоступен
в консоли из под админа rm -rf /*
Бородатость этого прикола защекотала меня до смерти
Комментарий недоступен
Я что-то не понял, зачем тут про предсказание переходов. Meltdown работает проще и без него.
Ну, возможно я что-то не понял... А как тогда работает? Во всех источниках указано, что игнорировать MMU он начинает как раз из-за него.
Когда вы обращаетесь к памяти по смещению array[system_memory[x] * 4096], процессор успевает загрузить байт из system_memory[x] и даже поставить на загрузку в кеш данные из array[] по этому смещению ДО ТОГО, как выкинет исклчение. Соответственно потом array можно «прослушать» на скорость доступа к разным элементам — тот, к которому обращение быстрее всего, и был в кеше.
Множитель 4096 был в том объяснении, которое я смотрел, но выбор такого множителя кажется странным, ведь размер линеек кеша 64 байта, а 4096 байт — это размер страниц памяти, что как-бы намекает, что может быть дело даже не в кеше, а в этих страницах.
А уязвимость в предсказании переходов — это как раз Spectre. Но я там тоже не до конца понимаю, как все работает.
Ну все верно, да. Но какого хрена он делает это заблаговременно, по сути игнорируя MMU? Как раз благодаря модулю предсказания ветвлений же, не?
вы путаете предсказание переходов и спекулятивное выполнение, это не одно и то же.
Ну спекулятивное выполнение же работает в связке с предсказанием переходов, разве я не прав?
Не обязательно.
Out-of-order загрузка данных из памяти в кэш (что и происходит в meltdown) является спекулятивной операцией, но никак не относится к предсказанию переходов.
Ниже Томатный Супчик разъяснил.
Да, но ведь там сначала происходит чтение из ячейки, к которой программа не имеет доступа, затем чтение из ячейки, к которой программа уже имеет доступ, и загрузка ее в кэш. И на первом этапе ответ от MMU не ожидается как раз потому, что раньше он часто был "утвердительный".
По крайней мере, так я понял из тех материалов, которые я читал по этой проблеме.
В момент, когда юзер просит значение из ячейки номер Х, у процессора возникает вопрос к MMU "а юзеру вообще можно лезть в ячейку Х?" и продолжение истории делится на две _ветви_: "да, можно" и "нет, нельзя". Чтобы узнать, по какой ветви процессору нужно пойти, он ждет ответа на свой вопрос от MMU, но ждать-то слишком долго! Для этого и есть BPU, он же Branch Prediction Unit, он же модуль предсказания _ветвления_. Он может статистически решить, что выполнение продолжится по ветви "да, запросить значение можно" и процессор пойдет по этой ветви, пока MMU еще не успел принести фактический, НЕ статистический ответ. Потом MMU приходит и говорит "нет, нельзя лезть в эту ячейку", процессор понимает, что заранее побежал не в ту сторону и отбрасывает результат вычислений, идет по ветви "нельзя" и, например, выдаёт эксепшн. Только в кэш уже насрал, откуда мы все бережно заберём, применив хитрость с измерением времени доступа :з Так что все эти спекулятивные операции и происходят _исключительно_ благодаря модулю предсказания ветвлений.
Спекулятивная загрузка данных в L1 не является операцией требующей ветвления и не зависит от потока исполнения, хотя и контролируется инструкциями LFENCE/MFENCE ([1], 11.4.4.3; [1], ISR LFENCE).
[1] Intel 64 and IA-32 Architectures Software Developer's Manual
Хотя может я ввёл в заблуждение, включив L1 speculative fetching в спекулятивное выполнение.
Вот можем отбросить всю историю про кэш, про MMU, про остальные фичи, из которых строится уязвимость. Есть одна ветвь, есть другая. По результатам проверки условия нужно пойти по одной из ветвей. BPU может статистически предсказать результат условия => ветвь, что приводит к спекулятивному выполнению операций, «расположенных» на этой ветке. В контексте Meltdown И Spectre речь идет именно об этом спекулятивном выполнении, которое всегда является следствием работы BPU (а в конкретном нашем случае важны ошибочные выводы BPU)
В этом примере нет ветвлений, модулю их предсказания тут просто нечего делать.
MMU не игнорируется. С точки зрения приложения исключение возникает именно в том месте, в котором должно. И дальше выполнение кода не происходит. Но благодаря конвейеру (который обеспечивает выполнение разных частей разных команд в одно и то же время), данные по адресу запрашиваются заблаговременно и чуть раньше, чем происходит проверка доступа, что влечет за собой сайд-эффекты в виде данных в кеше.
Так. Кажется, возникла путаница.
BPU с кэшем оказывают нам медвежью услугу в обеих вариациях уязвимости, спекулятивное выполнение в принципе всегда заслуга BPU. Разница между Meltdown и Spectre лишь в том, что в первом случае BPU успевает насрать в кэш из-за долгой реакции MMU и мы можем косвенно залезть в чужую зону памяти, а во втором MMU нам вообще не важен, потому что весь экшн происходит в зоне атакуемого приложения, то есть MMU даже не ругается. Это даёт Spectre большую свободу и практически лишает нас способов защититься, но при этом в разы усложняет эксплуатацию, ведь атакуемый код должен САМ попинать целевые ячейки и позволить нам замерить время ответа, чтобы проверить, вступает ли в дело кэш.
Чую, я всё только усложнила, но tl;dr: модуль предсказания ветвлений играет ключевую роль в ОБЕИХ уязвимостях, да.
Без этого он бы все выполнил последовательно. То есть, подождал MMU, получил отказ, выкинул исключение, а обращаться никуда бы не стал.
Так получается что вирус играет на этой теме как на пианино вслепую. И с ячеек снимает «ноты», т. е. значения.
А как он адресует, складывает это во что-то осмысленное? Он так читает миллионы ячеек что ли?
И разве не ясно системе, что запросы с лавиной отказов — это ненормально?
Ну по сути да. А почему нет? Это все происходит мгновенно же, надо понимать. Просто посмотри, сколько операций в секунду способен выполнить даже "слабый" процессор, и будет понятно, что масштабы размера памяти, занимаемой среднестатистической программой, — это вообще ничто в данном контексте.
И разве не ясно системе, что запросы с лавиной отказов — это ненормально?А кому должно быть ясно? Процессор не обладает искусственным интеллектом же. Там алгоритмы попримитивнее, на уровне "запретить/разрешить, да/нет". Думать он не умеет, лишь считать. Да и отказаться от выполнения команд он, как бы, тоже не может, даже если и поставить туда какой-то хитрый алгоритм обнаружения противоправных действий.
Хотя, конечно, что касается использования в данных целях искусственного интеллекта, то за этой идеей определённо будущее. Кстати, немного отходя от темы, одним из первых (а возможно и первым) антивирусным программным продуктом, использующим нейросеть и ИИ, стала программа разработчиков из России. Читал об этом. Суть в том, что именно ИИ смотрит и думает над тем, какие действия противоправны. Это, судя по описанию, помогает защититься даже от 0day уязвимостей и куда эффективнее простых подобных программ, контрящих атаки чисто по базе.
это про кого речь?
Компания Wallarm
Понятно. Но как зловред может вытащить что-то осмысленное для себя из этого салата?
В каком смысле? Содержимое ячеек, по сути, уже осмысленная инфа. В некоторых случаях и одну достаточно украсть. Конечно, с помощью этой уязвимости вряд ли выйдет прям всю память сдампить, но, допустим, украсть что-то из соседней вкладки в браузере можно вполне.
Как это осмысленная? Судя по твоему описанию, проц надо спросить:“Зачение этой ячейки — Kolyanok?”. Он ответит да или нет.
Каков размер ячейки? Данные в ней в каком виде? Как формировать запрос? Как интепритировать, что тебе ответили?
Посмотрел на видео. Он просто, по сути, дампит память, состоящую из хексов.
Ну память — это набор числовых значений же. Размер линии кэша 64 байта, это 512 двоичных чисел (1 или 0).
А текст, если что, перед сохранением в память интерпретируется в числа по специальной таблице, где каждый символ сопоставляется со своим числом (допустим, а — это 1, б — это 2 и т.д.), это называется кодировкой.
Запросы с лавиной отказов могут быть и не в зловредных, а просто криво написанных программах. Если выпустить патч, который будет такие программы убивать, то могут быть неприятные сюрпризы
Ну вообще я понимаю суть претензии. Получилось растянуто. Однако, опустив многие моменты я бы нанес большой ущерб понятности.
А так все получилось совсем на пальцах. Я не находил подобного объяснения для тех, кто совсем не в теме, поэтому и написал эту статью...
Процессор обращается к ячейке №23, которая принадлежит «Вирусу» и читает ее содержимое.
т.е. если бы в калькуляторе было 45, то он бы обратился к ячейке №45?
извенити я тупой
Да
Чет упустил этот момент я...
п.с. Спасибо, отлично написано!
Комментарий недоступен
Да, именно так поступают в браузерах.
А что касается фикса KPTI, используемого в большинстве ОС, то я даже не знаю, как это объяснить просто, по крайней мере в контексте моих предыдущих примеров...
Комментарий недоступен
Нет, предсказания не убираются, иначе бы замедление было заметно во всех задачах.
Комментарий недоступен
О каком кэшировании речь?
Комментарий недоступен
Кэш тоже никто не отключает, причина та же. Фикс немного сложнее)