Commit Graph
246 Commits
Author SHA1 Message Date
Chris Coulson 2348941b5d measure: Support binding signed policies to individual phases
systemd-measure can produce multiple signed policies for different
phases. However, a policy for a TPM resource that includes these signed
policies can currently be satisfied by any policy that is signed
with the same key.

It can be desirable to bind a resource's policy to one or more
specific phases. One way to do this could be to sign policies for
different phases or phase combinations with different keys. Another
approach is to limit the scope of signed policies using a policy
reference.

Using a policy reference works because:
- The reference is included along with the approved policy digest in the
  digest that is signed.
- The reference argument is included in the authorization policy for a
  resource via the TPM2_PolicyAuthorize assertion.
- During execution of the TPM2_PolicyAuthorize assertion, the TPM checks
  that the session's current policy digest is the approved policy digest,
  computes a digest from the approved policy digest and the supplied policy
  reference, and checks that the resulting digest is the one that was
  verified by TPM2_VerifySignature (via the returned ticket).

This adds a new --policyref argument to systemd-measure which binds all
of the signed policies to the specified policy reference. I did consider
making this more intelligent by auto-generating policy references for each
phase, but this approach provides the most flexibility for now. By making
use of the existing --append argument, a signer can produce multiple
signed policies with the same key that are bound to any individual phases
or combinations of phases.

The policy reference is a string without the NULL terminator. It is
supplied to the TPM via the TPM2B_NONCE type, which has a maximum size
equivalent to the size of the largest digest supported by the TPM. As
the signer doesn't know the capabilities of the target TPM,
systemd-measure limits the size of the policy reference to 32 bytes, to
fit within the size of a SHA256 digest.

This also includes the corresponding changes to systemd-cryptenroll and
systemd-repart (to add an equivalent --tpm2-public-key-policyref argument
so that the policy can be bound to the desired corresponding phase, and to
ensure that the policy reference is included in the LUKS2 token metadata),
and systemd-cryptsetup (to handle the policy reference stored in the LUKS2
token metadata).

This doesn't include policy reference support for credentials yet
because it requires a change to the credential headers.
2026-07-02 21:28:42 +02:00
Paul Meyer 1ef7c15bd8 ukify: show all sections and profiles in inspect JSON output
The JSON output keyed every section by name, so a UKI with repeated
sections only showed the last one: in a multi-profile UKI all but one
.profile/.cmdline/.pcrsig were dropped, as were extra .dtbauto/.efifw.

Report the shared base sections by name at the top level (so .cmdline
etc. stay where they were), each profile as its own by-name object under
a new "_profiles" array, and the alternative-set sections (.dtbauto,
.efifw) as arrays.

Co-developed-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-07-02 13:26:33 +02:00
Wolfgang Bumiller 345acde398 ukify: honor UKI/SBAT from config files
`UkifyConfig.sbat` is an optional list, so the default of
`config_set_if_unset` would only accept a single value.

Further, it is defined with an empty list as a default, causing
configuration values to always be ignored, as the default is not
`None`.

Set its `config_push` to `ConfigItem.config_list_prepend` to collect
the `SBAT` lines from the config.

Signed-off-by: Wolfgang Bumiller <w.bumiller@errno.eu>
2026-06-04 17:13:03 +02:00
Wolfgang Bumiller 5d5f9429fe ukify: deal with non-list values in config_list_prepend
`ConfigItem.config_list_prepend` assumed that the passed value was a
list. This just happens to be the case for the `--initrd` and
`--devicetree-auto` parameters, as those get special treatment in
`ConfigItem.apply_config` as they use space separated lists.

However, this is not generally the case: When trying to set
`UKI/Firmware` the following error occurred:

