Forum: PC Hard- und Software Unified Kernel Image mit Debian-Kernel?


von Bauform B. (bauformb)


Lesenswert?

Mahlzeit!

UEFI bootet von einem USB-Stick vorzugsweise EFI/boot/bootx64.efi. Wenn 
man Debians Standardkernel dort hin kopiert, funktioniert das, weil die 
mit CONFIG_EFI_STUB=y gebaut werden. Dabei fehlen allerdings die initrd 
und die cmdline. Beides kann man zusammen mit dem Kernel in ein Unified 
Kernel Image (UKI) packen. Dazu gibt es eine Anleitung

https://wiki.debian.org/EFIStub#Setting_up_a_Unified_Kernel_Image

Die Methode mit objcopy baut allerdings auch linuxx64.efi.stub ein. Das 
ist gut und richtig für einen selbstgebauten Kernel, aber 
doppeltgemoppelt, wenn ich den originalen Debian Kernel benutzen will. 
Die Methode mit systemd-ukify macht das wahrscheinlich irgendwie 
richtig, aber ich würde es bitte gerne verstehen. Danke!

Warum das attraktiv ist: Mit so einem UKI als bootx64.efi muss man genau 
1 Datei an die richtige Stelle kopieren und die Kiste bootet - komplett 
ohne Bootloader, ohne efibootmgr, ohne EFI-Variablen und ohne 
Installation. Es sollte mit USB-Sticks und Festplatten genau gleich 
funktionieren und es ist angeblich die zuverlässigste Methode mit 
zweifelhaften UEFI-Versionen.

was mir auch nicht geholfen hat:

https://uapi-group.org/specifications/specs/unified_kernel_image/

https://wiki.gentoo.org/wiki/Unified_Kernel_Image

https://wiki.archlinux.org/title/Unified_kernel_image

https://www.reddit.com/r/archlinux/comments/1bwyz0i/uki_unified_kernel_image_and_directly_booting_it/

https://github.com/benthetechguy/debian-uki-hooks

https://www.freedesktop.org/software/systemd/man/latest/ukify.html

von Daniel A. (daniel-a)


Lesenswert?

Bei Firmen wie RedHat, Canonical und Microsoft muss man immer etwas 
aufpassen, wenn die was pushen. Eine Ramdisk / Initramfs konnte man 
schon immer in den linux Kernel mit einkompilieren. Wenn es kein Secure 
Boot gäbe, wäre das tatsächlich eine Vereinfachung, aber das ist nicht, 
worum es denen geht.

Canonical und RedHat können ihren Kernel einfach mit dem MS key 
signieren, der in jedem amd64 PC hinterlegt wird. Aber du kannst das 
nicht. Du kannst einen Kernel mit eigenem Key selbst signieren, aber den 
musst du dann bei deinem System manuell hinterlegen. Bei manchen UEFI 
Firmware ist das (fast?) ein Ding der Unmöglichkeit.

Das ganze ist also, zusammen mit den Lockdown LSM, und der Zukunftsmusik 
der Systemd Verity Geschichten (abriegeln des gesamten System (bzw. 
/usr) damit DU das nicht mehr ändern kannst [1]), ein Schritt dahin, dir 
die Kontrolle über dein System wegzunehmen. Das ganze soll wie ein 
Android oder iOS abgeriegelt werden.

Die Agenda ist in etwa:
 * Lockdown LSM (fertig)
 * Unified kernel (in progress)
 * Verity (todo)
 * Bootloader nicht mehr anbieten / Signieren (braucht ja keiner mehr)
 * Secure Boot deaktivieren / Custom key erfassen, nicht mehr 
ermöglichen. (Optional, Nice to have)
 * Profit (Jetzt kontrollieren wir dein System Muhahahaha!)

PS: Das pushen der unified kernels, so wie der Rest, ist auch auf 
Lennart Poetterings Mist gewachsen. Er arbeitet für Microsoft.

1) https://0pointer.net/blog/fitting-everything-together.html (Abschnitt 
"Hermetic usr")

: Bearbeitet durch User
von Bauform B. (bauformb)


Lesenswert?

Bauform B. schrieb:
> Die Methode mit objcopy baut allerdings auch linuxx64.efi.stub ein. Das
> ist gut und richtig für einen selbstgebauten Kernel, aber
> doppeltgemoppelt, wenn ich den originalen Debian Kernel benutzen will.

Ja und nein: bootx64.efi enthält zwar 2 EFI-Stubs, aber den 
Kernel-eigenen kann man in diesem Fall nicht nutzen. Damit die Datei wie 
eine EFI application im PE32+ Format aussieht, muss ein EFI-Stub ganz am 
Anfang stehen. Der Kernel muss aber ganz am Ende stehen, weil der 
komprimiert ist und an Ort und Stelle entpackt wird. Stände der Kernel 
am Anfang, würden cmdline und initrd überschrieben werden.

