> This guide is a continuation of the the main [AMD SEV-SNP](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption) article and assumes you have already [enabled](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#enabling-sev-snp-for-your-vm) and [verified](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#attestation) SEV-SNP on your VM.

# Introduction

In an [AMD SEV-SNP](https://www.amd.com/en/developer/sev.html) environment, the memory and CPU registers of your guest virtual machine are cryptographically protected from the hypervisor. However, ensuring **confidentiality** is only half of the puzzle. With SEV-SNP architecture, it's also possible to cryptographically verify the **integrity** of the operating system code executing at boot.

During the initial launch, the [AMD Secure Processor (ASP)](https://en.wikipedia.org/wiki/AMD_Platform_Security_Processor) measures the initial guest memory state, which includes the virtual firmware (OVMF) and the loaded boot artifacts. In a traditional boot architecture, the kernel and initramfs are separate files loaded dynamically from disk. If kept separate, a malicious hypervisor could modify the initramfs (to inject malware) or alter the kernel command line parameters without changing the kernel binary itself.

A [Unified Kernel Image](https://wiki.archlinux.org/title/Unified_kernel_image) (UKI) solves this by packing the UEFI boot stub, the Linux kernel, the initramfs, and the kernel command line into a single .efi [PE/COFF](https://wiki.osdev.org/PE) binary. Because the entire binary is loaded into memory and measured as a single unit at boot, any tampering with the command line or early userspace code will alter the SEV-SNP launch measurement.

The AMD Secure Processor calculates the launch measurement based on several inputs: the number of vCPUs, CPU model, the UEFI firmware (OVMF) and the UKI (kernel+parameters+initramfs). If any of these parameters differ, the resulting hash will change. You may calculate the [expected launch measurement](https://www.qemu.org/docs/master/system/i386/amd-memory-encryption.html#calculating-expected-guest-launch-measurement) value independently in a trusted environment using the [sev-snp-measure](https://github.com/virtee/sev-snp-measure) utility. Then compare it with the measurement hash returned by the AMD Secure Processor.

For the ASP to include the UKI in its OVMF measurement, a [QEMU direct boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) needs to be performed. The simplified procedure is explained in the section below.

## Measured Direct Boot Process

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdlFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d71444cafa0ebd5096b9d29c0048eac7f1d4e416/901-boot-process.png.webp)

We start by User supplying their UKI image and booting the VM. Before the actual boot process starts, QEMU calculates SHA-256 hash of UKI and puts it in a special reserved section of memory where OVMF was loaded (`SEV hashes table` section, type: [0x10](https://github.com/virtee/sev-snp-measure/blob/8f2b337e38bc83f87cd30f3253cdfe8e3e12cc3a/sevsnpmeasure/ovmf.py#L21), GUID: [7255371f-3a3b-4b04-927b-1da6efa8d454](https://www.qemu.org/docs/master/specs/sev-guest-firmware.html)).

Right after this, QEMU reaches out to the ASP initiating the launch of new VM. `SNP_LAUNCH_START` allocates a new Guest Context (`GCTX`) and creates a key (`VEK` - Virtualization Encryption Key) which will be used for establishing encrypted communication with the VM.

Once the context is allocated, QEMU issues 3 subsequent `SNP_LAUNCH_UPDATE` commands to the ASK to measure and encrypt OVMF memory pages:

* At first, the main OVMF firmware code gets measured and encrypted with `VEK`.

* Next, the injected `SEV hashes table` (the included kernel, kernel parameters and initramfs get measured at this stage).

* Finally, the Virtual Machine Save Area (`VMSA`), `CPUID` and `Secrets` page get encrypted and measured. The last one is particularly important - besides measuring it, within the Secrets Page structure ASP also returns 4 unique encryption keys used for communication at different Virtual Machine Privilege Levels - `VMPL0-3` (corresponding keys `VMPCK0-3`).

Lastly, the `SNP_LAUNCH_FINALIZE` command is sent. The ASP locks the [Guest Policy](https://gist.githubusercontent.com/danko-miladinovic/1a118cdfe72c7bf01da6936d38bc936a/raw/b5dc43dbe791d30db0e0cce20f5c187cb53ca78b/AMD_SEV_SNP_Guest_Policy.md) and finalizes the Launch Digest (`GCTX.LD`) computation. ASP has finished all stages required to generate attestation report. At this stage, the guest VM can be booted.

More details regarding specific commands and parameters used for communication with AMD ASP can be found in the [SEV Secure Nested Paging Firmware ABI Specification](https://www.amd.com/content/dam/amd/en/documents/developer/56860.pdf) document.

Transitioning to Guest VM context, at this point OVMF is already encrypted in the guest's memory (with `SNP_LAUNCH_UPDATE(OVMF pages)`, the ASP performed encryption). QEMU starts execution setting the CPU Instruction Pointer at the [OVMF Reset Vector table](https://github.com/tianocore/edk2/blob/master/OvmfPkg/ResetVector/Ia16/ResetVectorVtf0.nasm.inc). In the next step, OVMF [executes ](https://lwn.net/Articles/972039/)a special `PVALIDATE` instruction on all accessed guest memory pages. A bit on memory page needs to be changed from `Guest-Invalid` to `Guest-Valid` when accessed from guest. This is an important step which aims to mitigate [memory aliasing attacks](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3015.html). 

To load the UKI into the memory of the guest, a special [fw_cfg interface](https://www.qemu.org/docs/master/specs/fw_cfg.html) of QEMU is used. It allows guest to read full contents of UKI kernel image into its own (encrypted) memory. Additionally, a hash of UKI is computed. If it does not match the hashes stored in `SEV hashes table`, OVMF will halt boot at this stage. Otherwise, it will proceed with booting the kernel by jumping to its [entry point](https://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html).

# Configuring Measured Direct Boot

## Generating UKI image with dracut

Since most [SEV-SNP supporting Linux distributions](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#operating-system-support) come with [dracut](https://wiki.archlinux.org/title/Dracut) initrd generator, this section will assume it is already installed and configured on the system. Unlike the old [initramfs-tools](https://wiki.debian.org/initramfs-tools) (default generator on Debian), dracut can also [generate UKI kernel images](https://wiki.archlinux.org/title/Unified_kernel_image#dracut) natively.

We just need to install the [systemd-boot](https://wiki.archlinux.org/title/Systemd-boot) dependency.

```
$ apt install systemd-boot           # Ubuntu 26.04+
$ dnf install systemd-boot-unsigned  # AlmaLinux 10.2+
```

The `systemd-boot` (or `systemd-boot-unsigned`) package contains the pre-compiled UEFI boot stub (`/usr/lib/systemd/boot/efi/linuxx64.efi.stub`). `dracut` requires this stub to act as the entry executable. The UEFI firmware runs this stub first, which then transitions execution to the embedded kernel and initramfs.

After that, you need to note the kernel parameters (`cmdline`) of currently booted environment.

```
$ cat /proc/cmdline 
BOOT_IMAGE=/vmlinuz-7.0.0-22-generic root=/dev/mapper/ubuntu--vg-ubuntu--lv ro
```

Next, create a new file in `/etc/dracut.conf.d/uki.conf`.

There we will tell dracut to generate UKI image (`uefi="yes"` with the `uefi_stub` of `systemd-boot` bootloader) and provide predictable kernel parameters from the step above. You should omit the `BOOT_IMAGE` or similar variables and put just the parameters after `root=`.

Then, `reproducible="yes"` makes dracut omit non-deterministic build metadata. That means, unless the actual kernel/initramfs is modified, the UKI should be byte-to-byte identical across builds.

Lastly, the `machine_id="no"` will not include [machine-id](https://manpages.debian.org/testing/systemd/machine-id.5.en.html) systemd parameter in the resulting UKI filename, making it deterministic.

```
uefi="yes"
uefi_stub="/usr/lib/systemd/boot/efi/linuxx64.efi.elf"
kernel_cmdline="root=/dev/mapper/ubuntu--vg-ubuntu--lv ro"
reproducible="yes"
machine_id="no"
```

Once the file is saved, you can trigger dracut UKI generation manually.

```
$ dracut --force
/var/tmp/dracut.d3EEoNP/uefi/uki.sbat does not contain a valid SBAT section, skipping.
Wrote unsigned /var/tmp/dracut.d3EEoNP/uefi/linux.efi
```

Given we do not use [UEFI Secure Boot](https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface/Secure_Boot) (but rather ensure verified boot via SEV-SNP), this warning regarding empty SBAT metadata block is actually expected and can be ignored.

The resulting generated UKI kernel image can be found in `/boot/efi/EFI/Linux/` directory:

```
$ ls -lah /boot/efi/EFI/Linux/
total 77M
drwxr-xr-x 2 root root 4.0K Jun 19 07:44 .
drwxr-xr-x 5 root root 4.0K Jun 19 07:36 ..
-rwxr-xr-x 1 root root  77M Jun 19 07:44 linux-7.0.0-22-generic.efi
```

Download it into a local directory using [scp](https://man7.org/linux/man-pages/man1/scp.1.html).

```
$ scp root@<vm-ip>:/boot/efi/EFI/Linux/linux-7.0.0-22-generic.efi .
```

Optionally, you may also inspect the contents of generated UKI stub. Using the `ukify inspect` command, you can see the dummy SBAT section, bootloader parameters, kernel version and cmdline, size and hashes of kernel and initrd contained in the UKI image.

```
$ apt install systemd-ukify
$ ukify inspect /boot/efi/EFI/Linux/linux-7.0.0-22-generic.efi 
.sbat:
  size: 237 bytes
  sha256: a47a46e2ddb7de186cba107151279408dc23ad54a4c8e8d3dac4203894d824b6
  text:
    sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
    systemd-stub,1,The systemd Developers,systemd,259,https://systemd.io/
    systemd-stub.ubuntu,1,Ubuntu,systemd,259.5-0ubuntu3,https://tracker.debian.org/pkg/systemd
.osrel:
  size: 406 bytes
  sha256: de1a366db70de1ae4b6612a1696f9c1f37948028fab733508a3d5cf49836faac
  text:
    PRETTY_NAME="Ubuntu 26.04 LTS"
    NAME="Ubuntu"
    VERSION_ID="26.04"
    VERSION="26.04 LTS (Resolute Raccoon)"
    VERSION_CODENAME=resolute
    ID=ubuntu
    ID_LIKE=debian
    HOME_URL="https://www.ubuntu.com/"
    SUPPORT_URL="https://help.ubuntu.com/"
    BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
    PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
    UBUNTU_CODENAME=resolute
    LOGO=ubuntu-logo
.cmdline:
  size: 42 bytes
  sha256: faa725c8adfe1844c61c5c70f149f4ba18d2bb77e7e74ba0329ed5a5b5d68f4a
  text:
    root=/dev/mapper/ubuntu--vg-ubuntu--lv ro
.uname:
  size: 16 bytes
  sha256: 29980f8b6e12ad60bd90ebde5a461cb5678ba325b22c5f69e30c0a4abac5ebb8
  text:
    7.0.0-22-generic
.linux:
  size: 17275272 bytes
  sha256: bf61b5a973bc279c55209e737987d54accb5e6b22b5ef4a7ef556a4d286641e0
.initrd:
  size: 63168764 bytes
  sha256: 06990edf68727363a27748cc9561a09b411cb9d2109227e8b8a9d2ea2b089fce
```

## Uploading the UKI image

To upload an UKI kernel image for measured boot, go to [**Orchestration > Measured Boot Images**](https://cloud.onidel.com/cloud/orchestration/measured-boot-images).

Then click on **Upload UKI** button.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdXdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6d73419b2d84026556e3cdec6cf9d5bd8e29d9b2/001-measured-boot-images-upload-uki.png.webp)

You will be presented with a popup which lets you pick a local UKI file and its description, then upload it into our platform.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdTBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dd068f79443f20bcc68fa81aabd05029aefbb603/002-upload-uki.png.webp?cw_image_width=693px)

Once the file is successfully uploaded, you will see it in the list of Measured Boot Images.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdTRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3f4b964d79698ff870951f98c0dceadcba35e56e/003-uki-visible-in-list.png.webp)

## Enabling Measured Direct Boot

After the UKI image was added on the platform, you may assign it to a VM. To achieve that, you need to first [enable SEV-SNP](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#enabling-sev-snp-for-your-vm) and then click on **Edit Measured Boot** in **Security Settings** tab.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdThCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7d9ecb5acb31cfa5cea51f17001c2fff5cf673c6/010-vm-edit-measured-boot.png.webp?cw_image_width=903px)

In the popup window, first switch the toggle on **Measured Direct Boot**, then **pick the UKI image** we've just uploaded and click **Save**.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdkFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--5b206c769fa55dfc29627df7ba6758a506b35107/011-pick-uki-enable-measured-boot-on-a-vm.png.webp?cw_image_width=639px)

After this, the VM will be automatically rebooted from your UKI image with direct boot settings applied to it.

![](https://cw.hypercore-internal.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdkVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--60c574430f9603e2d024625c46903d91c385933a/012-booted-from-uki.png.webp)

## Obtaining the OVMF firmware

Virtual Machines with measured direct boot enabled need to use a special OVMF binary containing the [SNP_KERNEL_HASHES](https://patchew.org/QEMU/20230302092347.1988853-1-dovmurik@linux.ibm.com/) metadata field. [QEMU modifies this section](https://www.qemu.org/docs/master/system/i386/amd-memory-encryption.html#calculating-expected-guest-launch-measurement) by including the hash of UKI in [PaddedSevHashTable structure](https://github.com/qemu/qemu/blob/b83371668192a705b878e909c5ae9c1233cbd5fb/target/i386/sev.c#L74) and placing it in reserved memory-mapped metadata sections of OVMF. Then, the ASP measures OVMF memory pages (including the injected UKI hash) and finalizes the Guest Context Launch Digest (`GCTX.LD`).

We publish this OVMF binary, enabling you to calculate the measurement hash independently and verify the VM runs in a proper environment. You can download it from [here](https://onsecure.s3.ap-southeast-1.onidel.cloud/OVMF.amdsev.fd) and verify with the following SHA-256 hash.

```
$ sha256sum OVMF.amdsev.fd 
ae562fa08aa0ae9419518b02a893c0be01ec39915da71d1ed4faa4ac8638988e  OVMF.amdsev.fd
```

The OVMF image is extracted from the official Debian [ovmf-amdsev](https://packages.debian.org/sid/ovmf-amdsev) package.

### OVMF Update Policy

We will notify customers by email **at least 3 days before** planned OVMF update affecting their confidential VMs. A new binary and SHA-256 hash will be provided. After receiving the notification, customers should update their expected measurement hash in trusted environment to ensure continuity of operations.

## Calculating Expected Measurement Hash

[Virtee](https://virtee.io/)'s [sev-snp-measure](https://github.com/virtee/sev-snp-measure) tool can be used to independently obtain the expected measurement hash for the current environment of your Virtual Machine. It is supposed to be ran within a trusted environment (**not** on the measured VM) and needs to be provided with the [OVMF firmware](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#ovmf-stub) and the [generated UKI image](https://kb.onidel.com/hc/kb/articles/1781519599-amd-sev_snp#generating-uki-image-with-dracut).

```
$ ls  # ensure the required files are present in current directory
linux-7.0.0-22-generic.efi  OVMF.amdsev.fd
$ git clone https://github.com/virtee/sev-snp-measure.git  # clone sev-snp-measure
```

Then run the script and take note of returned hash value. You may need to change the vCPU type (`--vcpu-type` - `EPYC-Milan` for [Premium VPS](https://onidel.com/services/premium-vps), `EPYC-Turin` for [High-Frequency VPS](https://onidel.com/services/high-frequency-vps)) and expected number of vCPUs (`--vcpus`) to match your instance parameters.

```
$ ./sev-snp-measure/sev-snp-measure.py --mode snp --vcpu-type EPYC-Milan --vcpus 1 --ovmf OVMF.amdsev.fd --kernel linux-7.0.0-22-generic.efi
9f04aac41f563c84da348904ab2c2f06ef661426ccf4653e34f5980390c52e420c030607b94d8f584228ce1269bff1b3
```

## Verifying VM Measurement

Just like we did in the [first part of SEV-SNP guide](https://kb.onidel.com/hc/kb/articles/1771943583-sev_snp-verified-boot-and-memory-encryption#attestation), you need to install the [snpguest](https://github.com/virtee/snpguest) tool and perform attestation within the VM. This time however, we will take a closer look at Measurement result and compare it with pre-calculated one from above.

```
$ snpguest report report.bin request-data.txt --random
$ snpguest display report report.bin
Attestation Report:
...

Measurement:
9F 04 AA C4 1F 56 3C 84 DA 34 89 04 AB 2C 2F 06
EF 66 14 26 CC F4 65 3E 34 F5 98 03 90 C5 2E 42
0C 03 06 07 B9 4D 8F 58 42 28 CE 12 69 BF F1 B3
```

Since we now have all the launch parameters required for verified boot (OVMF, UKI, CPU model and features), the hash should be exactly the same as computed using `sev-snp-measure` utility.

# Next Steps

By this point, you have successfully verified the whole trust chain from AMD hardware to the authenticity of core OS components by reproducing the **Measurement Hash**. To automate this verification process, you may use an external (trusted) server for [Remote Attestation](https://lirias.kuleuven.be/retrieve/768950/). This server knows the current proper measurement hash and can take actions based on requests from the client (VM). 

An example implementation could include [enabling networking at initramfs stage](https://kb.onidel.com/hc/kb/articles/1775577479-networking-at-initramfs-stage), then configuring [clevis](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/security_hardening/configuring-automated-unlocking-of-encrypted-volumes-using-policy-based-decryption_security-hardening#automated-unlocking-of-encrypted-volumes-by-using-a-trustee-attestation-server_configuring-automated-unlocking-of-encrypted-volumes-using-policy-based-decryption) with a remote [Trustee Key Broker Service](https://github.com/confidential-containers/trustee/tree/main/kbs) to release [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) decryption key, only when the expected measurement hash is provided. That effectively prevents the VM from booting outside of trusted environment.