Skip to content

feat(report): record the SEV platform facts that distinguish otherwise-identical hosts - #311

Draft
markg-github wants to merge 6 commits into
AMDEPYC:mainfrom
markg-github:feat/report-sev-environment
Draft

feat(report): record the SEV platform facts that distinguish otherwise-identical hosts#311
markg-github wants to merge 6 commits into
AMDEPYC:mainfrom
markg-github:feat/report-sev-environment

Conversation

@markg-github

Copy link
Copy Markdown
Contributor

What

Records, in every certification result, the facts that distinguish two hosts
which look identical by OS, kernel, QEMU and OVMF version but behave differently
under SNP.

New lines in the environment block:

- **Host CPU:** AMD EPYC 9654 96-Core Processor (family 0x19 model 0x11 stepping 0x1)
- **SEV firmware:** 1.55
- **SEV firmware blobs:** none present — BIOS-supplied firmware retained
- **SEV driver log:** `ccp …: SEV firmware updated from 1.55.38 to 1.55.39 | ccp …: SEV API:1.55 build:39`
- **Platform ID:** 21EFA113…
- **Attestation report:** version=3 cpuid=0x19/0x11/0x01 chip_id=present
- **Reported TCB:** bootloader=9 tee=0 snp=23 microcode=72 fmc=None
- **snphost:** snphost 0.7.0
- **snpguest (host):** snpguest 0.10.0
- **snpguest build tag:** v0.10.0
- **snpguest (guest):** snpguest 0.10.0

Why

A worked example, which is what prompted this. Eight distributions failed
attestation-test with processor is not of know SEV-SNP generation. The
results read as eight independent distribution failures. They were one platform
bug: the BIOS supplies SEV firmware 1.55 build 38, which leaves
CPUID_FAM_ID/MOD_ID/STEP zeroed in version-3 reports, and no host image
installs linux-firmware
, so nothing overrides it. Build 39 populates those
fields. Establishing that took several days and three discarded hypotheses,
because none of the relevant facts appeared in any result.

With these changes the same sweep reports blobs=none, build=38,
no override, and cpuid=0x00/0x00/0x00 on every image — the shared cause is
visible at a glance, and the two genuine distribution findings (Debian 13 ships
no SEV OVMF; Debian forky's kernel and QEMU cannot register a guest_memfd
slot) separate cleanly from it.

This overlaps with #268 and #265 in intent, and is a step toward both rather
than a substitute for either.

Notes for review

  • All detection is best-effort. Anything unavailable — no snphost, not
    running as root, a host this project did not build — yields None and the
    line is omitted. Nothing here can fail a run.
  • sev_firmware_source reports absence explicitly rather than omitting the
    field, because absence is the informative case: it means no override occurred
    and the platform is running its own firmware.
  • The driver log is quoted, not parsed. It carries the build number
    snphost show version omits, but its wording varies across kernels, and a
    parser would silently yield nothing on versions it did not anticipate.
  • The one commit that touches an existing test is the configfs-TSM capture,
    which adds two info steps to attestation-test. It exists because
    snpguest report will not write a report it cannot classify, so precisely
    when a run most needs the report there is no artifact at all. The kernel's
    vendor-neutral configfs-TSM interface returns the raw bytes without
    interpreting them. A guest whose kernel lacks support pays nothing. Filed
    upstream as snpguest report discards a report it cannot classify by processor generation virtee/snpguest#158.

Testing

Verified on an EPYC 9654 (Genoa) across nine host images and both firmware
states — BIOS-supplied build 38 with no blobs present, and build 39 loaded from
linux-firmware — plus the absent-tool paths on a machine with no SEV at all.

Two hosts can be identical by OS, kernel, QEMU and OVMF version and still
behave differently under SNP, so a failing certification result currently
cannot be diagnosed from its own report.

Record the facts that actually distinguish them:

  - SEV firmware version.  This is the firmware in effect, which is not
    necessarily the one the BIOS supplied: the ccp driver loads
    /lib/firmware/amd/*.sbin at boot when present, so it tracks the host OS
    image rather than the platform.
  - Reported TCB, condensed to one line.
  - Host CPU model and CPUID family/model/stepping.  Generation-dependent
    behaviour keys on family and model, both here and in the tooling this
    harness drives, so a failure that turns on processor generation is
    otherwise invisible in the result.
  - snphost and snpguest versions as found on the host.

Detection is best-effort in the existing style: anything unavailable, or
unreadable because the harness is not running as root, yields None and is
simply omitted from the report.
The host and guest each carry their own snpguest, installed independently.
When a guest-side attestation step fails there is currently nothing in the
result identifying which build produced the failure, so the first question
asked of such a report cannot be answered from it.

Add get_guest_snpguest_version() next to the existing guest OS probe, and
introduce update_environment_with_guest_info() to gather both on the first
successful launch. The existing update_environment_with_guest_os() keeps its
narrower meaning rather than quietly growing a second responsibility.

Best-effort as before: no guest, no vsock agent, or no snpguest on the guest
yields None and the line is omitted.
The snpguest build module resolves "latest" from the GitHub API at build
time unless SNPGUEST_TAG is set, so two builds of identical source can
install different tooling and nothing in the resulting image, or in the
certification result it produces, says which one it got.

Write the resolved tag to /usr/local/share/sev-certify/snpguest-tag during
the build and report it alongside the detected versions. Absent on hosts not
built by this project, in which case the line is simply omitted.

This records the tag rather than pinning it; pinning is a policy decision
left to SNPGUEST_TAG.
Two further facts that distinguish otherwise-identical hosts.

Platform identifier, from snphost show identifier, so results can be tied to
a specific machine rather than to a machine description.

A one-line summary of any attestation report the run produced: its version,
its CPUID family/model/stepping bytes, and whether CHIP_ID came back zeroed.
These decide how a report is parsed rather than what it attests — consumers
select a TCB layout from the processor generation implied by those CPUID
bytes, and reject the report outright when they do not recognise it, before
reading any of its contents. A result that fails there currently says only
that it failed.

CHIP_ID is included because MASK_CHIP_ID zeroes it and snphost show offers
no way to read that setting back, so a zeroed CHIP_ID is the only externally
visible sign that masking is in effect. The mask is set by
snphost config set <bl> <tee> <snp> <ucode> <mask> and cleared by
snphost config reset, which the config/commit test exercises directly.

The report is located by mtime against the start of the run so one left over
from an earlier run is not described as though this run produced it.
snpguest declines to write a report whose processor generation it cannot
resolve, so precisely when a run most needs the report it produces no
artifact at all: the one thing that would explain the failure is destroyed
by the failure.

Capture it a second way. The kernel's configfs-TSM interface is vendor
neutral and returns the raw report without interpreting it, so it yields
bytes in cases where the snpguest path yields nothing:

    mkdir /sys/kernel/config/tsm/report/sev_verify
    head -c 64 /dev/urandom > .../inblob
    cat .../outblob

Both steps are typed "info", so a guest whose kernel lacks configfs-TSM
support costs nothing and the certification result is unaffected either way.
The pulled artifact is named tsm-report.bin and joins report.bin as a source
the environment summary will describe, whichever is newer.

Verified against a Rocky 10.2 guest (kernel 6.12): 1184 bytes, parsing as a
version 3 report. The two reports differ in their bytes because each carries
its own nonce and signature.
The reported SEV firmware version is the one currently in effect, but says
nothing about where it came from. Two versions are in play at different
times, and they govern different things: the BIOS-supplied firmware runs at
processor init and decides platform-level behaviour, while the ccp driver
may replace it at boot from /lib/firmware/amd/*.sbin, after which every
firmware command answers from the replacement.

Whether that replacement happens is a property of the host OS image. A
result that reports only the resulting version cannot be read correctly,
because the same version string can mean "this platform's own firmware" or
"whatever this distro happened to ship".

Record two things:

  - The SEV firmware blobs available to the driver, identified by name and
    digest. Their absence is stated explicitly rather than left as a missing
    field, because absence is the informative case: it means no override
    occurred and the platform is running BIOS-supplied firmware.

  - The driver's own SEV lines from the kernel log, verbatim. These carry the
    firmware build number that snphost show version omits and report whether
    an update was applied at boot. They are quoted rather than parsed: the
    wording varies across kernels, and a parser would silently produce
    nothing on the versions it did not anticipate.

Digests are recorded because two builds of a blob share a filename and may
share an API version while differing in behaviour.
Copilot AI lite review requested due to automatic review settings August 31, 2026 16:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR expands sev_verify’s reporting so each certification result can include additional SEV/SNP platform facts (firmware state, CPU identity, platform identifier, tooling versions) that help distinguish otherwise-identical hosts and quickly correlate shared root causes across runs.

Changes:

  • Extend markdown output to include additional environment fields (CPU, SEV firmware details, platform identifier, attestation report summary, snphost/snpguest versions, and guest snpguest version).
  • Add host-side environment detection for SEV/SNP facts and a lightweight attestation report summarizer, plus logic to locate a report artifact produced during the run.
  • Record the snpguest release tag at image-build time and (best-effort) query the guest’s snpguest version via vsock; add configfs-TSM report capture steps to attestation-test.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
sev_verify/output.py Renders newly collected environment facts into the markdown environment block.
sev_verify/os_info.py Adds best-effort guest snpguest --version collection and a combined guest info updater.
sev_verify/environment.py Implements SEV/SNP environment probing, report summarization, and report artifact discovery.
sev_verify/cli.py Integrates guest info enrichment and populates report_summary from newly produced artifacts.
sev_verify/cert_tests/c3_0/c3_0_0_0/attestation_test.py Adds configfs-TSM report capture/pull as diagnostic info steps.
modules/build/common/snpguest/mkosi.build Persists the installed snpguest release tag into the built image for later reporting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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