]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ukify: allow multiple initrds 25180/head
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sat, 26 Nov 2022 13:31:57 +0000 (14:31 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 7 Dec 2022 16:22:05 +0000 (17:22 +0100)
If given, multiple initrds are concatenated into a temporary file which then
becomes the .initrd section.

It is also possible to give no initrd. After all, some machines boot without an
initrd, and it should be possible to use the stub without requiring an initrd.
(The stub might not like this, but this is something to fix there.)

man/ukify.xml
src/ukify/test/test_ukify.py
src/ukify/ukify.py

index 3cc13a4cba2ea0b2c0264377322cb086810ef885..17546d543dfe4d8b77c1d1b75278f38ef7633326 100644 (file)
@@ -24,7 +24,7 @@
     <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
index 48ffc6d4954ae368a2eb2e8f30cdcc7f7f493310..34701402e566ae1f6381beb80e70d9dd45c898b8 100755 (executable)
@@ -49,13 +49,13 @@ def test_round_up():
 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',
@@ -77,7 +77,7 @@ def test_parse_args_many():
          '--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')
@@ -103,7 +103,7 @@ def test_parse_sections():
          ])
 
     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'
@@ -334,9 +334,13 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
     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',
@@ -367,7 +371,7 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
     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',
     ],
@@ -377,6 +381,8 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
     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']
index 83423fc720624f6dbcc6e2672b11231199b5401e..e9e5d13d13ef309b7dba7c2b2672f1ceed43f2f0 100755 (executable)
@@ -206,8 +206,9 @@ class Section:
 
     @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)
@@ -404,6 +405,24 @@ def call_systemd_measure(uki, linux, opts):
         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
 
@@ -455,6 +474,7 @@ def make_uki(opts):
         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
@@ -469,7 +489,7 @@ def make_uki(opts):
         ('.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.
@@ -541,7 +561,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
 ''')
 
@@ -553,7 +573,8 @@ usage: ukify [options…] linux initrd
                    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',