Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include SVSM module as a payload in OVMF firmware image #33

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
*.bin
*.elf
*.swp
*.fd
*~
target/
gen_meta
stage1/stage1
print-meta
ovmf/release
ovmf/debug
.idea/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "edk2"]
path = edk2
url = https://github.com/coconut-svsm/edk2.git
branch = svsm
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"git.autoRepositoryDetection": false,
"git.ignoredRepositories": ["edk2", "openssl", "berkeley-softfloat-3", "cmocka", "oniguruma", "brotli", "jannson", "googletest", "boringssl", "pyca-cryptography", "wycheproof", "krb5"],
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
}
61 changes: 24 additions & 37 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,43 +107,19 @@ Building the guest firmware
A special OVMF build is required to launch a guest on top of the
COCONUT-SVSM. The changes also build on the EDK2 patches from AMD for
linux-svsm. But these changes were re-based and enhanced to support the
COCONUT-SVSM code base. To build the OVMF binary for the guest, checkout
this repository:
COCONUT-SVSM code base.

```
$ git clone https://github.com/coconut-svsm/edk2.git
$ cd edk2/
$ git checkout svsm
$ git submodule init
$ git submodule update
```
The source code for the special OVMF build is included as a submodule of
the COCONUT-SVSM repository and is built along with the COCONUT-SVSM
using the provided Makefile so no extra steps are required to build OVMF.

Also make sure to have the build dependencies for OVMF installed. On
openSUSE you can do this by:
However, the build dependencies for OVMF must be installed prior to building
COCONUT-SVSM. On openSUSE you can do this by:

```
$ sudo zypper si -d qemu-ovmf-x86_64
```

Then go back to the EDK2 source directory and follow these steps to
build the firmware:

```
$ export PYTHON3_ENABLE=TRUE
$ export PYTHON_COMMAND=python3
$ make -j16 -C BaseTools/
$ source ./edksetup.sh
$ build -a X64 -b DEBUG -t GCC5 -D DEBUG_ON_SERIAL_PORT -D DEBUG_VERBOSE -p OvmfPkg/OvmfPkgX64.dsc
```

This will build the OVMF code and variable binaries to use with QEMU.
You can copy them to a known location after the build is complete:

```
$ cp Build/OvmfX64/DEBUG_GCC5/FV/OVMF_CODE.fd /path/to/firmware/
$ cp Build/OvmfX64/DEBUG_GCC5/FV/OVMF_VARS.fd /path/to/firmware/
```

Preparing the guest image
-------------------------

Expand Down Expand Up @@ -177,6 +153,7 @@ Then checkout the SVSM repository and build the SVSM binary:

```
$ git clone https://github.com/coconut-svsm/svsm
$ git submodule update --init --recursive
$ cd svsm
```

