Безопасность встраиваемых систем Linux ч.1
Концепции безопасности
Безопасность — это снижение рисков. В РФ существуют стандарты ГОСТ Р ИСО/МЭК по информационной безопасности призванные обеспечить защиту компьютерных автоматизированных систем. Но в большинстве российских стандартов в этой области, нет детальной информации как строить систему защиты встраиваемых систем. Более детализированные руководства по безопасности выпускают ведомства на коммерческой основе. Поэтому воспользуемся руководящими документами Агентства Европейского союза по кибербезопасности (European Union Agency for Cybersecurity, ENISA). Далее, описанная концепция безопасности, основана на руководстве по организации защиты встраиваемых систем — Hardware Threat Landscape and Good Practice Guide (распространяется бесплатно).
Руководство вводит следующие понятия: Владельцы (owners) — собственники использующие продукт или услугу, такие как конечный пользователь, производитель, владелец бизнеса и т.д. В свою очередь владельцы нацелены на защиту своих активов (assets). Активом является какая-либо ценность в продукте или услуге (данные, программный код, репутация, и т.д.). Антагонистом в этой схеме выступает злоумышленник (threat actors), человек или абстрактный объект (хакер, конкурент, правительство и т.д.), который создает угрозу (threat) нацеленную на актив таким образом, чтобы это могло привести к ущербу. Для реализации угрозы, злоумышленник будет исследовать уязвимости (vulnerabilities), слабые места в системе с помощью вектора атаки (attack vector), метода или пути, используемого злоумышленником для доступа или проникновения в целевую систему.
На диаграмме ниже очень представлены данные концепции:
![](https://habrastorage.org/webt/2v/nd/oc/2vndocw6mz9ypx9rhz6f172jmqc.png)
Термины и отношения, связанные с угрозами, в соответствии с ENISA Threat Landscape 2013
В итоге это походит на игру щит и меч, между владельцем и злоумышленником. Насколько далеко зайдет владелец, чтобы защитить активы? Как далеко зайдет злоумышленник, чтобы создать угрозу? Все это зависит от стоимости активов, которая может быть выражена в виде эквивалента денежных средств, репутации, безопасной обстановке, и т.д. Восприятие ценности может быть неодинаковым для владельцев и злоумышленника.
Определение активов (и их стоимости) для снижения рисков компрометации выполняется в ходе моделирования угроз (threat modeling).
Моделирование угроз
При моделирование угроз выявляются потенциальные угрозы и расставляются по приоритету. По сути, это процесс оценки рисков, при котором происходит оценка стоимости активов и затраты на их защиту. Результатом моделирования угроз является модель угроз (threat model) вашего продукта:
![](https://habrastorage.org/webt/od/m6/ep/odm6epkjk5fyds4sui_ivv2oeem.png)
Моделирование угроз
Существует различные методы и методологии, призванные помочь при моделировании угроз, включая STRIDE, DREAD, VAST, OCTAVE и многие другие.
Для общего понимания рассмотрим STRIDE и DREAD.
Модель STRIDE хорошо помогает классифицировать угрозы, была разработана в Microsoft. Название STRIDE является аббревиатурой от шести основных типов угроз:
- Spoofing: спуфинг;
- Tampering: фальсификация;
- Repudiation: отказ;
- Information disclosure: раскрытие информации;
- Denial of service and Escalation of privileges: отказ в обслуживании и повышение привилегий.
![](https://habrastorage.org/webt/qp/ya/ze/qpyazecf4l_tixhq7jg4rgokimw.png)
Методология STRIDE
STRIDE можно использовать для выявления всех угроз, нацеленных на активы владельца:
![](/file/01d2c311babba97b79be6.png)
Методология DREAD призвана оценивать риски угроз компьютерной безопасности. Название представляет собой аббревиатуру пяти категорий угроз безопасности:
- Damage — ущерб (насколько серьезной будет атака);
- Reproducibility — воспроизводимость (насколько легко воспроизвести атаку);
- Exploitability — возможность использования (какой объем работы необходимо выполнить для реализации атаки);
- Affected users — затронутые пользователи (сколько людей будет затронуто);
- Discoverability — обнаруживаемость (насколько легко обнаружить угрозу).
![](/file/eba0cf2debade30bb54c6.png)
В то время как модель STRIDE помогает идентифицировать угрозы, методология DREAD помогает их ранжировать. Для каждой угрозы необходимо пройтись по категориям и выставить соответствующий бал: низкая вероятность (1 балл), средняя вероятность (2 балла), высокая вероятность (3 балла). По итогу, будет получен ранжированный список угроз и стратегий задач. Пример:
![](/file/e750b082b2f5a95b5870f.png)
Моделирование угроз позволяет сформировать четкое понимание что необходимо защищать, каким образом планируется это защищать, и сколько будет стоить реализация защиты. Эту часть модели угроз продукта, необходимо переоценивать для каждого цикла разработки. В результате модель угроз будет содержать приоритетный список угроз, над которыми необходимо работать, так что можно сосредоточиться на реализации мер защиты для повышения безопасности продукта.
Безопасный запуск системы (Secure Boot)
Как убедиться в том, что программный код, который используется для запуска системы не подложный? Для решения этой задачи необходимо обеспечить безопасный процесс запуска системы.
Целью обеспечения безопасного запуска системы является защита целостности и аутентичности загружаемых файлов. Защита процесса загрузки обычно основана на проверке цифровых подписей. Встраиваемая система Linux обычно состоит из трех основных компонентов: загрузчика, ядра и корневой файловой системы (rootfs). Все эти компоненты подписаны, и подписи проверяются во время загрузки.
Например, для проверки подписи загрузчика (bootloader) можно использовать какой-либо аппаратный механизм, реализованный в виде отдельной микросхемы или устройства. Аппаратный механизм в первую очередь проверяет цифровую подпись ядра, затем происходит загрузка виртуального диска (ramdisk image), который в свою очередь проверяет цифровые подписи корневой файловой системы (root filesystem). Поскольку у нас есть один компонент, проверяющий цифровую подпись следующего в цепочке загрузки, этот процесс часто называют цепочкой доверия (chain-of-trust).
![](https://habrastorage.org/webt/iq/qy/xj/iqqyxjseak_0t3opz1umuqp5jtq.png)
Пример, как реализована цепочкой доверия (chain-of-trust) на устройстве NXP iMX6
Все начинается с запуском исполняемого кода из ПЗУ (ROM) в SoC. В NXP iMX6 есть аппаратный компонент под названием High Assurance Boot (HAB), который проверяет на первом этапе цифровую подпись загрузчика (bootloader), что позволяет реализовать процесс безопасной загрузки устройства. High Assurance Boot на устройствах iMX6 так же можно назвать Root of Trust (корневое доверие), поскольку если компонент будет скомпрометирован, то весь дальнейший процесс безопасной загрузки также будет скомпрометирован.
Компоненту HAB для проверки цифровой подписи загрузчика (bootloader) необходимо сгенерировать пару ключей (открытый и закрытый). Загрузчик подписывается закрытым ключом, а внутри SoC сохраняется открытый ключ.
Внутри чипа iMX6 находится память однократной записи (One-time programmable memory) предназначенная для хранения ключей, т.е. хранится хеш открытого ключа.
Загрузчик при запуске (например, U-Boot) проверяет подпись ядра Linux. Для этого обычно используется формат образа, называемый FIT image. FIT image представляет собой контейнер для бинарных файлов с поддержкой хеширования и цифровой подписи, и обычно содержит образ ядра Linux, файлы дерева устройств и стартовый ramdisk. После создания пары ключей, необходимо подписать бинарные файлы в FIT image с помощью закрытого ключа, и настроить U-Boot для использования открытого ключа для проверки подписи FIT image.
После загрузки ядра Linux запустится программа инициализации из образа ramdisk. ramdisk содержит логику для окончательной проверки целостности корневой файловой системы перед ее монтированием. Существует несколько вариантов реализации данной задачи. Один из наиболее распространенных вариантов — использование модуля ядра device-mapper verity (dm-verity). Модуль ядра dm-verity обеспечивает проверку целостности блочных устройств и требует наличия rootfs только для чтения. Другой вариант заключается в использование IMA или dm-integrity, если необходимо монтировать корневую файловую систему в режиме чтение-запись.
![](https://habrastorage.org/webt/-k/ai/-m/-kai-mwf5m8oj034me7vugmaibm.png)
Схема полного процесса безопасной загрузки.
Это только один пример реализации безопасной загрузки, так же данный подход может быть применен к другим платам и ARM SoC.
При реализации защиты необходимо помнить что 100% безопасности не существует.
17 июля 2017 г. была публично раскрыта уязвимость в ROM используемой для безопасной загрузке в нескольких устройств NXP (семейства i.MX6, i.MX50, i.MX53, i.MX7, i.MX28 и Vybrid). И если ваша цепочка доверия скомпрометирована, то все скомпрометировано! Поэтому, после реализации системы защиты необходимо в течение всего срока эксплуатации устройства, следить за публикациями уязвимостей.
Хотя безопасная загрузка обеспечивает аутентичность и целостность исполняемого кода, она не защищает устройство от считывания исполняемого кода/данных злоумышленником. Поэтому, для защиты своей интеллектуальной собственности или обеспечения конфиденциальность данных, необходимо использовать шифрование.
Исполняемый код и шифрование данных
Для защиты конфиденциальных данных системы и пользователей необходимо обеспечить шифрование на самом устройстве. Данные — это любая информация, созданная во время работы устройства, включая базы данных, файлы конфигурации и так далее.
Не всегда требуется реализовывать шифрования исполняемых файлов, а полное шифрование корневой файловой системы встречается очень редко. Большинство используемых компонентов — это бесплатное программное обеспечение с открытым исходным кодом, поэтому его не имеет смысла скрывать. Существует также проблема в лицензирование GPLv3 и Tivoization (использование любого программного обеспечения GPLv3 вынудит вас предоставить пользователю механизм для обновления программного обеспечения, и это усложнит задачу, если шифруете исполняемый код). Наиболее распространенный подход — шифрование только приложений, которые вы разработали для устройств. Обычно это ваша интеллектуальная собственность.
В Linux доступно два сценария использования шифрования данных: полное шифрование диска (full disk encryption) и пофайловое шифрование (file-based encryption).
Полное шифрование диска работает на уровне шифрование блоков, при этом зашифровывается весь диск или его раздел, для этого необходимо использовать dm-crypt.
Пофайловое шифрование работает на уровне файловой системы, где каждый каталог/файл может быть зашифрован отдельным ключом. Две наиболее распространенные реализации файлового шифрования — это fscrypt и eCryptFS. fscrypt — это API, доступное в файловых системах, таких как: EXT4, UBIFS и F2FS. eCryptFS — более общее решение, реализованное в виде дополнительного слоя, который накладывается поверх существующей файловой системы и не зависит от нее.
Шифрование это хорошо, но как быть с ключами, где и как их хранить?
Ключи шифрования
Поскольку алгоритм с асимметричным ключом слишком медленный при использования для шифрования диска или файлов, то используется алгоритм с симметричным ключом. Это означает, что для шифрования и дешифрования используется один и тот же ключ. Причем этот ключ должен быть расположен где-то в файловой системе, чтобы можно было расшифровать и зашифрованный исполняемый код/данные.
Но оставлять ключ в файловой системе нельзя, верно?
В истории отметились некоторые случаи, когда компании проигнорировали этот момент и получили в дальнейшем проблемы. Например, для первой игровой консоли Xbox, Эндрю «bunnie» Хуанг, исследователь по безопасности, разработал устройство на базе FPGA для прослушивания коммуникационной шины и извлечения ключей шифрования из нее. Ключи были легко получены только потому, что они передавались в виде открытого текста, без какой-либо защиты. По сути Эндрю сделал снифер и сделал дамп всех передаваемых данных. Кстати, Эндрю Хуанг является автором книги по взлому оборудования под названием «Взлом Xbox».
![](https://habrastorage.org/webt/4o/yn/cm/4oyncm-erbsksne5oo7hhsb5ixe.png)
Подключение снифера к материнской плате Xbox
Защита зашифрованного исполняемого кода/данных так же надежна, как и защита ключа для их расшифровки! Итак, одна из проблем, с которыми сталкивается шифрование, — это хранение ключей.
Подходы к хранению ключей
Для настольных компьютеров или смартфонов, использующие шифрование файловой системы, ключ может быть получен из пароля пользователя (парольной фразы).
Во встраиваемых системах обычно нет интерактивного взаимодействия с пользователем для получения ключа из пароля каждый раз, когда происходит загрузка устройства. Таким образом, ключ должен храниться в зашифрованном виде в файловой системе или в безопасном хранилище.
Например, процессоры NXP i.MX содержат уникальный главный ключ (предварительно записанный NXP), доступ к которому может получить только специальный аппаратный модуль — CAAM (Cryptographic Accelerator and Assurance Module). Данный модуль используется для шифрования ключа и сохранения его в файловой системе. Во время загрузки устройства, модуль CAMM используется для расшифровки ключа и восстановления простого ключа, который будет использоваться для расшифровки файловой системы. Поскольку ключ внутри модуля CAAM недоступен, зашифрованный ключ защищен.
Если в процессор не интегрированы подобный функции безопасности, то можно воспользоваться внешними аппаратными модулями такими как Secure Element и TPM.
Secure Element — это безопасная компьютерная система, в состав которой входит безопасное хранилище с собственными защищенными приложениями (обычно реализуется с помощью Java Card). Что предоставляет возможность реализации различных алгоритмов шифрования, но в большинстве случаев интегрируют Стандарт шифрования с открытым ключом 11 (PKCS # 11). Secure Elements используется в смарт-картах и SIM-картах.
Доверенный платформенный модуль, или TPM (trusted platform module) – это отдельный микрочип на системной плате компьютера, разработанный согласно спецификации ISO/IEC 11889, и выполняет специфический круг задач, связанных с криптографией и защитой компьютера. TPM обеспечивает безопасное хранение данных, поэтому его можно использовать для хранения главного ключа, который в дальнейшем будет использоваться для шифрования/дешифрования ключа шифрования файловой системы. Помимо защищенного хранилища подобные устройства также предоставляют дополнительные функций безопасности, таких как генерация случайных чисел, вычисление хэшей, функции шифрования и подписи и т. Д. Так же TPM может быть реализован программно, но большинство реализации выполнено в виде аппаратного модуля.
Компания STMicroelectronics разработала модуль TPM STPM4RasPI для Raspberry Pi устройств. Данный модуль подключается на 40-контактный разъем и может работать по интерфейсу: I²C или SPI. Краткое руководство — Raspberry Pi extension board with an ST33 Trusted Platform Module.
![](https://habrastorage.org/webt/je/7s/9t/je7s9t867ujhxl4bqmqqrgvdk3s.jpeg)
TPM STPM4RasPI от STMicroelectronics
![](https://habrastorage.org/webt/dw/db/zn/dwdbznfkr9fsvdciwgdzeblw0im.png)
Модуль STPM4RasPI подключен к Raspberry Pi
После подключения модуля по интерфейсу SPI и установке драйверов, модуль STPM4RasPI обнаруживается в системе:
![](https://habrastorage.org/webt/tw/yk/zk/twykzkqz2twwscevy7agvx6q7yu.png)
STPM4RasPI подключен к Raspberry Pi
Третьим вариантом обеспечения безопасного хранилища данных является использование доверенной среды исполнения (Trusted Execution Environment, TEE). TEE — среда для исполнения кода и хранения данных, которая надежно изолирована от основной ОС устройства. Существую различные варианты реализации TEE:
- Использовать TrustZone в процессорах ARM — разделение TEE и основной ОС в рамках одного ядра процессора;
- Запуск TEE на отдельном ядре в рамках SoC и общение с ней через аппаратный интерфейс;
- Запускать TEE на отдельном SoC и установить с ним защищенное соединение через внешний интерфейс, например, SPI или SMbus.
Многие устройства используемые в повседневной жизни используют надежную среду исполнения, включая смартфоны, телевизионные приставки, игровые консоли и Smart TV.
Если вы реализуете в своем встраиваемом устройстве шифрование, то вам придется подумать о хранении ключей и управлении ими с самого начала проекта.
Многоуровневая безопасность
Использование безопасной загрузки и шифрования данных недостаточно для комплексной защиты встраиваемых устройств на Linux. Система защиты должна быть многоуровневая, где на каждом уровне используются свои методы защиты данных. Сочетание нескольких уровней защиты затрудняет взлом устройства злоумышленником. Например, можно защитить исполняемый код и обеспечить шифрование данных, но если выше приложение работает с ошибками, которые могут быть использованы, то у вас будет проблемы с безопасностью. Если на приложение можно осуществить различные векторы атак (пользовательский ввод, файлы конфигурации, сетевые соединения и т. д.), то все предыдущие используемые методы защиты данных не помогут.
Безопасный код
Если приложение подвержено векторам атак (пользовательский ввод, файлы конфигурации, сетевое взаимодействие и т. д.), то эти ошибки могут использовать эксплойты. В частности, программы написанные на небезопасных языках программирования таких как C/C++, применимы такие атаки как: переполнение буфера, разбиение стека и форматирование строк.
Как пример, в ядре Linux (с версии 2.6.34 до 5.2.x) была обнаружена ошибка переполнения буфера, при которой функциональность vhost переводила буферы виртуальной очереди в IOV. Проблема позволяет из гостевой системы создать условия для переполнения буфера в модуле ядра vhost-net (сетевой бэкенд для virtio), выполняемом на стороне хост-окружения. Атака может быть проведена злоумышленником, имеющим привилегированный доступ в гостевой системе, во время выполнения операции миграции виртуальных машин.
![](https://habrastorage.org/webt/mn/ze/7a/mnze7axd9smfuabnbsmqe5suofw.png)
Фрагмент кода с vhost
Уязвимости был присвоен номер CVE-2019-14835 и исправлена в 2019 году. На практике пользователь виртуальной (гостевой) машины мог использовать эту уязвимость для получения root доступа на основной хост-машине. Уязвимость (и многие другие) присутствовала в ядре Linux в течение нескольких лет!
В конце концов, в программном обеспечении всегда будут ошибки, но мы можем попытаться свести их к минимуму. И для этого мы можем использовать инструменты статического анализа.
Инструменты статистического анализа кода
Инструменты статического анализа позволяют анализировать исходный код (без запуска программы), для нахождения проблем до того, как вы столкнетесь с ними во время исполнения. Эти инструменты обнаруживают программные ошибки, такие как утечки памяти, целочисленное переполнение, выход за пределы массива, и многое другое!
Существует множество различных инструментов с открытым исходным кодом (cppcheck, splint, clang и т. д.). И коммерческие варианты, такие как популярный PVS-Studio. Компиляторы обычно содержат встроенный инструмент статического анализа, который во время компиляции выдает предупреждения или ошибки. Вот почему мы никогда не должны игнорировать предупреждения компилятора.
Итак, первый шаг к минимизации риска потенциальных уязвимостей в приложениях — никогда не игнорировать предупреждения компилятора и использовать инструменты статического анализа. Но некоторые проблемы очень сложно, а иногда и невозможно выявить на уровне исходного кода. Вот почему нам может потребоваться добавить защиту приложения по время его выполнения.
Защита приложения во время работы
Защита во время выполнения препятствует динамическому анализу приложения. Целью злоумышленника является адресное пространство в памяти используемой приложением. Например, злоумышленник может из памяти приложения считать ключи и пароли в открытом виде, или изменить некоторые данные. Многие из нас еще в детстве уже были хакерами и изменяли память процесса с помощью программы ArtMoney. Путём внедрения в процесс или в файл игры, ArtMoney обнаруживает ячейки адресов памяти, которые отвечают за хранение пользовательских настроек игры: время (таймер), количество денег, силы, жизней, патронов и т. д. Таким образом, можно было бесплатно себе обеспечить бесконечные патроны, максимум игровой валюты, или бесконечную жизнь.
Для защиты доступа к адресному пространству приложения используют подобные методы и инструменты:
- AddressSanitizer (ASan) — инструмент, созданный исследователями по безопасности от Google, для выявления проблем доступа к памяти в программах C и C ++. Когда исходный код приложения C/C ++ компилируется с включенной опцией AddressSanitizer, программа будет оснащена инструментарием во время выполнения для выявления и сообщения об ошибках доступа к памяти.
- ASLR (рандомизация адресного пространства) — метод компьютерной безопасности, который случайным образом упорядочивает позиции адресного пространства ключевых областей данных процесса (текст, стек, куча, библиотеки и т. Д.). Данный режим включается на уровне ядра Linux с последующей перекомпиляцией.
- Valgrind — еще один очень полезный инструмент, предназначенный обнаружения утечек памяти, а также профилирования.
Использование средств защиты это всегда выбор компромиссов, например использование ASLR снижает общую производительность системы, другие инструменты осложняют отладку и диагностику приложения.
Инструменты для фаззинга
Фаззинг — это техника автоматизированного тестирования, при которой на вход программе подаются специально подготовленные данные, которые могут привести её к аварийному состоянию или неопределённому поведению. Для проведения фаззинга существует множество бесплатных инструментов с открытым исходным кодом, такие как AFL и syzkaller (фаззер ядра Linux). Данные программы многие исследователи по безопасности и злоумышленники, используют для поиска уязвимых мест с точки зрения безопасности в программном обеспечении. В некоторых случаях исследователи разрабатывают свои инструменты для фаззинга, и это позволяет им зарабатывать большие деньги на платформах BugBounty, обнаруживая ошибки в программном обеспечении.
Такие методы и инструменты как фаззинг, статический анализ и защита во время выполнения, помогут значительно уменьшить количество ошибок в программном обеспечении. Но это не значит, что вы можете на этом остановится! В программном обеспечении всегда есть и будут ошибки, поэтому нужен еще один уровень защиты на случай, если программа будет взломана. И это приводит нас к системе разграничения прав доступа.