Как стать автором
Обновить

Путешествие туда и обратно за безопасным ELF-парсером

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров1.5K
Всего голосов 8: ↑8 и ↓0+11
Комментарии7

Комментарии 7

Тоже столкнулся в полный рост с тем, что безопасный парсер бинарных данных на С или С++ написать очень сложно. "Боевых" библиотек для парсеров-комбинаторов нет, а то, что есть - примерно такого же качества, как тут в статье и описано, т.е. либо "заброшено", либо "автор не чинит известные уязвимости, и гордится этим".

Остановился в итоге на двух решениях, которые к парсингу подходят с разных сторон. На KaitaiStruct пишу то, что для безопасности критическим не является, и для чего удобство разработки парсера важнее его безопасности. Реально критическое по безопасности делаю на WUFFS.

Можно было сделать линтер ELF-а, который отдельно совершенно от процесса загрузки и запуска проверяет ELF и, скажем, подписывает его своей подписью, если всё ОК. И запускать его на хосте, в процессе сборки (что с точки зрения целевой ОС эквиавалентно сендбоксингу). И поскольку это - совершенно отдельная тулза, можно было бы использовать в ней и GPL-ный код, а саму тулзу заопенсорсить. Или написать ее на Go, с использованием ELF-парсера из стандартной библиотеки Go - он там, как минимум, используется, а не просто лежит, т.е. обкатанный. И написан на безопасном языке.

А системный загрузчик мог бы даже и не пытаеться грузить ELF, если подпись не сходится.

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

А по поводу отдельного линтера: да, с точки зрения секьюрити лучше выносить эту активность в отдельное адресное пространство (процесс), тем самым сокращая поверхность атаки для основного elf загрузчика.

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

Да. Причем можно поместить подпись (подписи) в фиксированное место в конце файла, которое оформлено, как ELF-секция (чтобы файл оставался валидным ELF-файлом), но расположение подписи в конце файла избавляет проверяльщик от необходимости парсить ELF.

Вот эти ребята:

У них unikernel, способный запускать настоящие линуксные бинарники на голом железе (ну, на виртуальном, конечно). У них внутри эмулятор API линуксного ядра. Вряд ли совместимый на 100%, но достаточно совместимый, чтобы гошные микросервисы запускать. А гошный рантайм, он такой, хитренький. Много чего знает про ядро и активно использует.

Я так понимаю, у них внешний загрузчик ELF. Т.е., даёшь ему бинарник, он разбирается, какие ему нужны динамические библиотеки, свинчивает их вместе между собой и с эмулятором ядра и рожает на выходе загрузочный образ. И всё это снаружи, не во время исполнения. Т.е., ломай его. не ломай, на безопасность во время исполнения это никак не повлияет.

Особо не работал с таким, а там, получается, полноценная виртуализация для изоляции используется?

Ну примерно, да. Это unikernel - т.е., каждому процессу достаётся своя собственная виртуальная машина со своим собственным ядром ОС. Но ядро, оно такое, специфическое. Стандартное ядро стандартной ОС реализует, по сути, следующуе задачи:

  • управление ресурсами

  • изоляцию процессов

  • всякие полезности и удобный API к ним (ну, там, сети/сокеты, файлы, нити, мьютексы...)

Но поскольку здесь каждому процессу достаётся своя личная виртуальная машина, то первые две задачи решать на надо, ресурсы ограниченны и тождественны ресурсам виртуалки, а изоляцию обеспечивает виртуализация же.

Поэтому тут ядро весьма специфическое. В большинстве случаев в unikernel-ах используется некий самодельный API, но эти вот ребята осилили сделать линукс-совместимый API и ABI - достаточный для того, чтобы работали немодифицированные линуксные исполняемые файлы типа тех, которые обычно в облачном хостинге запускают (т.е., гуёвая аппликация с OpenGL вряд ли запустится, но вот нормальный микросервис - вполне).

И они используют это in production уже сколько-то лет, т.е. это вполне зрелый продукт, не студенческая самоделка.

При этом, насколько я понимаю, у них динамическая линковка внешняя. Т.е., исполняемый файл, его динамические библиотеки и это вот недоядро свинчиваются в единый загрузочный образ, и он запускается на виртуальной машине. Для приложения это выглядит так, словно его жизнь началась уже после того, как ld-linus.so отработал.

Там даже адресное пространство для приложения и недоядра единое, но это не проблема в такой архитектуре. Считайте, недоядро - это такое дополнение к libc, нет разницы, грохнет приложение libc или недоядро - в любом случае, оно грохнет только себя. И они на этом выигрывают (по их утверждениям - сильно) на переключениях контекстов между user и kernel spaces.

Заметим при этом, именно вот полноценная виртуализация тут не является 100% необходимым условием. Есть же проект User Mode Linux, в котором полноценное линуксное ядро запускается как user-space процесс на "настоящей" системе и может внутри себя запускать свои собственные процессы. Ядру, по большому счету, не нужен доступ к железу. Если ядру дадут какой-то нежелезный способ, к примеру, отправлять-принимать сетевые пакеты (например, через обращения к внешнему по отношению к нему "настоящему" ядру), то сетевой стек в ядре будет вполне счастлив.

Я это к тому, что вы, наверное, могли бы спортировать nanos под своё ядро, которое при этом не обязано реализoвывать полноценную виртуальную машину, а достаточно паравиртуализации - аналогично User Mode Linux. Я знаю, ваше ядро так может, я немного в курсе устройства вашей ОС.

Этим вы решите невероятное количество своих проблем. Например, проблемы с производительностью (присущие всем микроядрам), проблемы с фиксированным ABI (он станет разумным подмножеством ABI Linux). Проблемы со сборкой/мейнтейнингом user space - вы сможете использовать напрямую линуксные исполняемые файлы, иногда с минимальными модификациями, иногда вовсе без них. Проблемы совместимости API со стандартными ОС, что качественно упрощает разработку user land. Перечислять можно долго...

Замечу так же, что сам по себе nanos, его ядро - на удивление маленький проект, там немного более 100 тысяч строк вполне аккуратного, внятного кода. Т.е., проект вполне обозримый, его реально осознать и модифицировать под свои нужды.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий