Изображение взято из статьи «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"
Автор: Дмитрий Брайт