Unklar ist weiterhin, was linuxx64.efi.stub eigentlich macht. Das sind 
ca. 64KB, früher hat diese Funktionalität in 446 Byte gepasst! Und was 
passiert in der section namens ".sdmagic"? Und warum finde ich keine 
Alternative?

Na gut, egal, Hauptsache das bootet. Man braucht nur dieses 
linuxx64.efi.stub, eine Textdatei mit der kernel cmdline in einer Zeile 
und ein Script:
1
# make-uki
2
# funktioniert auch als User (nach einem chmod 644 /boot/initrd*)
3
# root muss nur noch den USB-Stick einrichten und /tmp/bootx64.efi kopieren
4
5
VMLINUZ="/vmlinuz"
6
INITRD="/initrd.img"
7
CMDLINE="cmdline"
8
STUB="linuxx64.efi.stub"
9
10
align="$(objdump -p $VMLINUZ | grep SectionAlignment | awk '{print "0x"$2}')"
11
echo algin vmlinuz $align
12
align="$(objdump -p $STUB    | grep SectionAlignment | awk '{print "0x"$2}')"
13
echo align stub $align
14
align=$((0x1000))
15
echo align $align
16
17
osrel_offs="$(objdump -h $STUB | grep .text | awk '{print "0x"$3" + 0x"$4}')"
18
osrel_offs=$(($osrel_offs))
19
osrel_offs=$((osrel_offs + $align - osrel_offs % $align))
20
echo osrel_offs stub $osrel_offs
21
osrel_offs=$((0x1000200))
22
echo osrel_offs $osrel_offs
23
24
cmdline_offs=$((osrel_offs + $(stat -Lc%s "/usr/lib/os-release")))
25
cmdline_offs=$((cmdline_offs + $align - cmdline_offs % $align))
26
27
initrd_offs=$((cmdline_offs + $(stat -Lc%s "$CMDLINE")))
28
initrd_offs=$((initrd_offs + $align - initrd_offs % $align))
29
30
linux_offs=$((initrd_offs + $(stat -Lc%s "$INITRD")))
31
linux_offs=$((linux_offs + $align - linux_offs % $align))
32
33
objcopy \
34
   --add-section        .osrel="/usr/lib/os-release" \
35
   --change-section-vma .osrel=$(printf 0x%x $osrel_offs) \
36
   --add-section        .cmdline="$CMDLINE" \
37
   --change-section-vma .cmdline=$(printf 0x%x $cmdline_offs) \
38
   --add-section        .initrd="$INITRD" \
39
   --change-section-vma .initrd=$(printf 0x%x $initrd_offs) \
40
   --add-section        .linux="$VMLINUZ" \
41
   --change-section-vma .linux=$(printf 0x%x $linux_offs) \
42
   "$STUB" "/tmp/bootx64.efi"
Eigentlich sollten $align und $osrel_offs berechnet werden, aber so 
funktioniert es nicht. Mit den festen Werten geht's wenigstens in 
bookworm/amd64.

von Bauform B. (bauformb)


Lesenswert?

Also, so geht es auch nicht, war ja klar. linuxx64.efi.stub schreibt 
beim Booten irgendwelche EFI-Variablen zwecks Kommunikation mit systemd. 
Das mag man mögen oder nicht.

Eine echte Alternative ist efibootguard; wahlweise gibt es einen Stub 
oder einen ziemlichen coolen Bootloader. Der hat etwas andere 
Prioritäten: kein Schnickschnack, der Rechner muss auf jeden Fall 
booten. Punkt. Auch nach einem missglückten Update.

Dazu startet er den Hardware-Watchdog und benutzt 2 zusätzliche 
Partitionen nur für die Bootloader-Config und natürlich eine zweite 
System-Partition. Es funktioniert aber auch mit nur einer System- und 
der EFI-Partition.

efibootguard ist relativ neu und sieht ziemlich gepflegt aus:

https://bugs.debian.org/cgi-bin/pkgreport.cgi?archive=both;src=efibootguard

https://packages.debian.org/trixie/efibootguard

https://github.com/siemens/efibootguard/blob/master/README.md

Disclaimer: Nein, ich bekomme kein Geld von Siemens.

von Daniel A. (daniel-a)


Lesenswert?

Erstaunlich, wie über verkompliziert das ganze da wird.

Beim Kompilieren konnte man ja schon immer die initramfs direkt in den 
Kernel packen: 
https://stackoverflow.com/questions/65868294/if-i-build-linux-kernel-from-source-does-it-contain-initramfs-inside-by-default#answer-65878026

