Skip to content

build: reproducible IGVM builds and build info files - #1240

Draft
bschaatsbergen wants to merge 8 commits into
coconut-svsm:mainfrom
bschaatsbergen:reproducible-builds
Draft

bschaatsbergen wants to merge 8 commits into
coconut-svsm:mainfrom
bschaatsbergen:reproducible-builds

Conversation

@bschaatsbergen

@bschaatsbergen bschaatsbergen commented Oct 10, 2026 •

Copy link
Copy Markdown

Fixes #1239.

Note: This is exploratory. I'm new to the codebase and haven't talked to any providers running their own SVSM builds to understand how they actually do this. Feedback on the approach is welcome. I'll likely have quite a bit wrong, just trying to piece out how to get around my problem as described in #1239.

Building the same commit twice could give two different launch measurements:

  • The kernel embedded the checkout path and the cargo registry path (through panic locations and __FILE__), so builds in different directories or with a different CARGO_HOME didn't match.
  • make RELEASE=1 and cargo xbuild --release built libtcgtpm differently. RELEASE only reached the libtcgtpm make through MAKEFLAGS, so one built the TPM at -O3 and the other at -O0.

With this proposal, the same commit, recipe, firmware and toolchain should produce the same IGVM file and the same measurement everywhere.

Changes

  • xbuild passes --remap-path-prefix for the workspace root and CARGO_HOME via --config target.x86_64-unknown-none.rustflags. This merges with .cargo/config.toml, unlike RUSTFLAGS, which would replace it.
  • libtcgtpm takes RELEASE from the cargo profile and builds with -ffile-prefix-map.
  • git describe uses a fixed --abbrev, so clones of different depth embed the same version string.

Build info and verification

Each build now writes a .buildinfo.json next to the IGVM file, similar to Debian's buildinfo files. It records the commit, recipe hash, component hashes, firmware hash, build options, toolchain versions and the launch digest, and is meant to be published alongside the image.

cargo xbuild --verify <buildinfo> <recipe> rebuilds and compares. On a mismatch it prints which inputs differ.

On match:

Building svsm...
    Finished `release` profile [optimized] target(s) in 2m 57s
Building bldr...
    Finished `release` profile [optimized] target(s) in 14.97s
Building tdx-stage1...
    Finished `release` profile [optimized] target(s) in 1.39s
Building userinit...
    Finished `release` profile [optimized] target(s) in 10.42s
Building igvmbuilder...
    Finished `release` profile [optimized] target(s) in 23.20s
Building igvmmeasure...
    Finished `release` profile [optimized] target(s) in 21.59s
Launch Digest (bin/coconut-vanadium.igvm): C20002F0BDBC357D8786884FCF0D686763164D498F1851389D4752C26F99C5E831A5A8D4DD6FE96A66D7F4E8BB15C75B

Verifying bin/coconut-vanadium.igvm against /expected/coconut-vanadium.igvm.buildinfo.json
launch digest  ok       C20002F0BDBC357D8786884FCF0D686763164D498F1851389D4752C26F99C5E831A5A8D4DD6FE96A66D7F4E8BB15C75B
image          ok       6f463206f83dc22a207984b62b9852a3712640a850a3e5d891fa7a0cda783106

On mismatch:

Verifying bin/coconut-vanadium.igvm against /publish/patched.buildinfo.json
launch digest  differs  built:    C20002F0BDBC357D8786884FCF0D686763164D498F1851389D4752C26F99C5E831A5A8D4DD6FE96A66D7F4E8BB15C75B
                        expected: AF44F2309B56B91C0DDBF317C0FE60576F75C4D92ABA0ECAA2FF0D04AFD9761A824C2723CF31184DC890004E52CD8F32
image          differs  built:    6f463206f83dc22a207984b62b9852a3712640a850a3e5d891fa7a0cda783106
                        expected: d928249e375e72eadc3194a0ebf1e7d6d33649667107e8388362183a1e9304af

Inputs that differ:
bldr           ok       8ef240fbdd6e953b1b59350705c6696843c0c33fc57d2c5a53f4db83c7197c13
fs             ok       973f0ed4bcc37cc93077456ac8413e8e96ab22213042f46e5686cca63f769960
kernel         differs  built:    24afa7e1a64ea45122dc8b473f8d48dd0f66fbb96c5b5c288e61aa3103a8669d
                        expected: 685b8adb0a87549bf8390977d196c89fbeb3f0732db76f2200be529e3c0e44ba
tdx-stage1     ok       41d3c548fd68506cf246913c9064837141b734eaeb741dd74108120a13ac44c3
firmware       ok       sha384 a315a017d8815c680574d2d6c7c593685ef8a8565779217dc6f88835425c54ca12633af3c3aa731e1a2fbf55839a3970
recipe         ok       820c34a4797df1b9d2372faa93eaba17e4cde87f9d585decdfb9761df6a4cbe2
commit         ok       a4a62924768c01fa61465ce982a14e448be4ffe7
describe       ok       v2026.09-devel-39-ga4a62924768c
release        ok       true
all features   ok       false
features       differs  built:
                        expected: vtpm
rustc          ok       rustc 1.88.0 (6b00bc388 2025-06-23)
cargo          ok       cargo 1.88.0 (873a06493 2025-05-10)
cc             ok       gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
objcopy        ok       GNU objcopy (GNU Binutils for Ubuntu) 2.42
Error: "the image does not match the build info"

The buildinfo file is only used for comparison. The recipe must match the recorded hash before anything is built, and unknown fields are rejected, so a third-party file can't control what gets executed.

Needs a decision

cargo xbuild --release now builds the vTPM C code at -O3 -DDEBUG=NO, same as make RELEASE=1 always did. This changes the measurement for that documented path.

This does not address

Vendors building from a private fork. Reproducible builds make a published commit checkable; unpublished source needs a separate discussion.

The measured image contained the checkout path and CARGO_HOME through
panic locations, so the same commit measured differently in another
directory. Remap them the way cargo's unstable trim-paths does, via
--config so that .cargo/config.toml keeps applying. Sort the fs modules
so the archive order is fixed.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
RELEASE only reached the inner make through MAKEFLAGS from a top-level
make RELEASE=1, so cargo xbuild --release built the TPM at -O0 while
make RELEASE=1 built it at -O3. Set it from PROFILE either way.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
assert() embeds __FILE__, which cmake passes as an absolute path, so
NVMem.c carried the checkout path into the kernel. Map the repository
root and OUT_DIR the same way xbuild does for the Rust side.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
git describe picks the abbreviation length from the number of objects
in the repository, so a shallow and a full clone of the same commit
embedded different version strings.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
A launch measurement alone does not say which code it stands for.
Write a .buildinfo.json next to every IGVM with the commit, recipe
and component hashes, firmware hash, build options, toolchain versions
and the launch digest, so whoever publishes an image can publish what
went into it, like the buildinfo files of Debian and other
distributions.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
Build the recipe given on the command line and compare the launch
digest and image hash with a build info file. The file is only
compared against: the recipe must hash to the recorded one before
anything is built, and unknown fields are rejected, so a file from a
third party cannot choose what gets executed. On a mismatch, list which
inputs differ so the reason does not have to be guessed.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
Build the qemu image in one checkout and verify it from a second one
at a different path with another CARGO_HOME.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
State what has to match for two builds to give the same measurement,
and how to check an image against its build info file.

Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
@00xc 00xc self-assigned this Oct 13, 2026

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.

Make IGVM builds reproducible so users can check launch measurements against source

2 participants