Компоненты ПО с открытым исходным кодом сейчас встречаются почти в каждом приложении. Это повышает эффективность разработки, но привносит дополнительные риски, в первую очередь связанные с атаками на цепочку поставок. Создавая операционную систему KasperskyOS, мы в «Лаборатории Касперского» задумались: как сделать переиспользование недоверенного кода безопасным? Эта задача особенно актуальна, когда речь идет о системе, на базе которой строятся продукты для отраслей с повышенными требованиями к кибербезопасности.

В этой статье мы расскажем, какие механизмы в KasperskyOS позволяют снизить риски, характерные для распространенных ОС. А также покажем на реальном примере, как системы на базе Linux и KasperskyOS по-разному справляются с киберугрозами.

В качестве примера мы выбрали сценарий вредоносной функциональности, заложенной в open-source-драйвер сетевой карты. В свете роста количества и сложности атак на цепочку поставок сценарий более чем реалистичный. Если вам интересно, почему мы выбрали именно сценарий использования Linux-драйвера и атаку на цепочку поставок, — оставим объяснения под спойлерами.

Почему в примере именно драйвер Linux

Ядро Linux как единая база драйверов и компонентов для новых ОС

Сегодня системы с открытым исходным кодом получили широчайшее распространение и используются практически во всех сферах жизни. Облачные платформы, хостинг-провайдеры, банковские системы, медицинские учреждения, системы управления производствами и множество других сервисов активно используют решения на базе ядра Linux.

Само ядро Linux — это источник инженерных решений, включающий огромную базу исходного кода с реализациями широко используемых структур данных, файловых систем, сетевых протоколов и драйверов устройств. Именно драйверы для ядра Linux рассматриваются как универсальный интерфейс общения производителей оборудования с авторами операционных систем.

Популярность и доступность ядра Linux привели к тому, что его компоненты, в первую очередь драйверы, стали использоваться за пределами Linux-экосистемы. Например, FreeBSD позволяет загружать в свое ядро немодифицированные драйверы Linux. Фреймворк для разработки операционных систем Genode предлагает методологию по переносу драйверов Linux в системы на его основе. Huawei заявляет о поддержке в микроядерной HarmonyOS NEXT контейнеров с драйверами Linux. Даже любительские проекты операционных систем, такие как KolibriOS, используют некоторые драйверы ядра Linux, перенесенные в их кодовую базу, например, для работы со встроенной графикой Intel.

Почему именно атака на цепочку поставок

Краткая история атак на цепочку поставок

За последние несколько лет мы видели немало инцидентов, в которых злоумышленники так или иначе атаковали цепочку поставок. Вот самые заметные из них:

2020 год — взлом компании SolarWinds, которая поставляет решения для мониторинга IT-инфраструктуры. Инцидент продемонстрировал серьезность угрозы, так как в результате атаки скомпрометированными оказались тысячи государственных и частных организаций по всему миру.

2021 год — к аналогичным последствиям привел взлом Codecov, когда была скомпрометирована утилита анализа покрытия кода.

2023 год — зафиксировано заражение распространяемых Microsoft артефактов через атаку на JFrog Artifactory, менеджер, использовавшийся в инфраструктуре корпорации.

2024 год — целый букет инцидентов:

Современные атаки на цепочку поставок часто развиваются продолжительное время и используют многовекторный подход. Злоумышленники применяют и социальную инженерию, и перехват доменных имен, и взлом GitHub-аккаунтов разработчиков популярных зависимостей ПО. Причем компрометация происходит через вредоносные изменения сборочной инфраструктуры, подмену динамических библиотек и даже через модификацию исходных кодов оригинального продукта.

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

Вредоносные модификации в системном ПО

Инцидент с XZ/liblzma показывает, что бэкдоры и намеренно внедренные уязвимости могут присутствовать даже в популярном системном ПО. Вредоносная модификация системного кода особенно опасна — это значительно усложняет выявление угроз и повышает потенциальный ущерб от атак.

Но возможно ли существование бэкдоров в таких проектах, как ядро Linux? Казалось бы, код ядра этой ОС проходит через серьезный процесс ревью: предполагается, что мейнтейнеры из публичных списков рассылки не пропустят зловредный патч в релиз. Но на практике даже опытные разработчики, сведущие в вопросах информационной безопасности, могут не заметить опасные изменения. Одна из причин заключается в том, что язык C, на котором разработано большинство системных программ, включая ядра ОС, позволяет скрывать неочевидное поведение за счет принятых умолчаний, undefined/unspecified поведения, неявных преобразований типов и особенностей препроцессора.

Вообще, возможность намеренного внедрения бэкдоров в операционные системы с открытым кодом обсуждается уже много лет. Еще в 2010 году бывший подрядчик ФБР заявил о том, что агентство когда-то внедрило уязвимости в код OpenBSD. Когда про потенциальную закладку стало известно, лидер проекта Тео Де Раадт провел серьезное ревью проблемных мест в коде. За много лет развития системы код, разумеется, серьезно поменялся, но Де Раадт все равно обнаружил потенциальные уязвимости. Сказать однозначно, были ли эти уязвимости добавлены кем-то намеренно или нет, — нельзя. Однако то, что их не заметили при первичном ревью, когда принимали патчи, — факт.

Сокрытие вредоносных изменений

Как ревьюеры могут не заметить внесение вредоносного кода в проект? Существует целый ряд методик, которые помогают злоумышленникам внедрять скрытые изменения в программное обеспечение. Если интересны конкретные примеры, то можно изучить результаты конкурса Underhanded C Contest. В его рамках требуется создать программу, которая выполняет оговоренную злонамеренную активность в дополнение к вполне безобидной функциональности. Важное условие: злонамеренное поведение должно быть трудно определяемым на ревью кода специалистами по информационной безопасности.

Дополнить картину может известная работа Кена Томпсона Reflections on Trusting Trust, поднимающая вопрос о доверии к инструментам разработки, например компиляторам. Ведь компилятор может инструментировать код, оставляя в нем уязвимости, генерируя пути обхода вызовов аутентификации популярных библиотек (например, PAM).

Кроме того, нередко необходимые BSP (Board Support Package) и драйверы ядра не входят в дерево исходного кода ядра Linux, а поставляются непосредственно производителем оборудования (типичная ситуация для китайских устройств). Таким образом, в них можно встретить уязвимости, которые широкое Linux-сообщество даже теоретически не могло заметить. Причем эти драйверы зачастую имеют невысокое качество, содержат специализированные хаки/оптимизации, используют нестабильные интерфейсы ядра и так далее. Так что уязвимости в них можно встретить и без всякого злого умысла.

Модели угроз современных ОС, «песочницы» для приложений и драйверов

Разумеется, при создании собственной операционной системы мы не собирались изобретать велосипед. Поэтому для начала мы провели анализ распространенных стандартов безопасности операционных систем и безопасной разработки, а также изучили подходы, которые применяют создатели современных коммерчески успешных ОС. В числе прочих мы изучили Android, ChromeOS, GrapheneOS, Whonix, Ubuntu Core, Qubes OS, Genode OS Framework, Legato, HarmonyOS NEXT, OpenBSD, seL4.

Большинство этих систем переосмысляют взгляды на модель угроз персональных и профессиональных устройств. То есть устройств, которые круглые сутки подключены к глобальной Сети и обрабатывают контент из недоверенных источников со сложной структурой, а также позволяют расширять функциональность за счет приложений, разрабатываемых третьими сторонами. При этом такие устройства могут быть атакованы с использованием разнообразных беспроводных технологий, таких как NFC, Bluetooth и Wi-Fi. А носимые девайсы вдобавок могут попасть в руки злоумышленников, то есть для их защиты необходимо рассматривать угрозы, предполагающие еще и физический доступ.

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

Наша команда, ответственная за разработку KasperskyOS, пришла к выводу о необходимости распространения этого подхода дальше уровня приложений, на другие, традиционно менее изолированные системные компоненты. В их число входят драйверы устройств и сетевой стек — как раз для снижения рисков, связанных с эксплуатацией потенциальных закладок или уязвимостей в коде Linux-драйверов и подсистем. Это позволит безопасно использовать Linux-драйверы при необходимости ускоренного портирования KasperskyOS на новые аппаратные платформы.

