Изображение взято из статьи «Linux Kernel EFI Boot Stub или «Сам себе загрузчик»»
Несколько месяцев назад я написал для скрипта
mkinitcpio
код, который позволяет ему создавать файлы UEFI с использованием заглушки systemd
.Само внесенное мной изменение можно найти на GitHub.
Далее я коротко продемонстрирую, чем эта возможность хороша, как она упрощает запуск системы, и как с ее помощью можно повысить безопасность, используя, например, Secure Boot.
Процесс загрузки
В последнее десятилетие большинство компьютеров имеют два варианта загрузки. Режим Legacy BIOS и пришедший ему на замену UEFI. Возможности последнего весьма обширны, но один из наиболее интересных аспектов в том, что технически ядро Linux представляет собой исполняемый файл MS-DOS, и если считать его первые два байта, то мы увидим
MZ
.Дело в том, что при запуске Linux через UEFI по факту мы запускаем двоичный файл Linux с набором команд, создавая точку входа. А поскольку UEFI сам является загрузчиком, то Linux можно запускать из него напрямую в виде загрузочной записи.
Однако большинство из нас не хочет связываться непосредственно с UEFI, поэтому ради простоты мы используем загрузчики вроде
grub
или system-boot
.Защита цепочки загрузки
При использовании загрузчика мы обычно предоставляем ему файл конфигурации,
initramfs
, и библиотеку ядра. Файл initramfs
включает в себя базовый набор компонентов дистрибутива Linux, отвечающих за разблокирование зашифрованных разделов, монтирование файловой системы и других разделов с последующим запуском системыinit
. Все эти три файла лежат зашифрованные в загрузочном разделе*. С помощью безопасной загрузки можно подписать ядро, поскольку оно является исполняемым файлом UEFI, но это оставит полностью незащищенными конфигурацию и
initramfs
. *Да, некоторые люди шифруют свои загрузочные разделы.
Решением будет вложить все эти составляющие в один исполняемый файл. Реализовать это доступным и вполне понятным способом позволяют образы EFI ядра.
Заглушки UEFI
В большинстве дистрибутивов исполняемый файл заглушки предоставляется подсистемой
systemd
. Если у вас нет отдельного пакета systemd
, то он может являться частью пакета gummiboot
.Принцип такой: мы вставляем нужные данные в разделы двоичного файла, который затем подхватывается файлом заглушки.
#!/bin/bash
objcopy \
--add-section .osrel="/etc/os-release" --change-section-vma .osrel=0x20000 \
--add-section .cmdline="/etc/kernel/cmdline" --change-section-vma .cmdline=0x30000 \
--add-section .splash="/usr/share/systemd/bootctl/splash-arch.bmp" --change-section-vma .splash=0x40000 \
--add-section .linux="/boot/vmlinuz-linux" --change-section-vma .linux=0x2000000 \
--add-section .initrd=<(cat /boot/intel-ucode.img /boot/initrd-linux.img) --change-section-vma .initrd=0x3000000 \
"/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "/efi/EFI/Linux/linux.efi"
Приведенный пример с Arch Linux создает исполняемый файл, содержащий информацию дистрибутива (
os-release
), считанные из файла параметры ядра, bmp-файл с логотипом дистрибутива, ядро и initramfs
с микрокодом. Подписав этот файл, мы в дальнейшем сможем аутентифицировать большинство других файлов, используемых в процессе загрузки. После этого его можно будет выполнять из оболочки UEFI без дополнительных аргументов, а также использовать загрузчиком*.
*Думаю, стоит упомянуть, что некоторые загрузчики не проверяют подписи выполняемых ими файлов. Это делает
systemd-boot
, но не делает grub
. Имейте в виду.Прием этот очень прост, но из соображений безопасности большинство инструментов реализуют его по-разному. В данном случае ощутимо помогает возможность единой генерации этих файлов.
MKINITCPIO
mkinitcpio
– это генератор initramfs
, разработанный и используемый в основном для Arch Linux, поэтому некоторые части текущего раздела будут ориентированы на этот конкретный дистрибутив. Тем не менее аналогичные возможности создания и работы с initramfs
есть, к примеру, в dracut
, где для этого используется - -uefi
. Если ваша программа для созданияinitramfs
такой функциональности не имеет, то ее добавление в проект не должно составить особого труда. При желании проработать последующий пример можете взять его предвыпускную версию из репозитория проекта. Любые полезные изменения кода и документация приветствуются.
github.com/archlinux/mkinitcpio/releases/tag/v31_rc0
Начнем с изменения файла
linux.preset
, который в Arch указывает на конфигурацию ядра.--- /etc/mkinitcpio.d/linux.preset
+++ /etc/mkinitcpio.d/linux.preset
@@ -2,13 +2,16 @@
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
+ALL_microcode=(/boot/*-ucode.img)
PRESETS=('default' 'fallback')
#default_config="/etc/mkinitcpio.conf"
default_image="/boot/initramfs-linux.img"
-#default_options=""
+default_efi_image="/efi/EFI/Linux/archlinux-linux.efi"
+default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
#fallback_config="/etc/mkinitcpio.conf"
fallback_image="/boot/initramfs-linux-fallback.img"
-fallback_options="-S autodetect"
+fallback_efi_image="/efi/EFI/Linux/archlinux-linux-fallback.efi"
+fallback_options="-S autodetect --splash /usr/share/systemd/bootctl/splash-arch.bmp"
Здесь определяется расположение микрокода и имя для исполняемого файла. Кроме того, мы передаем опцию
- -splash
, устанавливая изображение для загрузочного экрана. Обратите внимание, что в качестве пути сохранения необходимо указать расположение, куда смонтирован текущий раздел EFI.Далее исправляем параметры загрузки ядра. По умолчанию
mkinitcpio
считывает из /etc/kernel/cmdline
. Если же вы не уверены, откуда происходит считывание в вашей системе, то можете проверить /proc/cmdline
и использовать этот путь в качестве отправной точки. Но имейте в виду, что записи initrd
, указывающие на микрокод и initramfs
, нужно удалить. # cat /etc/kernel/cmdline
rw quiet bgrt_disable
Содержимое файла должно быть похожим на приведенное выше. Также учтите, что все флаги
root
или cyptdevices
должны остаться, если вы выполняете initramfs
без systemd
, обеспечивающего обнаружение разделов.Мы также добавляем в командную строку ядра команду
bgrt disable
. Этот флаг отключает отображение логотипа OEM после загрузки таблиц ACPI. В результате загрузочная заставка вместо замены неким невзрачным логотипом будет отображаться на несколько секунд дольше. При выполнении
mkinitcpio -P
вывод должен получиться примерно таким:[..snip..]
==> Starting build: 5.13.10-arch1-1
-> Running build hook: [base]
-> Running build hook: [systemd]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [block]
-> Running build hook: [keyboard]
-> Running build hook: [sd-encrypt]
-> Running build hook: [filesystems]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
==> Creating UEFI executable: /efi/EFI/Linux/archlinux-linux.efi
-> Using UEFI stub: /usr/lib/systemd/boot/efi/linuxx64.efi.stub
-> Using kernel image: /lib/modules/5.13.10-arch1-1/vmlinuz
-> Using os-release file /etc/os-release
==> UEFI executable generation successful
Вот и все! Мы сгенерировали с помощью
mkinitcpio
заглушку UEFI!Если вы используете
systemd-boot
, то больше ничего настраивать не нужно. Загрузчик для отображения в меню заглушек UEFI будет искать их в каталоге EFI/Linux. Это намного упрощает настройку загрузчика, так как для его создания нужно лишь выполнить
bootctl install
и сгенерировать исполняемый файл.Совет
Если вы хотите работать со старыми ядрами, то эта возможность также все упростит. Извлеките при создании образа пакетную версию ядра
linux
. В случае использования systemd-boot
ее можно будет задействовать для загрузки без дополнительных настроек.default_efi_image="/efi/EFI/Linux/linux-$(pacman -Q linux | awk '{print $2}').efi"
fshp
Использовать параметр EFI_STUB религия не позволяет?
Запустите make menuconfig, там много интересного. И initrd можно встроить, и блобы, и опции, и версию и на выходе сразу получить готовый efi файл.
Am1GO
EFISTUB подходит только для тех, кто сам себе ядра собирает, а systemd'овый EFI stub позволяет встроить произвольный бинарник в EFI-обёртку и поэтому отлично подходит всем, включая тех, кто использует готовые бинарные сборки.
Помимо прочего, systemd'овый EFI stub умеет взаимодействовать с TPM и в тех случаях, когда загрузка бинарника, полученного на основе этого stub осуществляется не напрямую, а через загрузчик, это даёт дополнительные преимущества: например, если реализовано автоматическое расшифровывание рутового раздела и при этом используется загрузчик, позволяющий передать произвольный cmdline, то его изменение приведёт к тому, что содержимое целевого PCR-регистра изменится и автоматической расшифровки не произойдёт. Всего этого, в случае упаковки ядра с предсобранным init'ом с помощью ядерного EFISTUB, разумеется, реализовать не удастся.
Дополнительное преимущество - это время, требуемое на изменение конфигурации: если мне необходимо добавить или убрать поддержку устройств, инициализируемых при загрузке до переключения на рутовый раздел, то в случае внешнего init'а и systemd'ового EFI stub на это потребуется порядка 1 минуты на пересборку initramfs и ещё 5 секунд на сборку нового подписанного EFI stub, в случае же с init'ом, вкомпиленным в ядро, которое собирается в виде EFISTUB, на это потребуется существенно больше времени.
Всё это не имеет ни малейшего отношения к религии и позволяет экономить собственное время для тех, кто его ценит.