objcopy seems to expect that the offset passed to --change-section-vma
is absolute instead of relative to ImageBase. If this is not accounted
for an invalid image is created that cannot be loaded:
0 .osrel 0000016b 0000000200016000 0000000200016000 00000400 2**2
…
6 .text 0000d242 0000000140001000 0000000140001000 00c6e800 2**4
This isn't an issue with gnu-efi based PE images, but natively created
ones will have a non-zero ImageBase.
Instead of consuming N arguments as tools directories, let's always
only consume one argument per specification of --tools. This avoids
issues where the linux image and initrd are interpreted as tools
directories.
llvm-objcopy works on stubs built for foreign architectures whereas
objcopy doesn't so let's prefer using llvm-objcopy instead of objcopy.
llvm-objcopy automatically sets the virtual address and doesn't provide
an option to set it manually so we only add --change-section-vma when
using objcopy
The default section flags differ between llvm-objcopy and objcopy
so we add a default for the section flags so we make sure all sections
are read-only data unless specified otherwise.
It's much nicer for the user if we fail early instead of doing partial
processing if we cannot read some input. We can't do those checks immediately
from argparse.Parser.parse_args(), because we want to fully process the
commandline first. In particular, even with invalid args, if --help is
specified somewhere, we want to handle that. Thus, we need to delay the checks
after argparse.Parser.parse_args() returns.
Ukify didn't have type annotations on functions, but it probably should.
Jörg's suggested correction included them and we might just as well start here.
The autodetection code is supposed to throw ValueError when it
cannot figure out the version so that we fall back to the next method.
With the patch:
Kernel version not specified, starting autodetection 😖.
Real-Mode Kernel Header magic not found
+ readelf --notes vmlinuz/arm64/vmlinuz-6.0.9-300.fc37.aarch64
readelf: vmlinuz/arm64/vmlinuz-6.0.9-300.fc37.aarch64: Error: Not an ELF file - it has the wrong magic bytes at the start
Found uname version: 6.0.9-300.fc37.aarch64
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.)
The approach is based on mkinicpio's autodetection.
This is hacky as hell. Some cases are actually fairly nice: ppc64el images have
a note that contains 'uname -r'. (The note is not uniquely labeled at all, and
only contains the release part instead of the full version-hostname-release
string, and we don't actually care about ppc, and it's very hard to read the
note from Python, but in general that'd be the approach I'd like.)
I opted to simply read and decompress the full linux binary in some cases.
Python doesn't make it easy to do streaming decompression with regexp matching,
and it doesn't seem to matter much: the image decompresses in a fraction of a
second.
Some gymnastics were needed to import ukify as a module. Before the file
was templated, this was trivial: insert the directory in sys.path, call import.
But it's a real pain to import the unsuffixed file after processing. Instead,
the untemplated file is imported, which works well enough for tests and is
very simple.
The tests can be called via pytest:
PATH=build/:$PATH pytest -v src/ukify/test/test_ukify.py
or directly:
PATH=build/:$PATH src/ukify/test/test_ukify.py
or via the meson test machinery output:
meson test -C build test-ukify -v
or without verbose output:
meson test -C build test-ukify
Zekret files are obfuscated using base64.
The option is added because we have a similar one for kernel-install. This
program requires python, and some people might want to skip it because of this.
The tool is installed in /usr/lib/systemd for now, since the interface might
change.
A template file is used, but there is no .in suffix.
The problem is that we'll later want to import the file as a module
for tests, but recent Python versions make it annoyingly hard to import
a module from a file without a .py suffix. imp.load_sources() works, but it
is deprecated and throws warnings.
importlib.machinery.SourceFileLoader().load_module() works, but is also
deprecated. And the documented replacements are a maze of twisted little
callbacks that result in an empty module.
So let's take the easy way out, and skip the suffix which makes it easy
to import the template as a module after adding the directory to sys.path.
Features:
- adds sections .linux, .initrd, .uname, .osrel, .pcrpkey, .pcrsig, .cmdline, .splash
- multiple initrds can be concatenated
- section flags are set properly (READONLY, DATA or CODE)
- uses systemd-measure to precalculate pcr measurements and create a signed json policy
- the inner linux image will be signed automatically with sbsign if unsigned
- uses sbsign to sign the output image
- offsets are calculated so that sections are placed adjacent, with .linux last
- custom sections are possible
- multiple pcr signing keys can be specified and different boot phase paths can be
signed with different keys
- most things can be overriden (path to tools, stub file, signing keys, pcr banks,
boot phase paths, whether to sign things)
- superficial verification of slash bmp is done
- kernel uname "scraping" from the kernel if not specified (in a later patch)
TODO:
- change systemd-measure to not require a functional TPM2. W/o this, we'd need
to support all banks in the build machine, which is hard to guarantee.
- load signing keys from /etc/kernel/
- supress exceptions, so if something external fails, the user will not see a traceback
- conversion to BMP from other formats
$ sudo /usr/lib/systemd/ukify \
--tools=build/ \
--measure \
/lib/modules/6.0.5-300.fc37.x86_64/vmlinuz \
/boot/08a5690a2eed47cf92ac0a5d2e3cf6b0/6.0.5-300.fc37.x86_64/initrd \
--secureboot-private-key=server.key --secureboot-certificate=server.crt \
--pcr-private-key=tpm2-pcr-private.pem --pcr-public-key=tpm2-pcr-public.pem \
--cmdline='rw quiet' \
--section test:TESTTESTTEST \
--section test2:TESTTESTTEST2 \
--pcr-banks=sha1 \
--uname="$(uname -rv)"
Host arch 'x86_64', efi arch 'x64'
+ sbverify --list /lib/modules/6.0.5-300.fc37.x86_64/vmlinuz
+ build/systemd-measure calculate --linux=/lib/modules/6.0.5-300.fc37.x86_64/vmlinuz --osrel=/etc/os-release --cmdline=/tmp/tmpcmdline_5aufjir --pcrpkey=tpm2-pcr-public.pem --initrd=/boot/08a5690a2eed47cf92ac0a5d2e3cf6b0/6.0.5-300.fc37.x86_64/initrd --bank=sha1
11:sha1=03df5e5243bc002b959d52359fe04e266d0b5ebf
11:sha1=54949b82bae32e80343ff0f01eeeeb75f4c07d3f
11:sha1=0fc62be88aa9c5ad7282aa8adb504f451bcec9df
11:sha1=b71155e7fcd467f7c1696f675e37887032e2eafa
+ build/systemd-measure sign --linux=/lib/modules/6.0.5-300.fc37.x86_64/vmlinuz --osrel=/etc/os-release --cmdline=/tmp/tmpcmdline_5aufjir --pcrpkey=tpm2-pcr-public.pem --initrd=/boot/08a5690a2eed47cf92ac0a5d2e3cf6b0/6.0.5-300.fc37.x86_64/initrd --bank=sha1 --private-key=tpm2-pcr-private.pem --public-key=tpm2-pcr-public.pem
+ objcopy /usr/lib/systemd/boot/efi/linuxx64.efi.stub --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x22000 --add-section .cmdline=/tmp/tmpcmdline_5aufjir --change-section-vma .cmdline=0x23000 --add-section .pcrpkey=tpm2-pcr-public.pem --change-section-vma .pcrpkey=0x24000 --add-section .initrd=/boot/08a5690a2eed47cf92ac0a5d2e3cf6b0/6.0.5-300.fc37.x86_64/initrd --change-section-vma .initrd=0x25000 --add-section .uname=/tmp/tmpuname0v3uzh5r --change-section-vma .uname=0x4009000 --add-section .test=/tmp/tmptestuxve59c8 --change-section-vma .test=0x400a000 --add-section .test2=/tmp/tmptest2_i143p9i --change-section-vma .test2=0x400b000 --add-section .pcrsig=/tmp/tmppcrsigdtcqxz_w --change-section-vma .pcrsig=0x400c000 --add-section .linux=/lib/modules/6.0.5-300.fc37.x86_64/vmlinuz --change-section-vma .linux=0x400d000 /tmp/uki4vsbf7y8
+ sbsign --key server.key --cert server.crt /tmp/uki4vsbf7y8 --output vmlinuz.efi
warning: data remaining[79849520 vs 79866644]: gaps between PE/COFF sections?
warning: data remaining[79849520 vs 79866648]: gaps between PE/COFF sections?
Signing Unsigned original image
Wrote signed vmlinuz.efi