ukify: allow building PE addon

Make the kernel optional too, so that we can easily build and sign a PE addon,
that can be used to carry extra command line options.
This commit is contained in:
Luca Boccassi 2023-04-18 00:40:43 +01:00 committed by Zbigniew Jędrzejewski-Szmek
parent 1db4acbe5d
commit 00e5933f57
3 changed files with 56 additions and 15 deletions

View file

@ -17,14 +17,14 @@
<refnamediv>
<refname>ukify</refname>
<refpurpose>Combine kernel and initrd into a signed Unified Kernel Image</refpurpose>
<refpurpose>Combine components into a signed Unified Kernel Image for UEFI systems</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>/usr/lib/systemd/ukify</command>
<arg choice="plain"><replaceable>LINUX</replaceable></arg>
<arg choice="plain" rep="repeat"><replaceable>INITRD</replaceable></arg>
<arg choice="opt"><replaceable>LINUX</replaceable></arg>
<arg choice="opt" rep="repeat"><replaceable>INITRD</replaceable></arg>
<arg choice="opt" rep="repeat">OPTIONS</arg>
</cmdsynopsis>
</refsynopsisdiv>
@ -35,8 +35,8 @@
<para>Note: this command is experimental for now. While it is intended to become a regular component of
systemd, it might still change in behaviour and interface.</para>
<para><command>ukify</command> is a tool that combines a kernel and an initrd with
a UEFI boot stub to create a
<para><command>ukify</command> is a tool that combines components (e.g.: a kernel and an initrd with
a UEFI boot stub) to create a
<ulink url="https://uapi-group.org/specifications/specs/unified_kernel_image/">Unified Kernel Image (UKI)</ulink>
— a PE binary that can be executed by the firmware to start the embedded linux kernel.
See <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>
@ -53,6 +53,9 @@
and <option>--section=</option>
below.</para>
<para><command>ukify</command> can also be used to assemble a PE binary that is not executable but
contains auxiliary data, for example additional kernel command line entries.</para>
<para>If PCR signing keys are provided via the <option>--pcr-public-key=</option> and
<option>--pcr-private-key=</option> options, PCR values that will be seen after booting with the given
kernel, initrd, and other sections, will be calculated, signed, and embedded in the UKI.
@ -78,10 +81,9 @@
<refsect1>
<title>Options</title>
<para>Note that the <replaceable>LINUX</replaceable> positional argument is mandatory. The
<replaceable>INITRD</replaceable> positional arguments are optional. If more than one is specified, they
will all be combined into a single PE section. This is useful to for example prepend microcode before the
actual initrd.</para>
<para>The <replaceable>LINUX</replaceable> and <replaceable>INITRD</replaceable> positional arguments are
optional. If more than one <replaceable>INITRD</replaceable> are specified, they will all be combined into
a single PE section. This is useful to for example prepend microcode before the actual initrd.</para>
<para>The following options are understood:</para>
@ -296,6 +298,19 @@
key <filename index='false'>pcr-private-system-key.pem</filename>. The Linux binary and the resulting
combined image will be signed with the SecureBoot key <filename index='false'>sb.key</filename>.</para>
</example>
<example>
<title>Kernel command line auxiliary PE</title>
<programlisting>ukify \
--secureboot-private-key=sb.key \
--secureboot-certificate=sb.cert \
--cmdline='debug' \
--output=debug.cmdline.efi
</programlisting>
<para>This creates a signed PE binary that contains an additional kernel command line parameter.</para>
</example>
</refsect1>
<refsect1>

View file

@ -206,6 +206,27 @@ def test_sections(kernel_initrd, tmpdir):
for sect in 'text osrel cmdline linux initrd uname test'.split():
assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
def test_addon(kernel_initrd, tmpdir):
output = f'{tmpdir}/addon.efi'
opts = ukify.parse_args([
f'--output={output}',
'--cmdline=ARG1 ARG2 ARG3',
'--section=.test:CONTENTZ',
])
try:
ukify.check_inputs(opts)
except OSError as e:
pytest.skip(str(e))
ukify.make_uki(opts)
# let's check that objdump likes the resulting file
dump = subprocess.check_output(['objdump', '-h', output], text=True)
for sect in 'text cmdline test'.split():
assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
def unbase64(filename):
tmp = tempfile.NamedTemporaryFile()

View file

@ -551,7 +551,7 @@ def make_uki(opts):
sbsign_invocation += ['--engine', opts.signing_engine]
sign_kernel = opts.sign_kernel
if sign_kernel is None and opts.sb_key:
if sign_kernel is None and opts.linux is not None and opts.sb_key:
# figure out if we should sign the kernel
sbverify_tool = find_tool('sbverify', opts=opts)
@ -583,7 +583,7 @@ def make_uki(opts):
else:
linux = opts.linux
if opts.uname is None:
if opts.uname is None and opts.linux is not None:
print('Kernel version not specified, starting autodetection 😖.')
opts.uname = Uname.scrape(opts.linux, opts=opts)
@ -624,7 +624,8 @@ def make_uki(opts):
# UKI creation
uki.add_section(Section.create('.linux', linux, measure=True))
if linux is not None:
uki.add_section(Section.create('.linux', linux, measure=True))
if opts.sb_key:
unsigned = tempfile.NamedTemporaryFile(prefix='uki')
@ -657,7 +658,7 @@ def parse_args(args=None):
description='Build and sign Unified Kernel Images',
allow_abbrev=False,
usage='''\
usage: ukify [options] linux initrd
usage: ukify [options] [linux [initrd]]
ukify -h | --help
''')
@ -666,6 +667,7 @@ usage: ukify [options…] linux initrd…
p.add_argument('linux',
type=pathlib.Path,
nargs="?",
help='vmlinuz file [.linux section]')
p.add_argument('initrd',
type=pathlib.Path,
@ -769,7 +771,8 @@ usage: ukify [options…] linux initrd…
opts = p.parse_args(args)
path_is_readable(opts.linux)
if opts.linux is not None:
path_is_readable(opts.linux)
for initrd in opts.initrd or ():
path_is_readable(initrd)
path_is_readable(opts.devicetree)
@ -784,7 +787,7 @@ usage: ukify [options…] linux initrd…
if opts.os_release is not None and opts.os_release.startswith('@'):
opts.os_release = path_is_readable(opts.os_release[1:])
elif opts.os_release is None:
elif opts.os_release is None and opts.linux is not None:
p = pathlib.Path('/etc/os-release')
if not p.exists():
p = path_is_readable('/usr/lib/os-release')
@ -815,6 +818,8 @@ usage: ukify [options…] linux initrd…
raise ValueError('--phases= specifications must match --pcr-private-key=')
if opts.output is None:
if opts.linux is None:
raise ValueError('--output= must be specified when building a PE addon')
suffix = '.efi' if opts.sb_key else '.unsigned.efi'
opts.output = opts.linux.name + suffix