Такое заявление может показаться странным для тех, кто более десяти лет работает над обеспечением безопасности памяти на аппаратном уровне, так что я его поясню. Безопасность памяти меня волнует в той же степени, в какой и сложение. Если оно работает, я могу строить на его основе что-нибудь интересное. По факту большинство интересующих меня вещей опираются на него как на фундамент. Если же сложение не работает, я не могу уверенно рассуждать о чём-либо, происходящем в программе.
То же касается и безопасности памяти. Для меня тот факт, что 70% уязвимостей возникают в результате её отсутствия, не говорит о важности этого аспекта. Важность безопасности памяти в том, что один связанный с ней баг может полностью подорвать все гарантии, на которые я опираюсь.
Доступ за границами выделенной памяти или баг с использованием данных после её освобождения могут привести к утечке памяти или повреждению любого состояния в программе. Если я думаю, что какие-то данные являются приватными для моего потока, потому что программа не сохраняет на них ссылку в местах, доступных для другого потока, то так будет, только пока программа обеспечивает безопасный доступ к памяти. Если я считаю, что объект иммутабелен, потому что не раскрываю изменяющие его API, и система типов говорит, что он иммутабелен, это будет так, опять же, только пока программа обеспечивает безопасный доступ к памяти.
Как только в ней появится хоть одна ошибка доступа к памяти, ни одно из этих свойств не сохранится, даже если вы формально проверили некоторые из них. Проект EverCrypt проделал феноменальную работу, создав формально верифицированные (включая защиту от атак по сторонним каналам) криптографические библиотеки, но свойства формально верифицированной программы подтверждаются только в случае действительности аксиом. Если же безопасности памяти нет, то и аксиомы не работают.
Если же у вас обеспечен безопасный доступ к памяти, вы можете начать создавать интересные вещи. Я начал работать над этим аспектом, потому что хотел создавать мощные компонентные системы. В 90-х различные платформы предоставляли обширные компонентные модели. В документы Word можно было встраивать инструменты управления COM (Component Object Model, объектная модель компонентов), которые встраивали другие мощные приложения. Большая часть всего этого исчезла, потому что опасно выполнять произвольный сторонний код. Пожалуй, единственными программами, которые делают это безопасно, являются браузеры (созданные конкретно под эту задачу).
В 2005 году я присутствовал на выступлении Алана Кея. В середине своего доклада он раскрыл неожиданный факт о том, что показанные им слайды являются не презентацией PowerPoint, а созданы с помощью языка Smalltalk. Он нарисовал несколько насекомых и написал код, который заставлял их бегать вдоль краёв вложенных изображений и видео. Алан задал разумный вопрос: «Зачем вам использовать программу, которая не является языком программирования?» К сожалению, если вы обмениваетесь документами, это означает выполнение кода других людей. Представьте, что у вас есть возможность получать код из интернета и встраивать его в документ, который вы отправляете кому-то, не боясь при этом скомпрометировать свою систему или систему получателя.
Вы можете начать делать подобное с помощью WebAssembly, но тогда столкнётесь с общей проблемой:
Изолировать легко, а вот безопасно обмениваться сложно.
Мы знаем, как всё безопасно изолировать. Эта техника десятилетиями использовалась для защиты ядерных пусковых установок. Тут всё просто. У вас есть компьютер, подключённый к другим компьютерам. Вы помещаете его в запертую комнату, у дверей которой ставите вооружённую охрану с приказом стрелять по всем, кто попытается проникнуть внутрь без разрешения.
В случае менее критических систем можно исключить момент с вооружённой охраной и даже запертой комнатой. Именно поэтому модули управления памятью (memory management units, MMUs), используемые в общедоступном аппаратном обеспечении, изменили мир. Благодаря им, ошибка в одной из нескольких выполняющихся программ не ведёт к сбою в другой и не рушит всю систему, но при этом программы могут обращаться к одному набору файлов. Два пользователя могут использовать одну систему, и ядро будет предоставлять им доступ как к личным, так и к общим коллекциям файлов.
Отсутствие безопасности памяти мешает нам создавать крутые вещи.
Именно поэтому меня не вдохновляет, когда она сопровождается примечанием вроде (MTE, Memory Tagging Extension), то есть «до тех пор, пока кто-нибудь не подберёт 4-битное число». Или если для полной компрометации вам нужно подменить три указателя, то «до тех пор, пока кто-нибудь не подберёт 12-битное число». Таким образом, если у вас обнаружится баг с безопасным доступом к памяти в Windows или Android, вы скомпрометируете лишь одного из 4,096 пользователей, то есть всего получается около пяти миллионов систем (при условии, что у вас всего одна попытка), и наверняка будете обнаружены в одной из тех, которую скомпрометировать не удастся.
Или примечанием вроде «пока вы не используете небезопасное ключевое слово, небезопасный пакет или пакет
sun.misc.Unsafe
» (Rust, Go, Java). Или: «пока вы не используете код из небезопасных языков» (во всех безопасных языках).Но я хочу использовать код из небезопасного языка! На GitHub есть тринадцать миллиардов строк С/С++, которые я не горю желанием переписывать (или платить кому-то за это) на безопасном языке. Хочу иметь возможность переиспользовать этот код, но ограничить его так, чтобы минимизировать баги. Хочу производить из него вызовы, зная, что он может выполнять запись через передаваемые указатели, но не может получить доступ к чему-либо не расшареному. Хочу быть уверен, что он не может ничего передать в мой процесс (не говоря уже о других местах на компьютере), пока я открыто ему это не разрешу. Хочу не беспокоиться о том, есть ли в нём баги, потому что могу явно ограничить зону поражения, исключив неприятные последствия в перспективе, которые предполагают ошибки, связанные с безопасным доступом к памяти.
Создавать такие системы нам позволяет платформа CHERI (Capability Hardware Extension to RISC-V, аппаратное расширение возможностей архитектуры RISC-V).
Очень здорово, что мы можем сократить число багов безопасного доступа к памяти или усложнить их эксплуатацию. Переписывание кода на безопасных языках исключает категории сбоев и делает мир лучше. Если вы можете позволить себе затраты на такое переписывание, делайте это! Применение средств противодействия, которые сократят число потенциально захваченных атакующими машин с двух миллиардов до пяти миллионов, значительно уменьшает возможный вред. Но такие подходы не позволяют мне создавать впечатляющие системы.
Большинство публикаций по технологии CHERI посвящены выполнению уже существующего ПО.
Возможность выполнять всё современное POSIX-совместимое ядро, пользовательское пространство и кучу ПО с безопасным доступом к памяти очень важна, поскольку именно такое ПО существует сейчас в мире. Но это не раскрывает те возможности, которые становятся доступны, когда мы можем рассматривать безопасность памяти как что-то просто работающее. Аналогично тому, как просто работает целочисленная арифметика (которая намного понятнее арифметики чисел с плавающей запятой).
И с появлением CHERIoT мы начали постепенно приближаться к такому миру. Вы можете взять существующий код C/C++ и перекомпилировать его для выполнения в сегменте (compartment) CHERIoT. Вы можете принять как должное тот факт, что любой доступ за границами выделенной памяти и использование памяти после освобождения будут перехвачены. Вы можете расшарить объект с другой ячейкой, передав в неё указатель. Вы можете полагаться на то, что в случае передачи в другой сегмент указателя на объект в стеке (или любого указателя, который явно обозначите как временный), любая попытка перехватить этот указатель будет пресечена. Вы можете расшарить представление буфера только для чтения, передав указатель без разрешения записи, или сложную структуру данных, передав его без транзитивного разрешения записи.
У этой платформы есть, как её называет Роберт Ватсон, «модель разработки ПО ‘пылесос’»: вы направляете свой пылесос в интернет, всасываете все нужные компоненты и потом поставляете. Только теперь вы можете проверить конкретно, к чему каждый из них имеет доступ. Поскольку даже код ассемблера должен следовать фундаментальным правилам для обеспечения безопасности памяти, вы можете составить политики, диктующие, к чему эти компоненты должны иметь доступ, и проверять их до подписания образа прошивки.
Простое угадывание адреса, по которому находятся важные данные или код, не даст вам возможности получить доступ к этой памяти. Вы можете писать безопасный код, не беспокоясь о целом классе ошибок, и обоходиться без развёртывания дорогостоящих обновлений безопасности для компонентов, которые находятся в изоляции. Самое же главное, вы будете понимать характеристики безопасности фрагмента кода, просто глядя на его интерфейс.
Платформа CHERIoT ориентирована на небольшие системы, поскольку целесообразно заменить всю операционную систему чем-то, что предоставит абстракции, которые намного безопаснее и юзабельнее всего, что может предоставить существующее оборудование. И даже на этом мы задействуем лишь самую малость того, что становится возможным, когда при создании системы безопасность памяти (включая целостность ссылок и потока управления) становиться её неотъемлемым свойством.
Думаю, с момента выхода аппаратного решения CHERI пройдёт ещё лет десять, пока операционные системы потребителей смогут использовать его для реализации основных абстракций. Но есть в этом то, что меня действительно вдохновляет, а именно возможность глубоко встраивать в сложные системы недоверенный код, который будет обширно взаимодействовать с другими фрагментами недоверенного кода.
Надеюсь встретить вас всех в этом мире.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (7)
Apoheliy
15.04.2024 15:31+3По-моему, автор статьи слегка передёргивает: безопасности памяти В ПРИНЦИПЕ не может быть, пока есть понятия разъёма pci, pci-ex или подобное.
Так как: типовое взаимодействие с внешним устройством (сетевая карта, звуковая или ещё +100500 устройств): программа отправляет некий адрес памяти во внешнее устройство и потом устройство само, БЕЗ участия центрального процессора, пишет в эту память данные.
Если программа чуть-чуть ошибётся в отправляемом адресе (и укажет на адреса в другой программе/процессе), то от повреждения памяти (по-моему) уже ничего не спасёт.
От этого можно защититься, только работать всё будет слегка медленно.
Примечание: текст по ссылке на CHERI как раз об этом и говорит: защита от некоторых угроз, связанных с виртуальной памятью и C++.
CodeRush
15.04.2024 15:31+1IOMMU/DART решают эту задачу, и DMA-атаки перестали быть сколько-нибудь серьезной проблемой на любых нормально настроенных устройствах уже лет пять примерно.
Self_Perfection
15.04.2024 15:31На любой Rust найдётся свой Rowhammer, увы.
c0r3dump
15.04.2024 15:31+2Всё же эксплуатация выхода за границы массива значительно проще чем rowhammer. Есть вообще хоть один реальный RCE с Rowhammer? А то на шифрование тоже криптоанализ с паяльником найдётся, не передавать же данные в открытом виде из-за этого?
Self_Perfection
15.04.2024 15:31+1Справедливо. Не знаю таких примеров. Но я и не хочу сказать, что усилия для безопасности памяти бесполезны.
Только что она не даёт абсолютной защиты.
Dj_Art
Этот момент уже наступил - просто купи Эльбрус с ТЪВ(трушно безопасными вычислениями) и вот оно. И, что самое забавное, Эльбрус купить можно, а её нет.
И вдогонку про состояние компилятора под cheriot, чтоб жизнь малиной не казалась.