Skip to content

Remove kernel cmdline hardcoding and enable PCR calculations for UKI - #745

Open
piyush-jena wants to merge 4 commits into
bottlerocket-os:developfrom
piyush-jena:uki-pcrsys-support
Open

piyush-jena wants to merge 4 commits into
bottlerocket-os:developfrom
piyush-jena:uki-pcrsys-support

Conversation

@piyush-jena

@piyush-jena piyush-jena commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Description of changes:

  • Add bootconfig library which provides support functions for the following
    • parse bootconfig.data and construct kernel command line for UKI images
    • decode .cmdline section in the PE image of UKI
    • parse grub.cfg to read grub config
    • encode UKI LoadOptions for PCR 9 calculation
    • merge_parameters to merge the contributions from the drop-ins.
  • Update pcrsys
    • enable prediction for SHA384 PCR bank
    • predict PCRs for UKI images
  • Add sub-commands in ukisys
    • merge-bootconfig
    • cmdline

rpm2img: the build path
rpm2img sets up BOOTCONFIG_DIR, installs RPMs into ROOT_MOUNT/BOOT_MOUNT, builds the root filesystem, then calls generate_verity_root from imghelper.

Next comes the block the middle commit moved /boot/boot-config.d drop-ins shipped by packages are validated (single-line key = value only), concatenated, sorted, passed through ukisys merge-bootconfig. This calls bootconfig::merge_parameters which collapses duplicate keys into one sorted quoted value.

x.a=k1
x.a=k2

is collapsed to

x.a="k1,k2"

This is compiled to binary bootconfig.data by the kernel's bootconfig -a. In case of UKI images, ukisys cmdline reads that binary data and kernel and init args and writes a file; ukify build --cmdline "@${file}". This data is embedded into the UKI in .cmdline section.

img2img: the repack path
img2img decompresses an existing image, re-extracts the partitions, optionally replaces root contents, and then mirrors rpm2img's ordering: it reads EFI-A's PARTUUID calls the same generate_verity_root to get a fresh DM_VERITY_ROOT. It never regenerates bootconfig. For GRUB images Section 6 just seds the new verity table into grub.cfg and re-signs it. For UKI images it calls repack_uki: ukify inspect extracts .linux, .cmdline, .osrel, .uname, .sbat; ukisys derive-stubstrips the Authenticode signature and the trailing payload sections to recover a stub ukify build will accept; the old.cmdline` is string-surgeried so only the dm-mod.create="..." value changes (the literal quotes are matched deliberately, since the table contains spaces); the UKI is rebuilt, re-signed with sign_uki, verified section-by-section.

pcrsys:
pcrsys disk --image "${OS_IMAGE}" --efi-vars ... --platform ...emitspcr-predictions.json`. It reads the real disk rather than trusting build variables and generates PCR predictions.

Some refactoring was done to move certain functions to more logical locations. They are detailed below:
tools/bootconfig/src/parsers/bootconfig.pest
tools/bootconfig/src/parsers/grub.pest
tools/bootconfig/src/parsers/grub.rs

  • These files are moved from tools/pcrsys/src/parsers

tools/bootconfig/src/parsers/bootconfig.rs

  • extract_values - returns vector of strings instead of comma separated strings
  • process_entry - BootconfigRule::bool_key errors out because rpm2img enforces the fact that boot parameters have to be key value pairs.
  • the rest of the functions are a copy

tools/bootconfig/src/grub_cmdline.rs

  • repair_quotes from tools/bootconfig/src/parsers/bootconfig.rs

tools/bootconfig/src/params.rs

  • format_params from tools/bootconfig/src/parsers/bootconfig.rs

Testing done:
Tested alongside bottlerocket-os/bottlerocket-core-kit#1076

  • Built aws-mantle-1, aws-ecs-3, aws-ecs-4 - both arches, nvidia and fips variants included
    • Launched instances and ensured it boots
    • Ensured the kernel command line is as expected
    • Ensured both pcr banks are predicted and it matches the measured value

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@piyush-jena
piyush-jena force-pushed the uki-pcrsys-support branch 2 times, most recently from 74fa987 to 0753837 Compare September 11, 2026 01:09
@piyush-jena piyush-jena changed the title Update PCR calculations to support UKI Remove kernel cmdline hardcoding and enable PCR calculations for UKI Oct 8, 2026
@piyush-jena
piyush-jena force-pushed the uki-pcrsys-support branch 2 times, most recently from de92863 to d8a62bd Compare October 14, 2026 09:37
@piyush-jena
piyush-jena marked this pull request as ready for review October 14, 2026 18:16
@piyush-jena
piyush-jena force-pushed the uki-pcrsys-support branch 4 times, most recently from 540c49c to 27d7dc2 Compare October 20, 2026 10:47

@jmt-lab jmt-lab left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do a pass for your AI to remove narrative comments, its not that bad right now but i see a few places where it is more narrative then helpful

Comment thread tools/bootconfig/src/params.rs Outdated
Comment on lines +69 to +83
merged.entry(name).or_default().extend(
value
.split(',')
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string),
);
}

