CNXSoft: Это гостевой пост Эрика Вириха, старшего инженера в RISCstar Solutions , демонстрирующий практическую реализацию безопасности для встроенных устройств с использованием стандартных инструментов Linux, таких как dm-verity и TPM 2.0. Рассматриваются модели угроз, безопасность файловых систем и TPM-шифрование с рабочими примерами кода.
В настоящее время невозможно (и справедливо) вывести встроенное устройство на рынок без комплексных мер безопасности. Большинство новых устройств хранят конфиденциальные данные, утечку которых в тёмные уголки интернета недопустима. Также важно предотвратить превращение устройства в часть ботнета.
Linux предоставляет обширный набор инструментов для обеспечения безопасности. Исторически не хватало простого, готового способа интегрировать эти инструменты в конфигурацию, безопасную по умолчанию. В этой статье показано, как современные инструменты упрощают развёртывание, обеспечивая при этом высокий уровень защиты.
Область безопасности встроенных устройств
Говоря о безопасности встроенных систем, важно определить, от чего именно мы защищаемся. Невозможно создать универсальную модель угроз для всех устройств, но можно выделить типичные требования.
Обычно в системе присутствуют два типа данных:
- Статичные бинарные файлы и библиотеки, формирующие ОС
- Изменяемые конфигурации и пользовательские данные, обновляемые в ходе работы
Для ОС необходимо обеспечить целостность заводского образа. Это защищает от атак на цепочку поставок и загрузки несанкционированного ПО. Однако в рамках этого поста бинарные файлы ОС не считаются конфиденциальными.
Динамические конфигурации и пользовательские данные считаются конфиденциальными. Они генерируются только во время работы устройства, не прошиваются на заводе и не заменяются при обновлениях. Эти данные необходимо защитить от считывания на украденных устройствах.
Существует больше вариантов и сценариев, актуальных для конкретных устройств. Но указанного достаточно для построения основы!
Конфигурация файловой системы
Звучит как план? Перейдём к реализации…
Linux позволяет гибко настраивать файловые системы. Для типичного встроенного устройства обычно требуется возможность записи в некоторые каталоги /etc
, /var
и /home
, тогда как остальная часть может оставаться неизменяемым образом.
Корневую файловую систему (/
) можно сделать неизменяемой, а записываемые разделы смонтировать отдельно. Однако управление множеством исключений сложно. Вместо этого есть более простой вариант: сделать /
доступной для записи и смонтировать раздел /usr
только для чтения. /usr
содержит все системные файлы. Они неизменяемы, а остальные файлы могут быть автоматически созданы при первой загрузке. Это позволяет исключить раздел /
из заводского образа и поставлять только /usr
.
Сначала такой подход может показаться неочевидным. Но он значительно упрощает управление записываемым разделом. Основное преимущество — работа всего с двумя разделами без символьных ссылок между ними. Также чётче разделяются требования: один раздел требует проверки целостности, другой — шифрования.
Управление ключами становится проще (и безопаснее), если создавать их на устройстве при первой загрузке. Внедрение уникального для устройства шифрования при развёртывании с единого заводского образа упрощается созданием зашифрованных разделов на устройстве. В противном случае потребуется сложное перешифрование разделов. Ещё один плюс — упрощение механизма сброса к заводским настройкам: достаточно удалить зашифрованные разделы и ключи, затем пересоздать их заново.
Внимательный читатель спросит, как система переживёт первую загрузку без файловой системы /
. Linux нуждается в ней для перехода в пользовательское пространство. Решение — initrd: минимальная файловая система. Не волнуйтесь, её настройка проще, чем кажется.
Защита ОС встроенного устройства
Работа с неизменяемой файловой системой упрощает проверку целостности. Весь раздел можно защитить, поместив его в dm-verity . Это создаёт хеш-дерево над блоками файловой системы, сводящееся к единому корневому хешу для проверки целостности всего дерева и файловой системы.
Теперь нужно подписать корневой хеш, подтверждая его происхождение из доверенного процесса сборки. Здесь обычно начинается усложнение. Один вариант — внедрить хеш в командную строку ядра, добавив usrhash=<корневой_хеш>
, который будет подписан вместе с другими артефактами загрузки. Это кажется простым, но усложняет сборку: теперь раздел /usr
нужно собрать до внедрения usrhash
в командную строку ядра — или, скорее, в Unified Kernel Image (UKI) . Это создаёт зависимость раздела /boot
от сборки /usr
или требует патчинга собранного образа для добавления опции usrhash=
.
Альтернатива — хранение метаданных с хешем и подписью в другом месте. Существуют различные методы, но недавно группа сопровождающих systemd и дистрибутивов Linux стандартизировала этот процесс. uapi-group теперь предоставляет стандарт для безопасного автоматического обнаружения корневого хеша и его подписи. Лучше всего: нам не нужно настраивать dm-verity вручную. systemd-repart
поддерживает это и автоматизирует процесс. Systemd также включает systemd-gpt-auto-generator
для автоматического обнаружения и монтирования.
Побочная тема: systemd-repart
systemd-repart
в своей основе выполняет именно то, что предполагает название: позволяет переразбивать диск на основе простых конфигурационных файлов.
Приведённая ниже конфигурация:
# 00-esp.conf
[Partition]
Type=esp
# 20-root.conf
[Partition]
Type=root
Weight=1000
# 30-home.conf
[Partition]
Type=home
Format=btrfs
SizeMinBytes=1G
Weight=2000
Он сравнит существующие разделы на загруженном диске с конфигурацией и создаст недостающие. формат конфигурации прост , но мощный.
Но это не самая сильная сторона systemd-repart
. Он также может создавать образы дисков с нуля!
Пример:
# 00-esp.conf
[Partition]
Type=esp
Format=vfat
CopyFiles=/boot:/
CopyFiles=/efi:/
SizeMinBytes=512M
SizeMaxBytes=512M
# mkosi.repart/10-usr.conf
[Partition]
Type=usr
CopyFiles=/usr:/
Verity=data
VerityMatchKey=usr
Minimize=guess
# mkosi.repart/20-usr-verity.conf
[Partition]
Type=usr-verity
Verity=hash
VerityMatchKey=usr
Minimize=best
# mkosi.repart/30-usr-verity-sig.conf
[Partition]
Type=usr-verity-sig
Verity=signature
VerityMatchKey=usr
Minimize=best
С этой конфигурацией мы можем вызвать:
systemd-repart \
--root=”/path/to/our/prepared/root/filesystem” \
--empty=create --size=auto --offline=yes \
--private-key=<db.key> \
--certificate=<db.crt> \
output.img
Он обработает всю настройку dm-verity, подписание и назначит известные UUID разделов GPT, которые systemd-gpt-auto-generator
может обнаружить для автоматического монтирования.