Expand All @@ -192,9 +169,20 @@ to get a debug build of the SVSM or
$ make RELEASE=1
```

to build the SVSM with the release target. When the build is finished
there is the ```svsm.bin``` file in the top-directory of the repository. This
is the file which needs to be passed to QEMU.
to build the SVSM with the release target.


When the build is finished there will be the file ```svsm.bin``` in the
top-directory of the repository. This contains the binary image for the SVSM
module. However, the makefile also builds a firmware volume that contains the
SVSM module embedded alongside the special build of OVMF. This firmware volume is
the file which needs to be passed to QEMU. The firmware volumes for debug and
release can be found in their respective directories:

```
$ cp ovmf/debug/* /path/to/firmware/
$ cp ovmf/release/* /path/to/firmware/
```

The project also contains a number of unit-tests which can be run by

Expand Down Expand Up @@ -226,13 +214,13 @@ run SEV-SNP guests. An ```sev-snp-guest``` object needs to be defined to
enable SEV-SNP protection for the guest. The 'svsm=on' parameter makes
QEMU reserve a small amount of guest memory for the SVSM.

Further, the OVMF binaries and the SVSM binary need to be passed to
QEMU. This happens via pflash drives:
Further, the OVMF binaries with embedded SVSM binary need to be passed to
QEMU. This happens via pflash drives where the standard OVMF firmware is
replaced with the version built using the COCONUT-SVSM Makefile:

```
-drive if=pflash,format=raw,unit=0,file=/path/to/firmware/OVMF_CODE.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=/path/to/firmware/OVMF_VARS.fd,snapshot=on \
-drive if=pflash,format=raw,unit=2,file=/path/to/svsm/svsm.bin,readonly=on \
```

With these extensions QEMU will launch an SEV-SNP protected guest with
Expand All @@ -251,7 +239,6 @@ $ sudo $HOME/bin/qemu-svsm/bin/qemu-system-x86_64 \
-no-reboot \
-drive if=pflash,format=raw,unit=0,file=/path/to/firmware/OVMF_CODE.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=/path/to/firmware/OVMF_VARS.fd,snapshot=on \
-drive if=pflash,format=raw,unit=2,file=/path/to/svsm/svsm.bin,readonly=on \
-netdev user,id=vmnic -device e1000,netdev=vmnic,romfile= \
-drive file=/path/to/guest/image.qcow2,if=none,id=disk0,format=qcow2,snapshot=off \
-device virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=on \
Expand Down
17 changes: 14 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
ifdef RELEASE
TARGET_PATH="release"
CARGO_ARGS="--release"
OVMF_BUILD_ARGS=
OVMF_BUILD_DIR=edk2/Build/OvmfSvsmX64/RELEASE_GCC5/FV
OVMF_OUTPUT_DIR=ovmf/release
else
TARGET_PATH="debug"
CARGO_ARGS=
OVMF_BUILD_ARGS=debug
OVMF_BUILD_DIR=edk2/Build/OvmfSvsmX64/DEBUG_GCC5/FV
OVMF_OUTPUT_DIR=ovmf/debug
endif

STAGE2_ELF = "target/svsm-target/${TARGET_PATH}/stage2"
Expand All @@ -12,7 +18,7 @@ FS_FILE ?= none

STAGE1_OBJS = stage1/stage1.o stage1/reset.o

all: svsm.bin
all: svsm.bin ovmf

test:
cd src/
Expand Down Expand Up @@ -52,8 +58,13 @@ stage1/stage1: ${STAGE1_OBJS}
svsm.bin: stage1/stage1
objcopy -O binary $< $@

ovmf: svsm.bin
scripts/build-ovmf.sh ${OVMF_BUILD_ARGS}
mkdir -p ${OVMF_OUTPUT_DIR}
cp ${OVMF_BUILD_DIR}/OVMF_CODE.fd ${OVMF_BUILD_DIR}/OVMF_VARS.fd ${OVMF_BUILD_DIR}/OVMF.fd ${OVMF_OUTPUT_DIR}

clean:
cargo clean
rm -f stage1/stage2.bin svsm.bin stage1/meta.bin ${STAGE1_OBJS} gen_meta
rm -f stage1/stage2.bin svsm.bin stage1/meta.bin ${STAGE1_OBJS} gen_meta ${OVMF_OUTPUT_DIR}/*.fd

.PHONY: stage1/stage2.bin stage1/kernel.elf svsm.bin clean stage1/svsm-fs.bin
.PHONY: stage1/stage2.bin stage1/kernel.elf svsm.bin clean ovmf stage1/svsm-fs.bin
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ any way:
* vTPM emulation
* Attestation support
* Persistency layer (needed for TPM and others)
* Carry FW as payload of SVSM to make the SVSM binary a drop-in
replacement for the FW when launching QEMU
* Live migration

Acknowledgments
Expand Down
1 change: 1 addition & 0 deletions edk2
Submodule edk2 added at e824ed
34 changes: 34 additions & 0 deletions scripts/build-ovmf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

if [[ $1 == "debug" ]]; then
LOG_FILE="ovmf_debug.build"
else
LOG_FILE="ovmf_release.build"
fi
rm -f $LOG_FILE

pushd $SCRIPT_DIR/../edk2

export PYTHON3_ENABLE=TRUE
export PYTHON_COMMAND=python3

# First build requires some initialisation
if [ ! -d "$SCRIPT_DIR/../edk2/BaseTools/Source/C/bin" ]; then
echo "Building OVMF base tools."
make -C BaseTools -j $(nproc) > $LOG_FILE 2> $LOG_FILE
fi

unset WORKSPACE
source edksetup.sh --reconfig >> $LOG_FILE 2>> $LOG_FILE

if [[ $1 == "debug" ]]; then
echo "Building OVMF(debug). Build output logged to edk2/$LOG_FILE."
build -a X64 -b DEBUG -t GCC5 -D DEBUG_ON_SERIAL_PORT -D DEBUG_VERBOSE -D SVSM_MODULE_FILE=../svsm.bin -p OvmfPkg/OvmfPkgSvsmX64.dsc >> $LOG_FILE 2>> $LOG_FILE
else
echo "Building OVMF(release). Build output logged to edk2/$LOG_FILE."
build -a X64 -b RELEASE -t GCC5 -D SVSM_MODULE_FILE=../svsm.bin -p OvmfPkg/OvmfPkgSvsmX64.dsc >> $LOG_FILE 2>> $LOG_FILE
fi
popd
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ pub mod elf;
pub mod error;
pub mod fs;
pub mod fw_cfg;
pub mod fw_meta;
pub mod io;
pub mod kernel_launch;
pub mod locking;
pub mod mm;
pub mod ovmf;
pub mod requests;
pub mod serial;
pub mod sev;
Expand Down
13 changes: 13 additions & 0 deletions src/ovmf/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
//
// Copyright (c) 2023 SUSE LLC
//
// Author: Roy Hopkins <[email protected]>
//
// vim: ts=4 sw=4 et

pub mod ovmf_fw;
pub mod ovmf_meta;

pub use ovmf_fw::OvmfFw;
pub use ovmf_meta::{parse_ovmf_meta_data, print_ovmf_meta, validate_ovmf_memory, SevOVMFMetaData};
85 changes: 85 additions & 0 deletions src/ovmf/ovmf_fw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2022-2023 SUSE LLC
//
// Author: Roy Hopkins <[email protected]>

use crate::address::PhysAddr;
use crate::cpu::percpu::this_cpu_mut;
use crate::error::SvsmError;
use crate::requests::update_mappings;
use crate::types::GUEST_VMPL;

use super::{parse_ovmf_meta_data, print_ovmf_meta, validate_ovmf_memory, SevOVMFMetaData};

/*
* The EDK2 OvmfSvsmX64 platform creates a reset vector with the entry point and metadata
* for the SVSM module at 4K below 4GB and the OVMF entry point and metadata at 8K below
* 4GB.
*/
const OVMF_ENTRY: u64 = 0xffffeff0;

