---
title: "Fetching AMD SEV Reports (With Zig)"
slug: fetching-amd-sev-reports-with-zig
url: https://listedarticles.com/articles/fetching-amd-sev-reports-with-zig
canonical_url: https://bschaatsbergen.com/posts/zig-sev-guest/
content_type: tutorial
language: en
published_at: 2026-10-10T22:00:00.000Z
updated_at: 2026-10-11T23:10:13.323Z
author: "Bruno Schaatsbergen"
authored_by: human
publisher: "Bruno Schaatsbergen"
publisher_url: https://bschaatsbergen.com/
topics: ["Security", "Programming"]
license: all-rights-reserved
word_count: 969
reading_minutes: 4
citation: "Bruno Schaatsbergen, Bruno Schaatsbergen. \"Fetching AMD SEV Reports (With Zig).\" 10 Oct 2026. https://bschaatsbergen.com/posts/zig-sev-guest/ (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Fetching AMD SEV Reports (With Zig)

> A walkthrough of AMD SEV-SNP attestation: what confidential VMs protect against, how to retrieve a signed attestation report on Linux and through Coconut SVSM and OpenHCL, and how to verify its certificate chain and validate it against expected policy using a Zig library.

In an ordinary VM, the hypervisor can read the guest’s memory.
[AMD SEV](https://www.amd.com/en/developer/sev.html), Secure Encrypted
Virtualization, encrypts that memory with a per-VM key the hypervisor does
not receive. SEV-ES extends the protection to saved CPU register state.

SEV-SNP, Secure Nested Paging, adds checks on memory ownership and mappings
to prevent a malicious hypervisor from substituting or remapping private
pages. This protects the guest from the host; it doesn’t make software
inside the guest trustworthy or prevent the host from stopping the VM.

SNP lets the guest request a report signed by the AMD secure processor,
providing evidence of the VM’s launch state and configuration. It contains
the launch measurement, guest policy, platform security versions and 64
bytes of caller-supplied `REPORT_DATA`.

Before the VM starts, the host loads its starting software into memory.
This includes the guest’s boot firmware: the startup software that initializes
the VM’s boot environment and hands control to the operating system’s
bootloader. On Google Cloud,
that’s [Google-managed UEFI firmware](https://docs.cloud.google.com/confidential-computing/confidential-vm/docs/verify-firmware)
based on [Open Virtual Machine Firmware (OVMF)](https://www.tianocore.org/tianocore-wiki.github.io/platforms-packages/platform-ports/ovmf.html).

The starting software can also include
[Coconut SVSM](https://github.com/coconut-svsm/svsm), a separate program that
hosts services such as a virtual TPM. The UEFI firmware still boots the OS;
SVSM provides services that need protection from the OS itself.

AMD’s SNP firmware runs on the secure processor. The host starts a launch
with `SNP_LAUNCH_START`, then supplies the starting image in chunks called
memory pages through
[`SNP_LAUNCH_UPDATE`](https://docs.kernel.org/virt/kvm/x86/amd-memory-encryption.html#kvm-sev-snp-launch-update).
For each normal code or data page, the firmware hashes the bytes it installs,
their guest-memory address, page type and permissions, together with the
previous hash. AMD’s firmware maintains this running hash in protected
state; the host cannot supply a replacement value.

`SNP_LAUNCH_FINISH` closes the launch. The final SHA-384 hash is the
**launch measurement**, a fingerprint of that initial image, including
SVSM when present. Later, the firmware
[copies this stored value into the report](https://github.com/amd/AMD-ASPFW/blob/3ca6650dd35d878b3fcbe5c7f58b145eed042bbf/fw/psp_bl_uapps/sev_uapp/src/sev_mcmd.c#L5677-L5679).
It stays fixed as the VM runs.

When performing [remote attestation](https://www.rfc-editor.org/rfc/rfc9334.html),
a Verifier compares it with the expected fingerprint for the starting
software and launch configuration it trusts. Google publishes
[signed launch endorsements](https://docs.cloud.google.com/confidential-computing/confidential-vm/docs/verify-firmware#retrieve)
containing those expected measurements for its firmware. Changing the
firmware bytes changes the fingerprint. Marking those pages as *unmeasured*
skips hashing their contents, but changes the page type included in the hash.
Either way, the result differs from the expected fingerprint, so the
Verifier can reject the attestation.

The kernel and applications that the firmware loads afterwards are not
automatically covered by this fingerprint.

## retrieving a report on Linux

Linux exposes the [SNP guest interface](https://docs.kernel.org/virt/coco/sev-guest.html)
at `/dev/sev-guest`. An application sends the `SNP_GET_REPORT` ioctl, and
the driver handles the protected exchange with AMD SNP firmware.

Alongside the report data, the request supplies a VM privilege level, or
VMPL: 0 through 3, with 0 most privileged. These levels matter because
Linux isn’t always the most privileged software inside the VM. Coconut
SVSM, for example, can use that separation to host a virtual TPM whose
keys are isolated from the guest kernel. Being root in Linux doesn’t put
you at VMPL0. Use the VMPL supported by your guest.

Here’s the request in Zig (0.17.0), using the layouts from Linux’s guest
API. `fd` is an open `/dev/sev-guest` descriptor inside an x86-64 Linux SNP
guest:

```
const std = @import("std");
const linux = std.os.linux;

const ReportRequest = extern struct {
    user_data: [64]u8,
    vmpl: u32,
    reserved: [28]u8 = @splat(0),
};

const GuestRequest = extern struct {
    msg_version: u8 = 1,
    padding: [7]u8 = @splat(0),
    req_data: u64,
    resp_data: u64,
    exitinfo2: u64 = 0,
};

pub fn fetchReport(fd: i32, challenge: [64]u8, vmpl: u32) ![1184]u8 {
    var request: ReportRequest = .{ .user_data = challenge, .vmpl = vmpl };
    var response: [4000]u8 = @splat(0);
    var call: GuestRequest = .{
        .req_data = @intFromPtr(&request),
        .resp_data = @intFromPtr(&response),
    };

    const SNP_GET_REPORT = 0xc0205300;
    const result = linux.ioctl(fd, SNP_GET_REPORT, @intFromPtr(&call));
    if (linux.errno(result) != .SUCCESS or call.exitinfo2 != 0) {
        return error.ReportRequestFailed;
    }

    const status = std.mem.readInt(u32, response[0..4], .little);
    const size = std.mem.readInt(u32, response[4..8], .little);
    if (status != 0 or size != 1184) return error.InvalidResponse;

    // The report follows the 32-byte firmware response header.
    return response[32..][0..1184].*;
}
```

Pass a fresh 64-byte challenge from the verifier and the guest’s VMPL.
The challenge comes back in `REPORT_DATA`, so the verifier can reject an
old report. The result is a raw report; its signature and policy still
need checking.

I packaged the retrieval, report parsing and verification in
[zig-sev-guest](https://github.com/bschaatsbergen/zig-sev-guest), so callers
don’t have to work with these buffers directly.

## retrieving reports through Coconut SVSM and OpenHCL

[Coconut SVSM](https://github.com/coconut-svsm/svsm) and
[Microsoft’s OpenHCL](https://openvmm.dev/guide/reference/architecture/openhcl.html)
have different designs, but both run privileged services such as a vTPM
inside the confidential VM. On SNP, AMD hardware encrypts that memory—including
the TPM’s private keys—with a per-VM key managed by the secure processor.
The guest kernel runs at a less privileged VMPL than SVSM or OpenHCL.
SNP’s page permissions block it from reading the pages holding the vTPM’s
private keys.

The guest can ask AMD to sign a report containing a hash of any public key.
That does not prove the key belongs to the protected vTPM. SVSM and OpenHCL
build the request using the public keys of the TPM they host. The
application therefore obtains this report through them.

Three request paths, all backed by SNP firmware.

## checking the evidence

In [zig-sev-guest](https://github.com/bschaatsbergen/zig-sev-guest), I use OpenSSL to verify the signing certificate’s chain
against the embedded AMD roots and check the report’s ECDSA P-384 signature.
The certificate’s security versions, and chip identity where present, must
also match the report.

Policy validation then compares the report with the expected launch
measurement, `REPORT_DATA`, VMPL and minimum platform security versions.
Debugging, SMT and migration are rejected unless explicitly allowed.

`verify.andValidate` performs both steps on the same report. The caller
supplies the expected values, including the fresh challenge, and checks any
vTPM-specific claims and quotes separately.