Угрозы и способы их митигации в KasperskyOS

Благодаря микроядерной архитектуре в KasperskyOS удается реализовать эффективные компенсирующие меры, способные снизить влияние множества рисков, характерных для монолитных операционных систем. Давайте рассмотрим часть модели угроз, разрабатываемой для KasperskyOS, связанную именно с заявленным сценарием — закладкой в Linux-драйвере устройства.

При моделировании угроз часто используются схемы потоков данных в системе. На них указывают как активные сущности, например процессы, так и пассивные, представляющие хранилища данных (файлы, записи баз данных, регистры устройств, структуры данных в памяти), непосредственно информационные потоки и границы доверия.

В популярных операционных системах на основе монолитных ядер для драйвера существует только две границы доверия: между пространством пользователя и ядром ОС и между ядром ОС и контролируемым оборудованием. Это не позволяет рассматривать угрозы драйверов в отрыве от угроз ядру операционной системы. При таких ограничениях крайне трудно митигировать угрозу атак на цепочку поставок драйверов, включающую возможность злонамеренной модификации их исходного кода и обнаружения случайных эксплуатируемых уязвимостей. В отличие от мира Windows, где имеется практика подписывания драйверов и их централизованное распространение, в Linux практически единственным эффективным решением оказывается использование харденингов, затрудняющих эксплуатацию. Как показывает опыт, даже самые продвинутые харденинги не способны остановить настойчивого и изобретательного взломщика. 

Системы на основе микроядра лишены этих ограничений. Благодаря иной организации архитектуры и разделению компонентов системы в некоторых случаях она допускает использование дополнительных границ доверия и дает возможность более детально и эффективно рассматривать угрозы.

Рассмотрим сокращенную модель угроз абстрактному драйверу устройства. К источникам угроз отнесем непосредственно устройства, взаимодействующие с драйвером, пользовательские процессы, для которых драйвер осуществляет абстракцию над устройством и мультиплексирование его ресурсов, стеки и подсистемы, предоставляющие дополнительные уровни абстракции над драйвером, например сетевой стек.

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

Угроза

Подугроза

Описание

Желаемое поведение

Митигация

DRV 1*. Отказ в обслуживании

DRV 1.1. Отказ в обслуживании всей операционной системы вследствие нарушения работы драйвера

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

В большинстве популярных операционных систем эту угрозу невозможно митигировать

Нарушение работы драйвера не приводит к отказу в обслуживании всей системы

Выполнение драйвера в «песочнице» с пониженными привилегиями. Драйвер запускается в отдельном процессе пространства пользователя. API ядра, связанный с выделением физической памяти, должен быть 
доступен только доверенному менеджеру драйверов, сами драйверы должны получать необходимые ресурсы (порты, регионы памяти, номера прерываний) через передачу Object Capability на соответствующие объекты. Возможны разные реакции на ошибочную остановку работы драйвера (завершение процесса): перезапуск, запись в журнал аудита

DRV 1.2. Отказ в обслуживании зависимых сервисов вследствие чрезвычайно длительной активности в ходе работы драйвера

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

Нарушение работы драйвера не приводит к отказу в обслуживании зависимых сервисов

Запуск драйвера в отдельном процессе. Мониторинг здоровья драйвера: «проверка пульса», сторожевой таймер. В ряде случаев подобные меры позволят перезапустить зависший драйвер и восстановить контекст его работы

DRV 2. Утечка информации

DRV 2.1. Утечка данных, размещенных в куче

Вследствие доступа за допустимые границы объекта, неинициализированных значений или иных ошибок в процессе обработки пользовательских данных возможна утечка данных из драйвера и ядра ОС. Такие данные могут содержать секреты, например ключи шифрования, используемые в ядерном Crypto API

Данные не утекают, или утечка ограничена нечувствительными данными

Отделение драйвера от ядра, запуск в пространстве пользователя. Благодаря этому драйвер не разделяет общих секретов с ядром ОС.