pub struct OvmfFw {
ovmf_meta: SevOVMFMetaData,
}

impl OvmfFw {
pub fn new() -> Self {
let ovmf_meta = parse_ovmf_meta_data()
.unwrap_or_else(|e| panic!("Failed to parse OVMF FW SEV meta-data: {:#?}", e));
print_ovmf_meta(&ovmf_meta);

if let Err(e) = validate_ovmf_memory(&ovmf_meta) {
panic!("Failed to validate firmware memory: {:#?}", e);
}

Self { ovmf_meta }
}

pub fn tables(self: &Self) -> (PhysAddr, PhysAddr, PhysAddr) {
let cpuid_page = match self.ovmf_meta.cpuid_page {
Some(addr) => addr,
None => panic!("OVMF FW does not specify CPUID_PAGE location"),
};

let secrets_page = match self.ovmf_meta.secrets_page {
Some(addr) => addr,
None => panic!("OVMF FW does not specify SECRETS_PAGE location"),
};

let caa_page = match self.ovmf_meta.caa_page {
Some(addr) => addr,
None => panic!("OVMF FW does not specify CAA_PAGE location"),
};
(cpuid_page, secrets_page, caa_page)
}

pub fn prepare_launch(self: &Self) -> Result<(), SvsmError> {
let caa = self.ovmf_meta.caa_page.unwrap();
let cpu = this_cpu_mut();
cpu.set_reset_ip(OVMF_ENTRY);

cpu.alloc_guest_vmsa()?;
cpu.update_guest_caa(caa);
update_mappings()?;

Ok(())
}

pub fn launch(self: &Self) -> Result<(), SvsmError> {
let vmsa_pa = this_cpu_mut().guest_vmsa_ref().vmsa_phys().unwrap();
let vmsa = this_cpu_mut().guest_vmsa();

log::info!("VMSA PA: {:#x}", vmsa_pa);

vmsa.enable();
let sev_features = vmsa.sev_features;

log::info!("Launching Firmware");
this_cpu_mut()
.ghcb()
.ap_create(vmsa_pa, 0, GUEST_VMPL as u64, sev_features)?;

Ok(())
}
}
Loading