Таким образом, мы запишем /usr
в заводской образ, а затем используем systemd-repart
на устройстве для заполнения оставшихся частей.
Защита перезаписываемых данных в безопасности встроенных устройств
Разобравшись со статической частью, рассмотрим динамический раздел /
. Необходима защита от считывания данных на украденных устройствах, что требует применения шифрования.
Запрос пин-кода у пользователя – простое решение, но непрактичное для headless-устройств. Вместо этого требуется безопасный механизм выдачи ключевых материалов только при нахождении системы в чётко определённом состоянии. Различные производители чипов давно предоставляют такие механизмы, но их безопасная реализация сложна и часто требует подписания NDA для доступа к руководствам.
К счастью, существует стандартное решение: Trusted Platform Modules (TPM). Это скорее API, чем конкретное аппаратное решение. TPM могут реализовываться как прошивка в изолированных средах основного процессора. На встроенных устройствах с полным контролем над прошивкой, TPM в виде прошивки обеспечивают стандартное решение без аппаратного TPM.
Детали стандарта TPM 2.0 выходят за рамки этой статьи. Ключевое – механизм привязки ключей к конкретным состояниям системы, делающий их доступными только в этих состояниях. TPM и UEFI совместно используют регистры для измерения состояний прошивки. uapi-group определяет список регистров и их распределение , релевантных для типичных Linux-систем.
В рамках статьи рассмотрим регистр «PCR #7», накапливающий состояния, влияющие на безопасную загрузку. Он позволяет привязываться к ключам и подписям, подтверждающим компоненты загрузки, раскрывая ключ только после успешной проверки цепочки. Это защищает ключи шифрования, если TPM недоступен для чтения после безопасной загрузки. Для TPM в виде прошивки ключи безопасны при надёжном механизме безопасной загрузки.
Как настроить это? Просто: используйте systemd-repart
!
[Partition]
Type=root
Encrypt=tpm2
systemd-repart
автоматически создаст зашифрованный раздел, зарегистрирует его в TPM и создаст внутри файловую систему. Файловая система получит правильный UUID раздела для автоматического обнаружения initrd на основе systemd.
Собираем всё вместе
Объединим компоненты. Для сборки заводского образа используем mkosi , который хорошо интегрируется с инструментами systemd и упрощает создание.
Простая конфигурация для построения пользовательского образа на основе пакетов Fedora:
[Distribution]
Distribution=fedora
Architecture=arm64
[Content]
Packages=systemd-boot,kernel-core
InitrdPackages=systemd-repart
Bootable=yes
KernelCommandLine=
# Only allow verity+signed /usr and encrypted auto-mounts
systemd.image_policy=usr=verity+signed:root=encrypted
# disable emergency shells
rd.systemd.mask=emergency.service systemd.mask=emergency.service
rd.systemd.mask=rescue.service systemd.mask=rescue.service
# INSECURE: Enable password less root for testing :)
RootPassword=hashed:
Autologin=yes
[Validation]
SecureBoot=yes
Verity=yes
VerityKey=mkosi.key
VerityCertificate=mkosi.crt
[Runtime]
RuntimeSize=2G
Конфигурацию разделов поместим в mkosi.repart/
(определит разметку заводского образа). Подойдёт вариант из предыдущего раздела.
Конфигурацию разделов для времени выполнения поместим в mkosi.extra
(будет добавлена в образ):
==> mkosi.extra/usr/lib/repart.d/00-esp.conf <==
[Partition]
Type=esp
==> mkosi.extra/usr/lib/repart.d/10-usr.conf <==
[Partition]
Type=usr
==> mkosi.extra/usr/lib/repart.d/20-usr-verity.conf <==
[Partition]
Type=usr-verity
==> mkosi.extra/usr/lib/repart.d/30-usr-verity.conf <==
[Partition]
Type=usr-verity-sig
==> mkosi.extra/usr/lib/repart.d/40-root.conf <==
[Partition]
Type=root
Encrypt=tpm2
==> mkosi.extra/usr/lib/repart.d/00-esp.conf <==
[Partition]
Type=esp
==> mkosi.extra/usr/lib/repart.d/10-usr.conf <==
[Partition]
Type=usr
==> mkosi.extra/usr/lib/repart.d/20-usr-verity.conf <==
[Partition]
Type=usr-verity
==> mkosi.extra/usr/lib/repart.d/30-usr-verity.conf <==
[Partition]
Type=usr-verity-sig
==> mkosi.extra/usr/lib/repart.d/40-root.conf <==
[Partition]
Type=root
Encrypt=tpm2
Сборка и загрузка системы:
mkosi build && mkosi vm
Пример конфигурации доступен в репозитории: https://github.com/riscstar/blogpost-secure-boot-userspace
Итоги

Прошивка загружает Unified Kernel Image, который запускает initrd. При первой загрузке initrd создаёт rootfs через вызов systemd-repart.Статья демонстрирует, что безопасность встроенных Linux-систем может быть простой. Современные готовые компоненты обеспечивают уровень защиты выше, чем 90% существующих решений.
Безопасность встроенных устройств всегда требует специфичного анализа. Эта публикация даёт основу для создания зашифрованной корневой ФС с низкой сложностью реализации. Однако важно отметить ограничения: не рассмотрены продвинутые методы сужения раскрытия ключей до initrd и защита от отката, предотвращающая загрузку уязвимых подписанных ядер для доступа к конфиденциальным данным.
В демонстрации использован простой образ mkosi
. Те же механизмы применимы к образам, собранным через Yocto. Обеспечивайте безопасность ваших систем!
Выражаем свою благодарность источнику, с которого взята и переведена статья, сайту cnx-software.com.
Оригинал статьи вы можете прочитать здесь.