Применение практик безопасного программирования, SDL.

Валидация параметров вызовов генерируемым парсером

DRV 2.2. Утечка данных, размещенных в стеке

* Коды угроз являются символическими и используются исключительно для внутренних ссылок в рамках данной таблицы и настоящей статьи.

Демонстрационный сценарий

Рассмотрим простой сценарий из модели угроз, приведенной выше: в драйвер сетевой карты намеренно заложили функциональность, приводящую к отказу в обслуживании. В системе присутствует критический пользовательский процесс, осуществляющий контроль оборудования, драйвер сетевой карты и сетевой стек используются для передачи журнала наблюдения за оборудованием. Полная остановка системы может привести к порче дорогостоящего оборудования. Нарушение в передаче журнала событий рассматривается как инцидент невысокого уровня.

На базе этого сценария мы построили экспериментальный стенд с использованием Radxa ROCK 3A. Стенд состоит из двух образов, выполняющих идентичную бизнес-задачу. При этом один образ реализован на простой сборке GNU/Linux, а другой использует KasperskyOS Community Edition. Обе системы имеют идентичную модельную «закладку» с уязвимостью в сетевом драйвере. Отправка специально сформированного пакета вызывает незамедлительный переход драйвера в ошибочное состояние.

Эксперимент демонстрирует сохранение критической функциональности с KasperskyOS и падение ядра ОС на основе GNU/Linux. Отметим, что данный пример реализует защиту от конкретной уязвимости в конкретном сценарии. Для митигации других угроз следует правильно подходить к архитектуре решения и использовать различные паттерны безопасности.

Продемонстрированные в эксперименте варианты атаки на цепочку поставок, потенциально способные оказать серьезное влияние на работоспособность системы и безопасность данных, сейчас актуальны как никогда. Тем не менее, такого рода угрозы редко рассматриваются разработчиками операционных систем, поскольку без возможности изоляции драйверов в песочнице у них нет возможности серьезно повлиять на реализацию этих угроз. Все что им остается — надеяться на добрые намерения разработчиков открытого системного ПО и качество доступных харденингов. 

Описание стенда и код можно посмотреть в нашем репозитории на GitFlic. Если вам интересно узнать больше о нашей операционной системе и заложенных в ней принципах безопасности, вы можете воспроизвести наш пример, установив KasperskyOS Community Edition.

