Skip to content

Commit 2950ee2

Browse files
committed
Add UKI feature
Unified kernel image (UKI) is a single executable which can be booted directly from UEFI firmware, or automatically sourced by boot-loaders with little or no configuration. Signed-off-by: Alexey Gladkov <[email protected]>
1 parent 3c709a6 commit 2950ee2

File tree

8 files changed

+371
-2
lines changed

8 files changed

+371
-2
lines changed

features/uki/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Feature: uki
2+
3+
Unified kernel image (UKI) is a single executable which can be booted directly
4+
from UEFI firmware, or automatically sourced by boot-loaders with little or no
5+
configuration.
6+
7+
See https://uapi-group.org/specifications/specs/unified_kernel_image/
8+
9+
## Parameters
10+
11+
- **UKI_FEATURES** -- Variable contains a list of features that will be
12+
added to the UEFI image.
13+
- **UKI_SPLASH_IMAGE** -- Variable contains a filename which will be used as a
14+
splash image when creating an UEFI (Optional). Requires bitmap (.bmp) image
15+
format.
16+
- **UKI_CMDLINE** -- Variable contains a list of kernel cmdline parameters.
17+
If the variable is not specified, the parameters from `/etc/cmdline.d/*.conf`
18+
will be taken. If the directory `/etc/cmdline.d` does not exist, the
19+
parameters from `/proc/cmdline` will be taken.
20+
- **UKI_KERNEL** -- The variable specifies the kernel image filename. If not
21+
specified, the image will be guessed.
22+
- **UKI_SBAT** -- CSV lines encoded the SBAT metadata for the image (Optional).
23+
- **UKI_UEFI_STUB** -- A simple UEFI kernel boot stub. By default,
24+
systemd-stub(7) is used. But an independent [stubby](https://github.com/puzzleos/stubby)
25+
can also be used.
26+
27+
## ToDo
28+
29+
- Multi-Profile UKIs
30+
- PE Addons
31+
- UKI TPM PCR Measurements

features/uki/bin/find-kernel

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash -eu
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
. shell-error
5+
6+
BOOTDIR="${BOOTDIR:-/boot}"
7+
arch="${ARCH:-$(uname -m)}"
8+
kversion="${KERNEL:-$(uname -r)}"
9+
10+
show_usage()
11+
{
12+
cat <<-EOF
13+
Usage: $PROG [options]
14+
15+
The utility shows the kernel image.
16+
17+
Options:
18+
--arch=ARCH Use ARCH as target architecture instead of `uname -m`;
19+
-k, --set-version=VERSION Use VERSION instead of `uname -r`;
20+
-h, --help Show this text and exit.
21+
22+
Report bugs to authors.
23+
24+
EOF
25+
exit
26+
}
27+
28+
TEMP=`getopt -n "$PROG" -o "k:,h" -l "arch:,set-version:,help" -- "$@"` ||
29+
show_usage
30+
eval set -- "$TEMP"
31+
32+
while :; do
33+
case "$1" in
34+
--arch) shift
35+
arch="$1"
36+
;;
37+
-k|--set-version) shift
38+
kversion="$1"
39+
;;
40+
-h|--help)
41+
show_usage
42+
;;
43+
--) shift; break
44+
;;
45+
*) fatal "unrecognized option: $1"
46+
;;
47+
esac
48+
shift
49+
done
50+
51+
efi_type=
52+
53+
if [ -n "$arch" ]; then
54+
case "$arch" in
55+
x86_64) efi_type=x64 ;;
56+
i?86) efi_type=ia32 ;;
57+
aarch64) efi_type=aa64 ;;
58+
*)
59+
fatal "Architecture '$arch' not supported to create a UEFI executable"
60+
;;
61+
esac
62+
fi
63+
64+
for ent in "$BOOTDIR"/loader/entries/*.conf; do
65+
[ -s "$ent" ] ||
66+
continue
67+
68+
version='' architecture='' linux=''
69+
70+
while read -r k v; do
71+
case "$k" in
72+
architecture) architecture="$v" ;;
73+
version) version="$v" ;;
74+
linux) linux="$v" ;;
75+
esac
76+
done < "$ent"
77+
78+
[ -z "$architecture" ] || [ "$architecture" = "$efi_type" ] ||
79+
continue
80+
81+
[ "$version" = "$kverion" ] ||
82+
continue
83+
84+
[ -n "$linux" ] ||
85+
continue
86+
87+
! readlink -ev -- "$linux" 2>/dev/null ||
88+
exit 0
89+
done
90+
91+
readlink -ev -- "$BOOTDIR/vmlinuz-$kversion" 2>/dev/null ||
92+
readlink -ev -- "/lib/modules/$kversion/vmlinuz" 2>/dev/null ||
93+
fatal "kernel not found"

features/uki/bin/pack-uki

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/bin/bash -eu
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
. sh-functions
5+
. shell-error
6+
7+
BOOTDIR="${BOOTDIR:-/boot}"
8+
PROCFS_PATH="${PROCFS_PATH:-/proc}"
9+
arch="${ARCH:-$(uname -m)}"
10+
11+
uefi_stub="${UKI_UEFI_STUB-}"
12+
kernel="${UKI_KERNEL-}"
13+
initrd="$workdir/initrd.img"
14+
outfile="${UKI_EFI_INTERNAL_IMAGE:-$workdir/linux.efi}"
15+
16+
17+
get_offset_value()
18+
{
19+
local idx name size vma lma file_off algn
20+
21+
printf -v "$1" '0'
22+
23+
while read -r idx name size vma lma file_off algn; do
24+
[ -z "$idx" ] || [ -z "${idx##*[!0-9]*}" ] ||
25+
printf -v "$1" '%s' "$(( 16#${size} + 16#${vma} ))"
26+
done < <(objdump -h "$2" 2>/dev/null)
27+
}
28+
29+
filter_objdump()
30+
{
31+
local k a b c d e;
32+
33+
eval "$1=('' '' '' '' '')"
34+
35+
while read -r k a b c d e; do
36+
[ "$k" != "$3" ] ||
37+
eval "$1=(\"\$a\" \"\$b\" \"\$c\" \"\$d\" \"\$e\")"
38+
done < <(objdump -p "$2" 2>/dev/null)
39+
}
40+
41+
pe_get_int_value()
42+
{
43+
local values
44+
filter_objdump values "$2" "$3"
45+
46+
[ -n "${values[0]}" ] &&
47+
printf -v "$1" '%s' "$(( 16#${values[0]} ))" ||
48+
return 1
49+
}
50+
51+
pe_file_format()
52+
{
53+
local magic=0
54+
pe_get_int_value magic "$1" "Magic"
55+
56+
case "$magic" in
57+
267|523) # 010b (PE32) | 020b (PE32+)
58+
return 0
59+
;;
60+
esac
61+
return 1
62+
}
63+
64+
section_align=0
65+
offset=0
66+
update_section_offset()
67+
{
68+
[ "$#" -eq 0 ] ||
69+
offset=$(( $offset + $(stat -Lc%s "$1") ))
70+
offset=$(( $offset + $section_align - $offset % $section_align ))
71+
}
72+
73+
args=()
74+
add_section()
75+
{
76+
local offs
77+
printf -v offs '0x%x' "$offset"
78+
args+=( --add-section "$1=$2" --change-section-vma "$1=$offs" )
79+
update_section_offset "$2"
80+
}
81+
82+
[ -n "$kernel" ] ||
83+
kernel="$("$FEATURESDIR"/uki/bin/find-kernel)"
84+
85+
[ -f "$kernel" ] ||
86+
fatal "Can't find a kernel image to create a UEFI executable"
87+
88+
verbose "Kernel image: $kernel"
89+
90+
if [ -z "$uefi_stub" ]; then
91+
case "$arch" in
92+
x86_64) efi_type=x64 ;;
93+
i?86) efi_type=ia32 ;;
94+
aarch64) efi_type=aa64 ;;
95+
*)
96+
fatal "Architecture '$arch' not supported to create a UEFI executable"
97+
;;
98+
esac
99+
100+
uefi_stub="/usr/lib/systemd/boot/efi/linux${efi_type}.efi.stub"
101+
fi
102+
103+
pe_file_format "$uefi_stub" ||
104+
fatal "wrong file format: $uefi_stub"
105+
106+
verbose "UEFI stub file: $uefi_stub"
107+
108+
get_offset_value offset "$uefi_stub"
109+
110+
[ $offset -gt 0 ] ||
111+
fatal "failed to get the size of $uefi_stub to create UEFI image file"
112+
113+
pe_get_int_value section_align "$uefi_stub" SectionAlignment ||
114+
fatal "failed to get the SectionAlignment of the stub PE header to create the UEFI image file"
115+
116+
image_base=0
117+
pe_get_int_value image_base "$uefi_stub" ImageBase ||
118+
fatal "failed to get the ImageBase of the stub PE header to create the UEFI image file"
119+
120+
printf -v image_base '0x%x' "$image_base"
121+
122+
args=( --image-base="$image_base" )
123+
124+
update_section_offset
125+
126+
for f in /usr/lib/os-release /etc/os-release; do
127+
if [ -s "$f" ]; then
128+
add_section ".osrel" "$f"
129+
break
130+
fi
131+
done
132+
133+
# shellcheck disable=SC2206
134+
cmdline=( ${UKI_CMDLINE-} )
135+
136+
if [ "${#cmdline[@]}" -gt 0 ]; then
137+
:;
138+
elif [ -d /etc/cmdline.d ]; then
139+
for conf in /etc/cmdline.d/*.conf; do
140+
[ -e "$conf" ] ||
141+
continue
142+
while read -r l; do
143+
# shellcheck disable=SC2206
144+
cmdline+=( $l )
145+
done < "$conf"
146+
done
147+
elif [ -e "$PROCFS_PATH/cmdline" ]; then
148+
while read -r l; do
149+
# shellcheck disable=SC2206
150+
cmdline+=( $l )
151+
done < "$PROCFS_PATH/cmdline"
152+
fi
153+
154+
verbose "UEFI kernel cmdline: ${cmdline[*]}"
155+
156+
uefi_cmdline="$workdir/cmdline.txt"
157+
printf >"$uefi_cmdline" '%s\0' "${cmdline[*]}"
158+
159+
add_section ".cmdline" "$uefi_cmdline"
160+
161+
if [ -n "${UKI_SPLASH_IMAGE-}" ] && [ -s "$UKI_SPLASH_IMAGE" ]; then
162+
add_section ".splash" "$UKI_SPLASH_IMAGE"
163+
verbose "UEFI splash image: $UKI_SPLASH_IMAGE"
164+
fi
165+
166+
uefi_sbat="$workdir/uki.sbat"
167+
printf >"$uefi_sbat" '%s\n' \
168+
"sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md" \
169+
${UKI_SBAT:+"$UKI_SBAT"}
170+
171+
add_section ".sbat" "$uefi_sbat"
172+
add_section ".linux" "$kernel"
173+
add_section ".initrd" "$initrd"
174+
175+
uefi_stub_file="$workdir/stub.efi"
176+
177+
cp $verbose -f -- "$uefi_stub" "$uefi_stub_file"
178+
objcopy --remove-section ".sbat" "$uefi_stub_file" >/dev/null 2>&1
179+
objcopy $verbose "${args[@]}" "$uefi_stub_file" "$outfile"
180+
181+
rm -f -- "$uefi_cmdline" "$uefi_sbat" "$uefi_stub_file" "$initrd"

features/uki/config.mk

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
UKI_FEATURES = \
4+
modules-blockdev \
5+
modules-crypto-user-api \
6+
modules-filesystem \
7+
modules-multiple-devices \
8+
modules-network \
9+
modules-nfs
10+
11+
UKI_PROGS = objdump objcopy
12+
13+
UKI_UEFI_STUB ?=
14+
UKI_SBAT ?=
15+
UKI_KERNEL ?=
16+
UKI_CMDLINE ?=
17+
UKI_SPLASH_IMAGE ?=
18+
19+
UKI_EFIDIR ?= $(firstword $(wildcard /efi/EFI $(BOOTDIR)/efi/EFI $(BOOTDIR)))
20+
UKI_IMAGEFILE ?= $(UKI_EFIDIR)/linux-$(KERNEL)$(IMAGE_SUFFIX).efi
21+
22+
FEATURES += $(if $(UKI),$(UKI_FEATURES))

features/uki/rules.mk

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
PHONY += pack-uki install
3+
4+
PUT_FEATURE_PROGS += $(UKI_PROGS)
5+
6+
UKI_EFI_INTERNAL_IMAGE := $(WORKDIR)/linux.efi
7+
8+
pack-uki: pack $(call if-active-feature,ucode compress bootconfig)
9+
@$(VMSG) "Packing UKI image ..."
10+
@$(FEATURESDIR)/uki/bin/pack-uki
11+
12+
install: pack-uki
13+
@$(VMSG) "Installing UKI image ..."
14+
@if [ -f "$(TEMPDIR)/images" ] && grep -Fxqs "$(UKI_IMAGEFILE)" "$(TEMPDIR)/images"; then \
15+
echo ""; \
16+
echo "An attempt to create two images with the same name. There is possibility"; \
17+
echo "that you forgot to define IMAGE_SUFFIX or UKI_IMAGEFILE in one of the config files."; \
18+
echo ""; \
19+
echo "ERROR: Unable to overwrite the image $(UKI_IMAGEFILE)"; \
20+
echo ""; \
21+
exit 1; \
22+
fi >&2
23+
@$(TOOLSDIR)/show-install-info "$(UKI_EFI_INTERNAL_IMAGE)"
24+
@chmod 600 -- "$(UKI_EFI_INTERNAL_IMAGE)"
25+
@mv -f $(verbose) -- "$(UKI_EFI_INTERNAL_IMAGE)" "$(UKI_IMAGEFILE)"
26+
@echo "$(UKI_IMAGEFILE)" >> "$(TEMPDIR)/images"
27+
28+
IMAGEFILE = $(UKI_IMAGEFILE)
29+
genimage:

mk/functions.mk.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ endef
7474

7575
define get-all-features
7676
$(strip $(if $(filter get-all-features,$(0)),\
77-
$(sort $(filter-out ,$(call expand-features,FEATURE-REQUIRES,$(FEATURES))))))
77+
$(sort $(filter-out ,$(call expand-features,FEATURE-REQUIRES,$(FEATURES) $(if $(UKI),uki))))))
7878
endef
7979

8080
define get-all-disable-features

mk/genimage.mk.in

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
# The previous call to 'guess' has already done this.
66
IGNORE_DEPMOD := 1
77

8+
# Replace 'install' target by UKI feature because regular initramfs must be
9+
# converted to create a UEFI image.
10+
INSTALL_TARGET_SUFFIX := $(if $(UKI),-uki-disabled)
11+
812
PHONY += create pack install genimage
13+
PHONY += install$(INSTALL_TARGET_SUFFIX)
914

1015
create: depmod-host
1116
@$(VMSG) "Creating initrd image ..."
@@ -18,7 +23,7 @@ pack: create
1823
@$(VMSG) "Packing image to archive ..."
1924
@$(TOOLSDIR)/pack-image
2025

21-
install: pack
26+
install$(INSTALL_TARGET_SUFFIX): pack
2227
@$(VMSG) "Installing image ..."
2328
@if [ -f "$(TEMPDIR)/images" ] && grep -Fxqs "$(IMAGEFILE)" "$(TEMPDIR)/images"; then \
2429
echo ""; \

0 commit comments

Comments
 (0)