Meltdown и Spectre на пальцах

Если вы не были в коме последние две недели, то наслышаны про две найденые уязвимости во всех современных процессорах — Meltdown и Spectre. Их уже назвали самой большой жопой десятилетия. Мы или теряем все личные данные или замедляем все сраные комплюктеры на 20%.

Давно хотел про них рассказать, но рассылки только раз в две недели. На моё удивление мало кто за это время смог на пальцах рассказать как же они на самом деле устроены. Оставили эту тему для меня, спасибо!

Многоклеточному читателю очевидно, что все сложные технические аспекты упрощены до безобразия, чтобы у всех тут не закипели мозги.

Вся безопасность в процессорах со времён 80-х годов строится на одном простом правиле: одна программа никогда (вообще-вообще) не имеет прямого доступа к памяти другой программы. Это реализовано на аппаратном уровне специальным блоком внутри процессора. Если вы напишете свою программу, где попытаетесь прочитать что-то из памяти другой программы — процессор беспощадно бросит в неё прерывание, которое моментально её убьет. Когда падает ваш фотошоп — чаще всего именно это и произошло. Разработчик по ошибке обратился по адресу, в который ему нельзя. Процессор его убил, продолжать работу нельзя.

Пойдем дальше. Предположим, у вас есть простой цикл:

Для гуманитариев: пока X меньше 10000 — увеличивать X на единицу.

Каждый раз в начале цикла процессору надо пойти в память и прочитать значение Х. А если это вообще внешнее устройство? А если сложная функция? Слишком долго. Так в 1995 году в Intel придумали красивый хак: после пары сотен проверок условия x < 10000, процессор считает, что оно «с высокой вероятностью выполняется», потому в дальнейшем он начинает выполнение строки x = x + 1, не дожидаясь проверки условия.

Это сильно ускорило процессоры. Отныне они не ждали проверки сложных условий, а выполняли операции как бы заранее, спекулятивно угадывая куда пойдёт условие. Как только на определённой итерации поступал сигнал «стой, вот там условие не выполнилось», процессор просто отбрасывал «лишнее», что успел насчитать, и моментально возвращал один из результатов как итоговый. SO FAST, SO FURIOUS.

Вот где те самые «20% производительности», о которых гудят нерадивые СМИ.

Ради скорости в «спекулятивном» режиме разработчики процессора решили даже не проверять имеет ли программа доступ к участку памяти, где лежит наш Х. То есть в теории мы даже можем прочитать чужие данные, ведь Х — всего лишь адрес в памяти, который может быть любым. Но посчитанное всё равно остаётся только регистрах процессора, никто его оттуда не прочитает, а если вдруг ошибка — всё успеет упасть и сброситься. Поэтому как бы насрать.

Такие вычисления стали стандартом и используются давно и везде: в AMD, в ARM, да даже в российском процессоре Эльбрус.

Но есть в процессоре блок, которому насрать на любые условности — это кеш. Он работает независимо и сохраняет в себя любые данные, к которым кто-либо недавно обращался. В обычном режиме мы всё равно не сможем прочитать из него чужие данные, процессор не даст, но вот в спекулятивном…

Мы можем специально написать такой код, чтобы наш цикл заведомо 999 раз выполнялся успешно, процессор назвал его «с высокой вероятностью верным» и начал считать его в спекулятивном режиме. То есть наперёд, не дожидаясь никаких проверок. Теперь мы можем прочитать значение чужой ячейки памяти, оно даже сохранится в кеш, но вот вытащить его наружу процессор всё равно не даст.

Осталось провернуть маленький хитрый финт ушами. Теперь медленно следите за руками: а что если прочитать ячейку внутри своей памяти, чей порядковый номер совпадает со значением ячейки в чужой памяти. Еще раз на пальцах: в «чужой» ячейке №15000 лежит число 27 — мой возраст. Мы возьмем её значение в спекулятивном режиме и успеем прочитать ячейку №27 внутри доступного нам пространства адресов. Там лежит какой-то мусор, ведь мы её никогда не использовали, а только выделили, но нам насрать. Процессор одупляется и говорит «так, сбросить все регистры, у нас несанкционированный доступ». Все всё забывают, но остаётся кеш.

Кеш запомнил тот мусор, который мы прочитали из ячейки №27. А еще он в десятки раз быстрее памяти. Мы начинаем сканировать по порядку все ячейки от №0 и обнаруживаем, что на 27 ячейке время чтения как-то сиииильно быстрее, чем на любых других ячейках. Это значит, что в той самой «чужой» ячейке и лежало число 27. Опаньки.

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