<cmdsynopsis>
<command>/usr/lib/systemd/ukify</command>
<arg choice="plain"><replaceable>LINUX</replaceable></arg>
- <arg choice="plain"><replaceable>INITRD</replaceable></arg>
+ <arg choice="plain" rep="repeat"><replaceable>INITRD</replaceable></arg>
<arg choice="opt" rep="repeat">OPTIONS</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Options</title>
- <para>Note that the <replaceable>LINUX</replaceable> and <replaceable>INITRD</replaceable> positional
- arguments are mandatory.</para>
+ <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 following options are understood:</para>
<programlisting>/usr/lib/systemd/ukify \
/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
+ early_cpio \
/some/path/initramfs-6.0.9-300.fc37.x86_64.img \
--pcr-private-key=pcr-private-initrd-key.pem \
--pcr-public-key=pcr-public-initrd-key.pem \
</programlisting>
<para>This creates a signed UKI <filename index='false'>./vmlinuz.signed.efi</filename>.
+ The initrd section contains two concatenated parts, <filename index='false'>early_cpio</filename>
+ and <filename index='false'>initramfs-6.0.9-300.fc37.x86_64.img</filename>.
The policy embedded in the <literal>.pcrsig</literal> section will be signed for the initrd (the
<constant>enter-initrd</constant> phase) with the key
<filename index='false'>pcr-private-initrd-key.pem</filename>, and for the main system (phases
def test_parse_args_minimal():
opts = ukify.parse_args('arg1 arg2'.split())
assert opts.linux == pathlib.Path('arg1')
- assert opts.initrd == pathlib.Path('arg2')
+ assert opts.initrd == [pathlib.Path('arg2')]
assert opts.os_release in (pathlib.Path('/etc/os-release'),
pathlib.Path('/usr/lib/os-release'))
def test_parse_args_many():
opts = ukify.parse_args(
- ['/ARG1', '///ARG2',
+ ['/ARG1', '///ARG2', '/ARG3 WITH SPACE',
'--cmdline=a b c',
'--os-release=K1=V1\nK2=V2',
'--devicetree=DDDDTTTT',
'--no-measure',
])
assert opts.linux == pathlib.Path('/ARG1')
- assert opts.initrd == pathlib.Path('/ARG2')
+ assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')]
assert opts.os_release == 'K1=V1\nK2=V2'
assert opts.devicetree == pathlib.Path('DDDDTTTT')
assert opts.splash == pathlib.Path('splash')
])
assert opts.linux == pathlib.Path('/ARG1')
- assert opts.initrd == pathlib.Path('/ARG2')
+ assert opts.initrd == [pathlib.Path('/ARG2')]
assert len(opts.sections) == 2
assert opts.sections[0].name == 'test'
pub2 = unbase64(ourdir / 'example.tpm2-pcr-public2.pem.base64')
priv2 = unbase64(ourdir / 'example.tpm2-pcr-private2.pem.base64')
+ # simulate a microcode file
+ with open(f'{tmpdir}/microcode', 'wb') as microcode:
+ microcode.write(b'1234567890')
+
output = f'{tmpdir}/signed.efi'
opts = ukify.parse_args([
- *kernel_initrd,
+ kernel_initrd[0], microcode.name, kernel_initrd[1],
f'--output={output}',
'--uname=1.2.3',
'--cmdline=ARG1 ARG2 ARG3',
subprocess.check_call([
'objcopy',
*(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in (
- 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')),
+ 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')),
output,
tmpdir / 'dummy',
],
assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n'
assert open(tmpdir / 'out.uname').read() == '1.2.3'
assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
+ assert open(tmpdir / 'out.initrd', 'rb').read(10) == b'1234567890'
+
sig = open(tmpdir / 'out.pcrsig').read()
sig = json.loads(sig)
assert list(sig.keys()) == ['sha1']
@classmethod
def create(cls, name, contents, flags=None, measure=False):
- if isinstance(contents, str):
- tmp = tempfile.NamedTemporaryFile(mode='wt', prefix=f'tmp{name}')
+ if isinstance(contents, str | bytes):
+ mode = 'wt' if isinstance(contents, str) else 'wb'
+ tmp = tempfile.NamedTemporaryFile(mode=mode, prefix=f'tmp{name}')
tmp.write(contents)
tmp.flush()
contents = pathlib.Path(tmp.name)
uki.add_section(Section.create('.pcrsig', combined))
+def join_initrds(initrds):
+ match initrds:
+ case []:
+ return None
+ case [initrd]:
+ return initrd
+ case multiple:
+ seq = []
+ for file in multiple:
+ initrd = file.read_bytes()
+ padding = b'\0' * round_up(len(initrd), 4) # pad to 32 bit alignment
+ seq += [initrd, padding]
+
+ return b''.join(seq)
+
+ assert False
+
+
def make_uki(opts):
# kernel payload signing
opts.uname = Uname.scrape(opts.linux, opts=opts)
uki = UKI(opts.stub)
+ initrd = join_initrds(opts.initrd)
# TODO: derive public key from from opts.pcr_private_keys?
pcrpkey = opts.pcrpkey
('.dtb', opts.devicetree, True ),
('.splash', opts.splash, True ),
('.pcrpkey', pcrpkey, True ),
- ('.initrd', opts.initrd, True ),
+ ('.initrd', initrd, True ),
('.uname', opts.uname, False),
# linux shall be last to leave breathing room for decompression.
description='Build and sign Unified Kernel Images',
allow_abbrev=False,
usage='''\
-usage: ukify [options…] linux initrd
+usage: ukify [options…] linux initrd…
ukify -h | --help
''')
help='vmlinuz file [.linux section]')
p.add_argument('initrd',
type=pathlib.Path,
- help='initrd file [.initrd section]')
+ nargs='*',
+ help='initrd files [.initrd section]')
p.add_argument('--cmdline',
metavar='TEXT|@PATH',