Комментарии (8)


  1. Apoheliy
    25.06.2025 22:56

    Один из вопросов про запуск драйверов в "песочнице": как делается обработка прерываний? Обычно обработчик прерываний интенсивно общается со своим оборудованием (как минимум, информирует устройство, что нужно снять флаг прерывания).

    Причём обработчик в "обычном линуксе" для этого делается легковесным, все вызовы по минимуму, никаких блокирующих операций (условно: ни новой памяти, ни запросов в какие-либо службы). Причина понятна: любой лок может подвесить систему. И здесь запуск с привилегиями ядра выглядит вполне логичным.

    Если же мы поместим драйвер с обработчиком в пространство пользователя (в "песочницу") и любой вызов будем прогонять через привилегии и проверки, то ...

    Вот и интересно, как это у вас сделано! Ничего не блокируется? Или "здесь" проверяем, "здесь" не проверяем?


    1. 15432
      25.06.2025 22:56

      Драйвер с обработчиком в пространстве пользователя, регистрация обработчика как колбэка. Теоретически можно покрыть проверками, чтоб дрова не могли регать чужие IRQ себе.

      https://support.kaspersky.ru/kos-community-edition/1.3/libkos_irq_api

      А вот что будет, если в таком колбэка драйвер тупо подвиснет, надо смотреть, зависнет ли подсистема обработки IRQ в ядре


    1. AnnaTref
      25.06.2025 22:56

      Спасибо за правильный вопрос. Почитать про обработку прерываний в КОСи можно в документации - https://support.kaspersky.com/help/KCE/1.3/ru-RU/libkos_irq_api.htm. Вкратце, если сравнивать с Линуксом, то в КОСи вся обработка - это bottom half, но в выделенном real-time-овом потоке. При обработке запрещены все блокирующие вызовы. Доставка прерываний на тот же вектор на время обработки прерывания запрещена на уровне контроллера прерываний. Это конечно медленнее, но MSI уже на подходе.


      1. apevzner
        25.06.2025 22:56

        Возникает проблема с разделяемыми прерываниями

        Если одно прерывание относится к разным драйверам, на каждом из них лежит ответственность "сбросить" свою причину для возникновения прерывания.

        Если драйвер этого не сделает, прерывание будет приходить снова и снова.

        Можно, конечно, сделать автодетект spurious interrupts, и отшибать захрясшее прерывание. Но при ложном срабатывании есть шанс заодно отстрелить ни в чём не повинное устройство, которому не повезло оказаться на том же прерывании.

        Вот, даже в линуксе такое происходит, даже на очень обкатанном железе, а не на какой-то экзотике (интеловский чипсет, встроенный в него контроллер I2O, на нём висит совершенно стандартный тачпад). Приходится даже иногда делать неприятные костыли, типа этого:

        https://github.com/alexpevzner/hotfix-kvadra-touchpad


  1. apevzner
    25.06.2025 22:56

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

    Правилов песочницы драйвер при этом не нарушит...


    1. Apoheliy
      25.06.2025 22:56

      Возможно, использовать IOMMU, которое не даст через DMA сходить куда не положено.

      И тоже интересно: как от всего этого защищаются?

      Может автор ещё пару-тройку статей напишет?


      1. apevzner
        25.06.2025 22:56

        Во-первых, IOMMU есть не на всякой платформе. Во-вторых, работает оно с переменным успехом.

        И главное, когда доходит до сравнения микроядерной архитектуры с монолитом, микроядерщики очень любят говорить, что их подход принципиально надежнее потому, что поверхность атаки меньше.

        Но при этом не говорится о такой обширной и малоисследованной поверхности атаки, как аппаратура.

        Если гарантии защиты, предоставляемые процессором, более-менее широко известны и хорошо исследованы (что не мешает находить в них уязвимости с завидной регулярностью), то чипсеты и периферийные контроллеры, никто их толком не исследовал.

        При этом процессор, его интерфейсы открыты всем разработчикам и он всё время под пристальным вниманием. А драйвера пишутся по принципу, "работает и ладно". При обнаружении ошибок в аппаратуре, если есть возможность обойти её в драйвере, то и хорошо. Но целью всегда является соблюдение функциональных требований. Если сетевая карта посылает и принимает пакеты, ну, всё ОК. Никто никогда не зафайлит ошибку, позволяющую добраться через сетевую карту к чужой памяти, потому что штатный драйвер этого делать не будет, а возможность работать с недоверенным драйвером никогда не исследовалась (и чтобы всерьёз этим заниматься, драйвер надо с самого начала проектировать под эту задачу, а не помещать опенсорсный в песочницу).

        Не надо думать, что в аппаратуре нет ошибок. Их там есть, и очень много. И поскольку исправление ошибок в аппаратуре очень дорого, в целом, если ошибку можно обойти в драйвере, это считается вполне приемлимым решением. Но работает оно только при условии, что драйверу можно доверять.

        Современная аппаратура очень сложная, и нередко там не только микросхемки, но и сложная прошивка. У некоторых видов оборудования сложность вполне сравнима с системным ПО хоста. И какие там есть гарантии изоляции, никто не знает (скорее всего, никаких).

        В общем, если честно, я не вижу возможности обеспечить надежность ОС, помещая драйвера в изоляцию - именно потому, что это изолирует только код самих драйверов, но не обеспечивает никакой изоляции со стороны периферийной аппаратуры, которая находится полностью под контролем драйверам и которой можно доверять только при условии, что мы доверяем драйверу.

        Что до возможности восстановить работоспособность системы после падения/зависания драйвера путём его перезапуска, тут я бы тоже поспорил.

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

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

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

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

        Так что с теоретической точки зрения контейнеризация драйверов устройств звучит заманчиво, но с практической точки зрения есть много нюансов.


  1. ukhanov
    25.06.2025 22:56

    А как митигировать Вашу митигацию?