Warum man jetzt, wenn man das nachträglich machen will, zusätzliche 
Loader / efi-stubs und was weiss ich was zusammenwursten muss, 
erschliesst sich mir nicht wirklich.

von Bauform B. (bauformb)


Lesenswert?

Daniel A. schrieb:
> Beim Kompilieren konnte man ja schon immer die initramfs direkt in den
> Kernel packen:

...und cmdline und Splash Image und vor allem efi-stub. So ein Kernel 
versteht sogar root=PARTUUID=$uuid, würde also auch ganz ohne initrd 
funktionieren. Ja, wenn man alles selber macht, ist alles ganz einfach 
;)

> Warum man jetzt, wenn man das nachträglich machen will, zusätzliche
> Loader / efi-stubs und was weiss ich was zusammenwursten muss,
> erschliesst sich mir nicht wirklich.

Zusätzlich braucht man garnichts, insbesondere gerade keinen 
zusätzlichen Loader. Der Unterschied ist nur, ob man per make den Linker 
alle Einzelteile zusammenbauen lässt oder ob man es von Hand mit objcopy 
macht. Ein einzelner make Befehl sieht natürlich einfacher aus, innen 
drin passiert praktisch das gleiche.

Ob man lieber so eine unified Datei benutzt oder vmlinuz und initrd per 
Bootloader lädt, ist doch Geschmackssache. Im Paket efibootguard gibt's 
die Zutaten für beide Varianten. Wer mehrere Kernel zur Auswahl mag, den 
betrifft das alles nicht, der nimmt sowieso grub.

Eigentlich hatte ich nur eine Alternative zu grub gesucht. Für einen 
bootfähigen USB-Stick ist mir grub viel zu kompliziert. Mit so einem UKI 
müsste ich nur genau eine Datei kopieren. Mit dem Loader aus 
efibootguard muss ich 3 Dateien kopieren und die config erzeugen. Das 
finde ich dann doch einfacher als ein UKI zu bauen, das Ergebnis sieht 
auch vertrauter aus. Und das Fallback System mit Watchdog wäre für eine 
bestimmte Anwendung durchaus interessant.

von Daniel A. (daniel-a)


Lesenswert?

Bauform B. schrieb:
> Zusätzlich braucht man garnichts, insbesondere gerade keinen
> zusätzlichen Loader. Der Unterschied ist nur, ob man per make den Linker
> alle Einzelteile zusammenbauen lässt oder ob man es von Hand mit objcopy
> macht. Ein einzelner make Befehl sieht natürlich einfacher aus, innen
> drin passiert praktisch das gleiche.

Das passt dann aber nicht zu:

Bauform B. schrieb:
> Ja und nein: bootx64.efi enthält zwar 2 EFI-Stubs, aber den
> Kernel-eigenen kann man in diesem Fall nicht nutzen.

Entweder es ist das selbe, und man kann den Kernel-eigenen nehmen, oder 
es ist es nicht, in dem Falls ist der Zusätzliche stub eindeutig ein 
weiterer Loader.

Ob der jetzt in einer separaten Datei ist, oder nicht, spielt keine 
Rolle.

von Bauform B. (bauformb)


Lesenswert?

Daniel A. schrieb:
> Entweder es ist das selbe, und man kann den Kernel-eigenen nehmen,
> oder es ist es nicht, in dem Falls ist der Zusätzliche stub
> eindeutig ein weiterer Loader.

Debian baut einen ein, den kann man für manche Anwendungen nutzen, nur 
für diese nicht. Debian baut auch IPv6 ein, das nutzt auch nicht jeder. 
Auch der Kernel-eigene ist eine extra Datei, bevor der Linker alles 
zusammen baut.

Bauform B. schrieb:
> Damit die Datei wie eine EFI application im PE32+ Format aussieht,
> muss ein EFI-Stub ganz am Anfang stehen. Der Kernel muss aber ganz
> am Ende stehen, weil der komprimiert ist und an Ort und Stelle
> entpackt wird. Stände der Kernel am Anfang, würden cmdline und
> initrd überschrieben werden.

von Daniel A. (daniel-a)


Lesenswert?

Bauform B. schrieb:
> Stände der Kernel am Anfang, würden cmdline und initrd überschrieben werden.

Offenbar geht es, wenn der Kernel selbst Kompiliert wird, und man dabei 
die initramfs direkt mit einbaut. Würde die objcopy Geschichte oben 
wirklich das selbe tun, wie beim selber bauen, kann es keinen 
unterschied machen, und es muss auch so gehen.

Mit anderen worten, eine schöne Begründung von dir, nur ändert es nichts 
an meinem Argument.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.