Traceback (most recent call last):
  File "/usr/bin/ukify", line 2565, in <module>
    main()
    ~~~~^^
  File "/usr/bin/ukify", line 2548, in main
    opts = UkifyConfig.from_namespace(parse_args())
                                      ~~~~~~~~~~^^
  File "/usr/bin/ukify", line 2542, in parse_args
    apply_config(opts)
    ~~~~~~~~~~~~^^^^^^
  File "/usr/bin/ukify", line 2295, in apply_config
    item.apply_config(namespace, section_name, group, key, value)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/ukify", line 1892, in apply_config
    self.config_push(namespace, group, dest, value)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/ukify", line 1765, in config_list_prepend
    setattr(namespace, dest, value + old)
                             ~~~~~~^~~~~
TypeError: unsupported operand type(s) for +: 'PosixPath' and 'list'

Since we can thus get both lists and strings, check the type and
convert non-list values to `[value]`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@errno.eu>
2026-06-04 17:13:03 +02:00
Luca Boccassi 595d3d593c test: skip test-execute and ukify addon test when cross-compiling
These tests run native tools on foreign ELF files, so they fail,
just skip them when cross-compiling
2026-05-29 17:48:52 +01:00
Daan De Meyer b01dcb3a81 test-ukify: Skip kernel images we can't access 2026-05-20 16:04:26 +00:00
Jörg Behrmann c884b1410d ukify: factor out method for value parsing in ConfigItem 2026-05-18 09:41:22 +09:00
Yu Watanabe 58aa8824f3 test_ukify: apply "ruff format"
Also drops unnecessary noqa attribute for importing pefile.
2026-05-18 02:44:46 +09:00
Yu Watanabe 75f7304512 ruff: do not warn about too long line and lambda assignment 2026-05-18 02:32:42 +09:00
Yu Watanabe c54decc406 ruff: use single quotes for multiline strings 2026-05-18 02:32:39 +09:00
Clayton Craft 9149c75953 ukify: fix default path for hwids
The documentation and commit that added this seem to suggest this should
be under /usr/lib/systemd

fixes 117ec9db7e
2026-04-22 23:05:50 +01:00
Luca Boccassi 117ec9db7e ukify: default --hwids to /usr/lib/systemd/boot/hwids/<EFI_ARCH>/
If the option is not manually specified, and the hwids directory exists
for the target EFI architecture, default to it. Allow passing '' to skip.

Fixes https://github.com/systemd/systemd/issues/40542
2026-02-25 13:51:31 +00:00
Nick Rosbrook 75890d949f ukify: omit .osrel section when --os-release= is empty
The primary motivation for this is to allow users of ukify to build
UKI-like objects, without having them later be detected as a UKI by
tools like kernel-install and bootctl.

The common code used by these tools to determine if a PE binary is a UKI
checks that both .osrel and .linux sections are present. Hence, adding
a mechansim to skip .osrel provides a way to avoid being labeled a UKI.
2026-01-06 04:44:43 +09:00
Daan De Meyer 9fc537437b keyutil: Rename public to extract-public 2025-12-18 13:56:38 +01:00
Lukáš Zaoral 1e5498e237 ukify: prefer compression.zstd when available
Python 3.14 introduced support for zstd in the standard library [1].  Therefore,
let's prefer it when available to decrease the number of necessary  external
dependencies.

[1] https://docs.python.org/3/whatsnew/3.14.html#whatsnew314-zstandard
2025-12-09 14:37:22 +09:00
Luca Boccassi 1441f2e992 Add padding to sbat section (#38621) 2025-08-19 17:49:07 +01:00
Zbigniew Jędrzejewski-Szmek ee45041da1 ukify: stop appending NUL to merged .sbat section
Our tools will strip trailing NULs, so this is not causing harm, except for
the corner case when the .sbat section is exactly of the maximum size and now
it wouldn't fit because we're one byte short.

But it also is not needed. Sections have an encoded size of the data and the
reader must use that and must be prepared to handle a text section that does
not end in NUL. Appending of the NUL was added in
c3f7501c4d, without any discussion of this
change. Since we didn't insert the NUL before, the tools must have been
prepared to work without it. I think it's better to keep the code clean and
not do this unnecessary step.
2025-08-19 11:50:28 +02:00
Zbigniew Jędrzejewski-Szmek ec1d031f3d ukify: fix insertion of padding in merged sections
The padding was done to expand the new section contents to the expected size of
the new section. And this then would be used for the content in the existing
section. The new section cannot be larger than the old section, but it can be
smaller. If the new section was smaller, then we'd not write enough padding and
the output file would be corrupted.

This was observed in CI when the .sbat section in the stub was padded to 1k.
The UKI with an .sbat section that was merged and was fairly short would hit
this scenario and be corrupted.
2025-08-19 11:08:35 +02:00
Zbigniew Jędrzejewski-Szmek 2ddbbb22db ukify: adjust comments 2025-08-19 10:46:36 +02:00
Zbigniew Jędrzejewski-Szmek dbd7b16819 ukify: extend error message when section is too small 2025-08-19 10:46:36 +02:00
Li Tian 776991a3f3 ukify: rstrip and escape binary null characters from 'inspect' output (#38607)
SBAT section of UKI may contain \u000 null characters. Rstrip them, and if there's anything left in the middle,
escape them so they are displayed as text.

Fixes #38606
2025-08-18 22:43:41 +01:00
Luca Boccassi 4f9f0e5041 ukify: drop NX bit from UKI if kernel doesn't have it
If the kernel is not NX_COMPAT ready (W^X memory compatible) then the
UKI should not be marked as NX_COMPAT ready either, as the kernel
section is the loadable code in the image.

https://microsoft.github.io/mu/WhatAndWhy/enhancedmemoryprotection/
https://www.kraxel.org/blog/2023/12/uefi-nx-linux-boot/

While the sd-stub EFI code itself is NX ready, it is more useful
to think of it as one unit of execution together with the kernel
it embeds, as that's what it is used for.

Fixes https://github.com/systemd/systemd/issues/38545
2025-08-14 03:49:20 +09:00
Luca Boccassi 3ddb2ee172 ukify: fix hwids section reproducibility
When there are multiple json files the glob list output is
not stable. Sort it before use.

Follow-up for 0333b9d589
2025-07-31 11:47:54 +01:00
DaanDeMeyer 1408505318 meson: Fix missing test dependencies
These test would fail when executed directly with meson test before
doing a build because the required dependencies are not declared, let's
fix that.
2025-07-14 13:07:29 +01:00
Zbigniew Jędrzejewski-Szmek 85830b0d62 ukify: fix version detection for aarch64 zboot kernels with gzip or lzma compression
Fixes https://github.com/systemd/systemd/issues/34780. The number in the header
is the size of the *compressed* data, so for gzip we'd read the initial part of
the decompressed data (equal to the size of the compressed data) and not find
the version string. Later on, Fedora switched to zstd compression, and there we
correctly use the number as the size of the compressed data, so we stopped
hitting the issue, but we should still fix it for older kernels.

I verified that the fix works for gzip-compressed kernels. I also made the same
change for the code for lzma compression. I'm pretty sure it is the right thing,
even though I don't have such a kernel at hand to test.

>>> ukify.Uname.scrape('/lib/modules/6.12.0-0.rc2.24.fc42.aarch64/vmlinuz')
Real-Mode Kernel Header magic not found
+ readelf --notes /lib/modules/6.12.0-0.rc2.24.fc42.aarch64/vmlinuz
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
Found uname version: 6.12.0-0.rc2.24.fc42.aarch64
2025-07-10 13:37:07 +09:00
Luca Boccassi 6cc01c8cc4 ukify: fix parsing uname version with '+'
Debian started using '+' in the kernel uname version, which fails the
regex in ukify. Fix it.
2025-07-06 10:49:22 +01:00
Luca Boccassi 60bda55f5b ukify: when decompressing kernel before signing, call verify on decompressed file
Otherwise it will fail as it's an archive, not a PE file:

Invalid DOS header magic
Can't open image /boot/vmlinuz.old
/boot/vmlinuz.old is compressed and cannot be loaded by UEFI, decompressing
+ sbverify --list /boot/vmlinuz.old
=========================== short test summary info ============================
FAILED ../src/ukify/test/test_ukify.py::test_efi_signing_sbsign[3650] - subprocess.CalledProcessError: Command '['sbverify', '--list', PosixPath('/boot/vmlinuz.old')]' returned non-zero exit status 1.
FAILED ../src/ukify/test/test_ukify.py::test_efi_signing_sbsign[None] - subprocess.CalledProcessError: Command '['sbverify', '--list', PosixPath('/boot/vmlinuz.old')]' returned non-zero exit status 1.
FAILED ../src/ukify/test/test_ukify.py::test_inspect - subprocess.CalledProcessError: Command '['sbverify', '--list', PosixPath('/boot/vmlinuz.old')]' returned non-zero exit status 1.

Follow-up for 0dd03215f1
2025-07-04 12:39:43 +09:00
Luca Boccassi 7eb1cd2f0c Ukify tweaks (#36389) 2025-03-05 11:26:47 +00:00
Zbigniew Jędrzejewski-Szmek 6446c7d99b ukify: avoid treating invalid option as an argument
For some reason, argparse treats undefined options as positional args in
certain scenarios:

$ src/ukify/ukify.py --badopt='11'
ukify.py: error: unrecognized arguments: --badopt=11
$ src/ukify/ukify.py --badopt '11'
ukify.py: error: unrecognized arguments: --badopt
$ src/ukify/ukify.py --badopt '11 12'
Assuming obsolete command line syntax with no verb. Please use 'build'.
Traceback (most recent call last):
  File "/home/zbyszek/src/systemd/src/ukify/ukify.py", line 2497, in <module>
    main()
    ~~~~^^
  File "/home/zbyszek/src/systemd/src/ukify/ukify.py", line 2485, in main
    check_inputs(opts)
    ~~~~~~~~~~~~^^^^^^
  File "/home/zbyszek/src/systemd/src/ukify/ukify.py", line 671, in check_inputs
    value.open().close()
    ~~~~~~~~~~^^
  File "/usr/lib64/python3.13/pathlib/_local.py", line 537, in open
    return io.open(self, mode, buffering, encoding, errors, newline)
           ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '--badopt=11 12'

I suspect that this is some crap compat for Windows, where option parsing is
an even bigger mess than here.

Being told about positional args, when no positional args were specified is
confusing, so add a check for this.
2025-03-05 11:17:33 +01:00
Zbigniew Jędrzejewski-Szmek bd208a5421 ukify: drop dots from exception messages
In Python, exception messages are often embedded in surrounding text, so in
general they should not contain punctuation.

Also, no need to instantiate the exception object if no arguments are used.
2025-03-04 10:07:38 +01:00
Luca Boccassi cf4deeaf1e ukify: print all remaining log-like output to stderr
We want to be able to capture stdout for json and such, so convert
all remaining logging to stderr.
2025-02-17 22:20:32 +01:00
Daan De Meyer 7db71cd79f ukify: Fix --sign-profile= check for main profile
opts.profile is always an env file so we have to parse the default
value as well to check if it's in --sign-profile= or not.
2025-02-16 18:52:26 +01:00
Zbigniew Jędrzejewski-Szmek 40ff0dec08 test/ukify: deduplicate path specification 2025-02-14 17:27:12 +01:00
Luca Boccassi cdedc90caf ukify: do not insist on a stub being available when joining pcrsigs
It is not used in this case, so skip it, otherwise it will need to be
installed even if it is not used

Follow-up for 9876e88e23
2025-02-14 10:57:03 +00:00
Luca Boccassi 0dd03215f1 ukify: if the specified kernel is not a valid PE file try to decompress it
On some distros on some architectures (e.g.: Ubuntu arm64) the kernel is shipped as
a gzipped file, which the UEFI firmware does not understand.
If pefile fails to parse it, try to decompress it.
2025-02-14 10:57:03 +00:00
Luca Boccassi a6d51ae582 ukify: fix zboot parsing with zstd
The header starts with 'zstd', not 'zstd22':

$ ukify build --linux vmlinuz-6.13+unreleased-cloud-arm64 --initrd /boot/initrd.img-6.12.12-amd64 --output uki
Kernel version not specified, starting autodetection 😖.
Real-Mode Kernel Header magic not found
+ readelf --notes vmlinuz-6.13+unreleased-cloud-arm64
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
Traceback (most recent call last):
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 2510, in <module>
    main()
    ~~~~^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 2499, in main
    make_uki(opts)
    ~~~~~~~~^^^^^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 1328, in make_uki
    opts.uname = Uname.scrape(linux, opts=opts)
                 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 384, in scrape
    version = func(filename, opts=opts)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 374, in scrape_generic
    text = maybe_decompress(filename)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 221, in maybe_decompress
    return get_zboot_kernel(f)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 201, in get_zboot_kernel
    raise NotImplementedError(f'unknown compressed type: {comp_type!r}')
NotImplementedError: unknown compressed type: b'zstd\x00\x00'
2025-02-14 10:28:41 +00:00
Luca Boccassi fbc6fecf1a ukify: switch from zstd to zstandard
The zstd library does not support stream decompression, and it
requires the zstd header to contain extra metadata, that the kernel
build does not append:

$ file -k vmlinuz-6.13+unreleased-cloud-arm64
vmlinuz-6.13+unreleased-cloud-arm64: PE32+ executable (EFI application) Aarch64 (stripped to external PDB), for MS Windows, 2 sections\012- data
$ ukify build --linux vmlinuz-6.13+unreleased-cloud-arm64 --initrd /boot/initrd.img-6.12.12-amd64 --output uki
Kernel version not specified, starting autodetection 😖.
Real-Mode Kernel Header magic not found
+ readelf --notes vmlinuz-6.13+unreleased-cloud-arm64
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
Traceback (most recent call last):
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 2508, in <module>
    main()
    ~~~~^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 2497, in main
    make_uki(opts)
    ~~~~~~~~^^^^^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 1326, in make_uki
    opts.uname = Uname.scrape(linux, opts=opts)
                 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 382, in scrape
    version = func(filename, opts=opts)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 372, in scrape_generic
    text = maybe_decompress(filename)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 219, in maybe_decompress
    return get_zboot_kernel(f)
  File "/home/bluca/git/systemd/src/ukify/ukify.py", line 199, in get_zboot_kernel
    return cast(bytes, zstd.uncompress(f.read(size)))
                       ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
zstd.Error: Input data invalid or missing content size in frame header.

This appears to be by design:

https://github.com/sergey-dryabzhinsky/python-zstd/issues/53

Switch to python3-zstandard, which works.
2025-02-14 10:28:41 +00:00
Luca Boccassi 2ac8fcf656 ukify: add --pcr-certificate= parameter
Public keys and certificates are not the same, as the latter embeds more
information that the former, and other tools like sd-measure have distinct
parameters for each of them.
Add a new --pcr-certificate= parameter to ukify, and use it to pass certs
down to sd-measure, as an alternative to --pcr-public-key=. Do not allow
specifying both.
2025-02-09 22:24:00 +00:00
Luca Boccassi 32caed550f ukify: do not fail if pefile complains about hardcoded 256MB limit
pefile has an hardcoded limit to 256MB per section:

https://github.com/erocarrera/pefile/issues/396

When building an initrd with large firmware files and
lots of kernel modules, this limit can be reached.
Skip over those warnings.
2025-02-07 13:58:51 +00:00
Luca Boccassi 9876e88e23 ukify: add --pcrsig and --join-pcrsig arguments to append offline signature
Add a build parameter to take an existing UKI and attach a .pcrsig section
to it. This allows one to create a UKI with a .pcrpkey section with
--policy-digest to get the json output from sd-measure, sign the digest
offline, and attach the .pcrsig section with the signature later.
2025-02-07 13:58:51 +00:00
Daan De Meyer 38801c9129 ukify/measure: Revert changes to use SizeOfImage from Linux PE binary
With 19812661f1, we make sure at runtime
in the stub itself that SizeOfImage from the Linux EFISTUB PE binary is
taken into account, so there's no need to take this into account in ukify
itself. By reverting the ukify change, we again ensure that Misc_VirtualSize
reflects the actual size of the Linux EFISTUB PE binary in the .linux section
which lots of tooling depends on. It also makes sure we don't measure a bunch
of extra zeroes in the stub which should fix systemd-pcrlock measurements as
well.

This effectively reverts 2188c759f9 and
0005411352.

Fixes #35851
2025-02-04 14:11:38 +01:00
Zbigniew Jędrzejewski-Szmek 2e326aad7f ukify: Calculate section size more correctly (#36215)
We should only use Misc_VirtualSize if it's smaller than SizeOfRawData,
since in that case it'll be the non-aligned section size. Otherwise we
have to use SizeOfRawData to get the size on disk.
2025-02-03 10:17:37 +01:00
Ani Sinha 83bf58f39d uki: introduce support for a .efifw section
UKIs can be used to bundle uefi firmwares that can be measured and
used on a confidential computing environment. There can be more than one
firmware blob bundle, each one for a specific platform. Also firmware images
can themselves be containers like IGVM files that can in turn bundle the
actual firmware blob. This change is specifically for uefi firmwares, not
IGVM container files.

This change adds support to introduce a .efifw section in UKI that can be
used for firmware blobs/images. There can be multiple such sections and each
section can contain a single firmware image.

The matching .hwids entry for a specific platform can be used to select the
most appropriate firmware blob.

ukify tool has been also changed to support addition of a firmware image
in UKI.

Since firmware gets measured automatically, we do not need to measure it
separately as a part of the UKI.
2025-01-31 10:05:00 +01:00
Daan De Meyer 33b25fa11c ukify: Calculate section size more correctly
We should only use Misc_VirtualSize if it's smaller than SizeOfRawData,
since in that case it'll be the non-aligned section size. Otherwise we
have to use SizeOfRawData to get the size on disk.
2025-01-29 14:44:27 +01:00
Daan De Meyer 2d6ae4319c ukify: Fix two typing issues 2025-01-29 14:43:06 +01:00
Luca Boccassi c4c3eb03fd Minor fixes for ukify tests (#36197) 2025-01-28 18:49:42 +00:00
Luca Boccassi 25a253084a test: support slow test-ukify on Debian/Ubuntu
Kernels are installed in /boot/ so find them there too
2025-01-28 17:28:55 +00:00
Luca Boccassi bcca98c632 test: use local stub if available in test-ukify
It might not be available on a CI system in the system
path, so use the local one if it was built
2025-01-28 17:28:55 +00:00
Daan De Meyer b61efe6591 ukify: Add --sign-profile
Let's allow configuring which UKI profiles we generate signed PCR
measurements for since there are various types of profiles for
which we do not want to generate signed PCR measurements so that they
can not unlock the encrypted rootfs.

Fixes #36173.
2025-01-28 17:20:46 +00:00
Luca Boccassi d1429288ae ukify: add riscv32 to --efi-arch
It is supported in the arch mapping at the top and in elf2efi, so add it here too
2025-01-25 01:25:38 +00:00