// The merged value is quoted because an unquoted comma-separated value is a
// bootconfig array, which the kernel expands back into one command-line
// assignment per value.
Ok(merged
.into_iter()
.map(|(key, values)| format!("{key} = \"{}\"\n", Vec::from_iter(values).join(",")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Don't split, sort and dedupe the inside of bootconfig values when merging drop-ins

merge_parameters splits every value on ,, trims each piece, puts the pieces in a BTreeSet (sorted, deduplicated) and joins them back into one quoted value. That changes what the kernel sees for any value where commas or order matter. rpm2img now runs ukisys merge-bootconfig for every image, GRUB images included, so this affects both boot paths:

  • kernel.console = ttyS0,115200n8 becomes kernel.console = "115200n8,ttyS0", so the kernel gets console=115200n8,ttyS0 and the serial console breaks.
  • kernel.console = tty0, ttyS0 (an array, which the rpm2img grep check allows) used to expand to console=tty0 console=ttyS0. It now becomes the single argument console=tty0,ttyS0, which means device tty0 with options ttyS0. The an_array_becomes_one_value test locks this in, while the ukisys test cmdline_joins_bootconfig_with_image_builder_arguments (no merge step) expects the expanded form, so the two code paths disagree.
  • Values where order matters, such as lsm=, get alphabetised. Values with empty fields, like a,,b or dm-style tables, lose those fields.

Suggested fix: merge only identical repeated assignments, or keep each contribution's raw value as an array element in first-seen order (emit key = v1, v2, so the kernel gets one argument per contribution). Don't re-split inside a quoted value. If comma-joining is really intended for list-valued keys like module_blacklist, limit it to an explicit allow-list of those keys and keep the original order.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have scope to know if this is a real finding or not, let me know if not

Comment thread twoliter/embedded/rpm2img Outdated
# (for reading grub.cfg/vmlinuz), but UKI images use a FAT BOOT-A partition,
# so PCR prediction would fail here.
if [[ "${UEFI_SECURE_BOOT}" == "yes" && "${UKI_IMAGE}" == "no" ]]; then
if [[ "${UEFI_SECURE_BOOT}" == "yes" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Gate pcrsys on platform for UKI images, or non-AWS secure-boot UKI builds now fail

This hunk drops the UKI_IMAGE == no guard, so pcrsys disk now runs for every secure-boot image, UKI images included. The step can't fail quietly: stdout is redirected into pcr-predictions.json, and rpm2img runs with errexit. On the UKI path, predict_pcrs in tools/pcrsys/src/main.rs bails with ensure_whatever!(platform == Platform::Aws, "direct UKI prediction supports AWS only"). Neither rpm2img nor img2img limits --with-uki-image=yes to AWS. So a metal or vmware variant built with UKI_IMAGE=yes and UEFI_SECURE_BOOT=yes, which used to build (prediction was skipped), now fails at the end of the build.

The same change is in twoliter/embedded/img2img (~line 679).

Suggested fix: keep skipping prediction for non-AWS UKI images, e.g. if [[ "${UEFI_SECURE_BOOT}" == "yes" && ( "${UKI_IMAGE}" == "no" || "${VARIANT_PLATFORM,,}" == "aws" ) ]]. Alternatively, reject UKI_IMAGE=yes for non-AWS platforms up front with a clear error.

Comment thread tools/ukisys/src/main.rs Outdated
Comment on lines +5 to +8
//! ukisys: constructs kernel commandline for UKIs, derives the unsigned
//! systemd-stub PE a Bottlerocket UKI was built from, by stripping its
//! Authenticode signature and truncating the trailing payload sections.
//! Those sections are `.osrel`, `.cmdline`, `.uname`, and `.linux`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Remove the duplicated crate doc paragraph

The new module doc text (lines 5-8) was added next to the old paragraph (lines 1-4) instead of replacing it, so rustdoc now shows "ukisys: ..." twice with nearly the same text. Delete lines 1-4 and keep the new wording that mentions command-line construction.

Add a bootconfig crate for the image tools to share:

- Decode binary bootconfig.data and parse bootconfig text.
- Merge the module_blacklist and initcall_blacklist values that
  packages contribute in separate drop-ins into one value each.
- Compose the command line embedded in a UKI, rejecting lines that
  collapsing whitespace would change, unpaired quotes, and lines too
  long for the kernel.
- Parse grub.cfg, and predict the command line GRUB starts the kernel
  with and the one the kernel then reports.

The parsers start from pcrsys's bootconfig and grub.cfg grammars.

Signed-off-by: Piyush Jena <jepiyush@amazon.com>
UKI images carried hard-coded systemd and FIPS tokens in place of
bootconfig. Build their command line from the image's own bootconfig
instead:

- `ukisys merge-bootconfig` combines the drop-ins' module_blacklist and
  initcall_blacklist values, so `bootconfig -a` accepts them.
- `ukisys cmdline` composes the command line from bootconfig.data and
  the image's base kernel and init arguments.
- rpm2img assembles bootconfig before the UKI, passes the composed
  command line to ukify, and shares the base arguments with grub.cfg.

Drop uki_bootconfig_cmdline from imghelper, along with its test.

Signed-off-by: Piyush Jena <jepiyush@amazon.com>
Predict PCRs for UKI images that the firmware boots directly, on AWS:

- PCR 4 measures the UKI's Authenticode digest.
- PCR 5 has one state, since no BOOT partition attributes change.
- PCRs 7 and 14 carry no shim measurements.
- PCR 9 measures the LoadOptions and the os-release initrd that
  systemd-stub generates.
- PCR 11 measures the UKI's sections and the boot phases.

Also:

- Read the partition table and the boot files once, into a model of
  the disk image.
- Parse grub.cfg and bootconfig with the bootconfig crate.
- On aarch64, GRUB starts the kernel through its PE entry point, whose
  EFI stub measures GRUB's LoadOptions into PCR 9 before rottweiler
  extends /proc/cmdline. Predict that event too.
- Reject GPT headers that claim more than 128 partition entries, before
  gptman reserves memory for every entry.

Signed-off-by: Piyush Jena <jepiyush@amazon.com>
rpm2img and img2img skipped PCR prediction for UKI images. pcrsys now
predicts direct UKI boot on AWS, so run it for AWS UKI images too. UKI
images for other platforms are still skipped.

Signed-off-by: Piyush Jena <jepiyush@amazon.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants