mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: Introduce kernel hashes measured boot
This introduces the kernel hashes measured boot table into cloud hypervisor if a cmdline and kernel is passed into an SEV-SNP CVM, incorporating a kernel/cmdline/optional initrd into a memory page that is measured into the launch digest of a SEV-SNP CVM. If both --kernel and --cmdline are not provided, we do not insert this data page Signed-off-by: Kevin Hui <kevinhui@meta.com>
This commit is contained in:
Generated
+62
@@ -343,6 +343,15 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
version = "1.6.2"
|
version = "1.6.2"
|
||||||
@@ -482,6 +491,12 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -515,6 +530,15 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.23.0"
|
version = "0.23.0"
|
||||||
@@ -599,6 +623,17 @@ dependencies = [
|
|||||||
"thousands",
|
"thousands",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"const-oid",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -992,6 +1027,15 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hybrid-array"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hypervisor"
|
name = "hypervisor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2010,6 +2054,17 @@ dependencies = [
|
|||||||
name = "serial_buffer"
|
name = "serial_buffer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -2244,6 +2299,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uds_windows"
|
name = "uds_windows"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -2556,6 +2617,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serial_buffer",
|
"serial_buffer",
|
||||||
|
"sha2",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ flume = "0.12.0"
|
|||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
libc = "0.2.186"
|
libc = "0.2.186"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
|
sha2 = "0.11.0"
|
||||||
signal-hook = "0.4.4"
|
signal-hook = "0.4.4"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
uuid = { version = "1.23.1" }
|
uuid = { version = "1.23.1" }
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ seccompiler = { workspace = true }
|
|||||||
serde = { workspace = true, features = ["derive", "rc"] }
|
serde = { workspace = true, features = ["derive", "rc"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serial_buffer = { path = "../serial_buffer" }
|
serial_buffer = { path = "../serial_buffer" }
|
||||||
|
sha2 = { workspace = true }
|
||||||
signal-hook = { workspace = true }
|
signal-hook = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracer = { path = "../tracer" }
|
tracer = { path = "../tracer" }
|
||||||
|
|||||||
+163
-8
@@ -39,6 +39,13 @@ use crate::cpu::CpuManager;
|
|||||||
use crate::igvm::loader::Loader;
|
use crate::igvm::loader::Loader;
|
||||||
use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType};
|
use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType};
|
||||||
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
|
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
use crate::sev::{MeasuredBootInfo, SEV_HASH_BLOCK_ADDRESS, SEV_HASH_BLOCK_SIZE};
|
||||||
|
|
||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
const ISOLATED_PAGE_SHIFT: u32 = 12;
|
const ISOLATED_PAGE_SHIFT: u32 = 12;
|
||||||
@@ -96,6 +103,29 @@ pub enum Error {
|
|||||||
MissingIgvm,
|
MissingIgvm,
|
||||||
#[error("Error applying VMSA to vCPU registers: {0}")]
|
#[error("Error applying VMSA to vCPU registers: {0}")]
|
||||||
SetVmsa(#[source] crate::cpu::Error),
|
SetVmsa(#[source] crate::cpu::Error),
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
#[error("Error building SEV-SNP measured boot hash block")]
|
||||||
|
MeasuredBoot(#[source] vmm_sys_util::errno::Error),
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
#[error(
|
||||||
|
"igvmfile inserts unmeasured parameter area [0x{region_start:x}, 0x{region_end:x}) over SEV-SNP kernel hashes region [0x{hash_start:x}, 0x{hash_end:x})"
|
||||||
|
)]
|
||||||
|
MeasuredBootHashOverlap {
|
||||||
|
region_start: u64,
|
||||||
|
region_end: u64,
|
||||||
|
hash_start: u64,
|
||||||
|
hash_end: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// KVM SNP page types — linux/arch/x86/include/uapi/asm/sev-guest.h
|
// KVM SNP page types — linux/arch/x86/include/uapi/asm/sev-guest.h
|
||||||
@@ -225,6 +255,13 @@ pub fn load_igvm(
|
|||||||
memory_manager: Arc<Mutex<MemoryManager>>,
|
memory_manager: Arc<Mutex<MemoryManager>>,
|
||||||
cpu_manager: Arc<Mutex<CpuManager>>,
|
cpu_manager: Arc<Mutex<CpuManager>>,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
measured_boot: Option<MeasuredBootInfo>,
|
||||||
#[cfg(feature = "sev_snp")] host_data: &Option<String>,
|
#[cfg(feature = "sev_snp")] host_data: &Option<String>,
|
||||||
) -> Result<Box<IgvmLoadedInfo>, Error> {
|
) -> Result<Box<IgvmLoadedInfo>, Error> {
|
||||||
let hypervisor_type = cpu_manager.lock().unwrap().hypervisor_type();
|
let hypervisor_type = cpu_manager.lock().unwrap().hypervisor_type();
|
||||||
@@ -275,6 +312,45 @@ pub fn load_igvm(
|
|||||||
let mut loader = Loader::new(memory);
|
let mut loader = Loader::new(memory);
|
||||||
|
|
||||||
let mut parameter_areas: HashMap<u32, ParameterAreaState> = HashMap::new();
|
let mut parameter_areas: HashMap<u32, ParameterAreaState> = HashMap::new();
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
let measured_boot_hash_block = if hypervisor_type == HypervisorType::Kvm {
|
||||||
|
measured_boot
|
||||||
|
.as_ref()
|
||||||
|
.map(|measured_boot| {
|
||||||
|
measured_boot
|
||||||
|
.build_hash_block()
|
||||||
|
.map_err(Error::MeasuredBoot)
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
let measured_boot_hash_page_base = SEV_HASH_BLOCK_ADDRESS / HV_PAGE_SIZE;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
let measured_boot_hash_offset = (SEV_HASH_BLOCK_ADDRESS % HV_PAGE_SIZE) as usize;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
let mut measured_boot_hash_block_inserted = measured_boot_hash_block.is_none();
|
||||||
|
|
||||||
for header in igvm_file.directives() {
|
for header in igvm_file.directives() {
|
||||||
debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask);
|
debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask);
|
||||||
@@ -418,6 +494,34 @@ pub fn load_igvm(
|
|||||||
.map_err(Error::Loader)?;
|
.map_err(Error::Loader)?;
|
||||||
imported_page = true;
|
imported_page = true;
|
||||||
}
|
}
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
if let Some(hash_block) = measured_boot_hash_block.as_ref().filter(|_| {
|
||||||
|
!imported_page && gpa / HV_PAGE_SIZE == measured_boot_hash_page_base
|
||||||
|
}) {
|
||||||
|
let mut page = if data.is_empty() {
|
||||||
|
vec![0; HV_PAGE_SIZE as usize]
|
||||||
|
} else {
|
||||||
|
let mut page = data.clone();
|
||||||
|
page.resize(HV_PAGE_SIZE as usize, 0);
|
||||||
|
page
|
||||||
|
};
|
||||||
|
// If a data page from the bootloader contains this range,
|
||||||
|
// we need to ensure that the measured boot table is injected
|
||||||
|
// prior to importing the pages
|
||||||
|
page[measured_boot_hash_offset
|
||||||
|
..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE]
|
||||||
|
.copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]);
|
||||||
|
loader
|
||||||
|
.import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, &page)
|
||||||
|
.map_err(Error::Loader)?;
|
||||||
|
measured_boot_hash_block_inserted = true;
|
||||||
|
imported_page = true;
|
||||||
|
}
|
||||||
if !imported_page {
|
if !imported_page {
|
||||||
loader
|
loader
|
||||||
.import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data)
|
.import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data)
|
||||||
@@ -579,14 +683,37 @@ pub fn load_igvm(
|
|||||||
.get_mut(parameter_area_index)
|
.get_mut(parameter_area_index)
|
||||||
.expect("igvmfile should be valid");
|
.expect("igvmfile should be valid");
|
||||||
match area {
|
match area {
|
||||||
ParameterAreaState::Allocated { data, max_size } => loader
|
ParameterAreaState::Allocated { data, max_size } => {
|
||||||
.import_pages(
|
#[cfg(all(
|
||||||
gpa / HV_PAGE_SIZE,
|
feature = "kvm",
|
||||||
*max_size / HV_PAGE_SIZE,
|
feature = "sev_snp",
|
||||||
BootPageAcceptance::ExclusiveUnmeasured,
|
feature = "fw_cfg",
|
||||||
data,
|
target_arch = "x86_64"
|
||||||
)
|
))]
|
||||||
.map_err(Error::Loader)?,
|
if measured_boot_hash_block.is_some() {
|
||||||
|
let region_end = *gpa + *max_size;
|
||||||
|
let hash_end = SEV_HASH_BLOCK_ADDRESS + SEV_HASH_BLOCK_SIZE as u64;
|
||||||
|
if *gpa <= SEV_HASH_BLOCK_ADDRESS && hash_end <= region_end {
|
||||||
|
// In the case of parameter being inserted where the kernel hashes table lies,
|
||||||
|
// we should reject the igvmfile since it would interfere with the launch digest
|
||||||
|
return Err(Error::MeasuredBootHashOverlap {
|
||||||
|
region_start: *gpa,
|
||||||
|
region_end,
|
||||||
|
hash_start: SEV_HASH_BLOCK_ADDRESS,
|
||||||
|
hash_end,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader
|
||||||
|
.import_pages(
|
||||||
|
gpa / HV_PAGE_SIZE,
|
||||||
|
*max_size / HV_PAGE_SIZE,
|
||||||
|
BootPageAcceptance::ExclusiveUnmeasured,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.map_err(Error::Loader)?;
|
||||||
|
}
|
||||||
ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
|
ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
|
||||||
}
|
}
|
||||||
*area = ParameterAreaState::Inserted;
|
*area = ParameterAreaState::Inserted;
|
||||||
@@ -605,6 +732,34 @@ pub fn load_igvm(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
if let Some(hash_block) = measured_boot_hash_block.as_ref()
|
||||||
|
&& !measured_boot_hash_block_inserted
|
||||||
|
{
|
||||||
|
// Fallback to adding kernel hashes after importing if the page data wasn't found previously
|
||||||
|
let mut page = vec![0u8; HV_PAGE_SIZE as usize];
|
||||||
|
page[measured_boot_hash_offset..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE]
|
||||||
|
.copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]);
|
||||||
|
loader
|
||||||
|
.import_pages(
|
||||||
|
measured_boot_hash_page_base,
|
||||||
|
1,
|
||||||
|
BootPageAcceptance::Exclusive,
|
||||||
|
&page,
|
||||||
|
)
|
||||||
|
.map_err(Error::Loader)?;
|
||||||
|
gpas.push(GpaPages {
|
||||||
|
gpa: measured_boot_hash_page_base * HV_PAGE_SIZE,
|
||||||
|
page_type: page_types.normal,
|
||||||
|
page_size: page_types.isolated_page_size_4kb,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
if sev_snp_enabled {
|
if sev_snp_enabled {
|
||||||
memory_manager
|
memory_manager
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ pub mod migration_transport;
|
|||||||
mod pci_segment;
|
mod pci_segment;
|
||||||
pub mod seccomp_filters;
|
pub mod seccomp_filters;
|
||||||
mod serial_manager;
|
mod serial_manager;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
pub(crate) mod sev;
|
||||||
mod sigwinch_listener;
|
mod sigwinch_listener;
|
||||||
mod sync_utils;
|
mod sync_utils;
|
||||||
mod uffd;
|
mod uffd;
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use core::mem::size_of;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||||
|
use std::os::unix::fs::FileExt;
|
||||||
|
|
||||||
|
use linux_loader::bootparam::boot_params;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use uuid::{Uuid, uuid};
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
use vmm_sys_util::errno;
|
||||||
|
use zerocopy::{Immutable, IntoBytes};
|
||||||
|
|
||||||
|
pub(crate) type Result<T> = std::result::Result<T, errno::Error>;
|
||||||
|
|
||||||
|
// https://github.com/tianocore/edk2/blob/f98662c5e35b6ab60f46ee4350fa0e6eab0497cf/OvmfPkg/Include/Fdf/MemFd.fdf.inc#L89-L93
|
||||||
|
pub const SEV_HASH_BLOCK_ADDRESS: u64 = 0x10c00;
|
||||||
|
pub const SEV_HASH_BLOCK_SIZE: usize = 0x400;
|
||||||
|
const SHA256_HASH_SIZE: usize = 32;
|
||||||
|
|
||||||
|
// Measured hashes table definitions. These match the definitions found in
|
||||||
|
// QEMU's implementation: https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L68
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(IntoBytes, Immutable)]
|
||||||
|
struct SevHashTableEntry {
|
||||||
|
pub guid: [u8; 16],
|
||||||
|
pub len: u16,
|
||||||
|
pub hash: [u8; SHA256_HASH_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(IntoBytes, Immutable)]
|
||||||
|
struct SevHashTable {
|
||||||
|
pub guid: [u8; 16],
|
||||||
|
pub len: u16,
|
||||||
|
pub cmdline: SevHashTableEntry,
|
||||||
|
pub initrd: SevHashTableEntry,
|
||||||
|
pub kernel: SevHashTableEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEV_HASH_TABLE_PADDING: usize =
|
||||||
|
size_of::<SevHashTable>().next_multiple_of(16) - size_of::<SevHashTable>();
|
||||||
|
|
||||||
|
#[derive(IntoBytes, Immutable)]
|
||||||
|
pub struct PaddedSevHashTable {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
hash_table: SevHashTable,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
padding: [u8; SEV_HASH_TABLE_PADDING],
|
||||||
|
}
|
||||||
|
|
||||||
|
// These GUIDs are defined in both EDK2 and QEMU:
|
||||||
|
// https://github.com/tianocore/edk2/blob/master/OvmfPkg/AmdSev/BlobVerifierLibSevHashes/BlobVerifierSevHashes.c#L36-L43
|
||||||
|
// https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L2344-2360
|
||||||
|
const SEV_HASH_TABLE_GUID: Uuid = uuid!("9438d606-4f22-4cc9-b479-a793d411fd21");
|
||||||
|
const SEV_KERNEL_HASH_GUID: Uuid = uuid!("4de79437-abd2-427f-b835-d5b172d2045b");
|
||||||
|
const SEV_INITRD_HASH_GUID: Uuid = uuid!("44baf731-3a2f-4bd7-9af1-41e29169781d");
|
||||||
|
const SEV_CMDLINE_HASH_GUID: Uuid = uuid!("97d02dd8-bd20-4c94-aa78-e7714d36ab2a");
|
||||||
|
|
||||||
|
pub struct MeasuredBootInfo {
|
||||||
|
pub kernel: File,
|
||||||
|
// QEMU also makes initrd optional in the hash table
|
||||||
|
pub initramfs: Option<File>,
|
||||||
|
pub cmdline: CString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasuredBootInfo {
|
||||||
|
fn measured_boot_io(err: &io::Error) -> errno::Error {
|
||||||
|
errno::Error::new(err.raw_os_error().unwrap_or(libc::EINVAL))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha256_reader<R: Read>(mut reader: R) -> Result<[u8; 32]> {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
let mut chunk = [0u8; 8192];
|
||||||
|
loop {
|
||||||
|
let bytes_read = reader
|
||||||
|
.read(&mut chunk)
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&chunk[..bytes_read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hasher.finalize().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_hash_block(&self) -> Result<[u8; SEV_HASH_BLOCK_SIZE]> {
|
||||||
|
// Current tooling appends a NUL byte at the end of cmdlines:
|
||||||
|
// https://github.com/virtee/sev-snp-measure/blob/main/sevsnpmeasure/sev_hashes.py#L71-L74
|
||||||
|
let cmdline_digest: [u8; 32] = Sha256::digest(self.cmdline.as_bytes_with_nul()).into();
|
||||||
|
|
||||||
|
// If no initrd is provided, we will simply hash over an empty buffer, mimicking
|
||||||
|
// QEMU's behavior: https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L2387
|
||||||
|
let initrd_digest: [u8; 32] = if let Some(initramfs) = &self.initramfs {
|
||||||
|
let mut initramfs = initramfs
|
||||||
|
.try_clone()
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
initramfs
|
||||||
|
.seek(SeekFrom::Start(0))
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
Self::sha256_reader(initramfs)?
|
||||||
|
} else {
|
||||||
|
Self::sha256_reader(Cursor::new([]))?
|
||||||
|
};
|
||||||
|
|
||||||
|
// The kernel components are split up into a setup section and the actual kernel data,
|
||||||
|
// so split them up to avoid double counting, assuming this is a bzImage Linux kernel
|
||||||
|
let mut setup_header = vec![0u8; size_of::<boot_params>()];
|
||||||
|
self.kernel
|
||||||
|
.read_exact_at(&mut setup_header, 0)
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
|
||||||
|
let kernel_start = {
|
||||||
|
// Matches QEMU's way of finding the kernel start/setup start
|
||||||
|
// https://gitlab.com/qemu-project/qemu/-/blob/master/hw/i386/x86-common.c#L903
|
||||||
|
let bp = boot_params::from_mut_slice(&mut setup_header).unwrap();
|
||||||
|
let setup_sects = if bp.hdr.setup_sects == 0 {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
bp.hdr.setup_sects
|
||||||
|
};
|
||||||
|
(u64::from(setup_sects) + 1) * 512
|
||||||
|
};
|
||||||
|
let setup_len = kernel_start as usize;
|
||||||
|
let mut setup_data = vec![0u8; setup_len];
|
||||||
|
if setup_len <= setup_header.len() {
|
||||||
|
setup_data.copy_from_slice(&setup_header[..setup_len]);
|
||||||
|
} else {
|
||||||
|
setup_data[..setup_header.len()].copy_from_slice(&setup_header);
|
||||||
|
self.kernel
|
||||||
|
.read_exact_at(
|
||||||
|
&mut setup_data[setup_header.len()..],
|
||||||
|
setup_header.len() as u64,
|
||||||
|
)
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut kernel = self
|
||||||
|
.kernel
|
||||||
|
.try_clone()
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
kernel
|
||||||
|
.seek(SeekFrom::Start(kernel_start))
|
||||||
|
.map_err(|e| Self::measured_boot_io(&e))?;
|
||||||
|
let kernel_digest = Self::sha256_reader(Cursor::new(setup_data).chain(kernel))?;
|
||||||
|
|
||||||
|
let table = PaddedSevHashTable {
|
||||||
|
hash_table: SevHashTable {
|
||||||
|
guid: SEV_HASH_TABLE_GUID.to_bytes_le(),
|
||||||
|
len: size_of::<SevHashTable>() as u16,
|
||||||
|
cmdline: SevHashTableEntry {
|
||||||
|
guid: SEV_CMDLINE_HASH_GUID.to_bytes_le(),
|
||||||
|
len: size_of::<SevHashTableEntry>() as u16,
|
||||||
|
hash: cmdline_digest,
|
||||||
|
},
|
||||||
|
initrd: SevHashTableEntry {
|
||||||
|
guid: SEV_INITRD_HASH_GUID.to_bytes_le(),
|
||||||
|
len: size_of::<SevHashTableEntry>() as u16,
|
||||||
|
hash: initrd_digest,
|
||||||
|
},
|
||||||
|
kernel: SevHashTableEntry {
|
||||||
|
guid: SEV_KERNEL_HASH_GUID.to_bytes_le(),
|
||||||
|
len: size_of::<SevHashTableEntry>() as u16,
|
||||||
|
hash: kernel_digest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
padding: [0; SEV_HASH_TABLE_PADDING],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hash_block = [0u8; SEV_HASH_BLOCK_SIZE];
|
||||||
|
// The remainder of the page is zeroed to ensure measurements are reliably calculated
|
||||||
|
hash_block[..size_of::<PaddedSevHashTable>()].copy_from_slice(table.as_bytes());
|
||||||
|
Ok(hash_block)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,6 +105,13 @@ use crate::migration::get_vm_snapshot;
|
|||||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
||||||
use crate::migration::url_to_file;
|
use crate::migration::url_to_file;
|
||||||
use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path};
|
use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path};
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
use crate::sev::MeasuredBootInfo;
|
||||||
#[cfg(feature = "fw_cfg")]
|
#[cfg(feature = "fw_cfg")]
|
||||||
use crate::vm_config::FwCfgConfig;
|
use crate::vm_config::FwCfgConfig;
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
@@ -1545,6 +1552,13 @@ impl Vm {
|
|||||||
igvm_file: IgvmFile,
|
igvm_file: IgvmFile,
|
||||||
memory_manager: Arc<Mutex<MemoryManager>>,
|
memory_manager: Arc<Mutex<MemoryManager>>,
|
||||||
cpu_manager: Arc<Mutex<cpu::CpuManager>>,
|
cpu_manager: Arc<Mutex<cpu::CpuManager>>,
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
measured_boot: Option<MeasuredBootInfo>,
|
||||||
#[cfg(feature = "sev_snp")] host_data: &Option<String>,
|
#[cfg(feature = "sev_snp")] host_data: &Option<String>,
|
||||||
) -> Result<EntryPoint> {
|
) -> Result<EntryPoint> {
|
||||||
// Only reserve bootloader/VMSA regions for KVM + SEV-SNP; other hypervisors
|
// Only reserve bootloader/VMSA regions for KVM + SEV-SNP; other hypervisors
|
||||||
@@ -1561,6 +1575,13 @@ impl Vm {
|
|||||||
memory_manager,
|
memory_manager,
|
||||||
cpu_manager.clone(),
|
cpu_manager.clone(),
|
||||||
"",
|
"",
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
measured_boot,
|
||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
host_data,
|
host_data,
|
||||||
)
|
)
|
||||||
@@ -1655,10 +1676,61 @@ impl Vm {
|
|||||||
if payload.igvm.is_some() {
|
if payload.igvm.is_some() {
|
||||||
let igvm_file =
|
let igvm_file =
|
||||||
igvm_file.ok_or(Error::IgvmLoad(igvm_loader::Error::MissingIgvm))?;
|
igvm_file.ok_or(Error::IgvmLoad(igvm_loader::Error::MissingIgvm))?;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
let measured_boot = if let (true, Some(kernel_path), Some(_cmdline)) = (
|
||||||
|
payload
|
||||||
|
.fw_cfg_config
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|cfg| cfg.kernel && cfg.cmdline),
|
||||||
|
payload.kernel.as_ref(),
|
||||||
|
payload.cmdline.as_ref(),
|
||||||
|
) {
|
||||||
|
let kernel = File::open(kernel_path).map_err(Error::KernelFile)?;
|
||||||
|
let initramfs = if payload
|
||||||
|
.fw_cfg_config
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|cfg| cfg.initramfs)
|
||||||
|
{
|
||||||
|
payload
|
||||||
|
.initramfs
|
||||||
|
.as_ref()
|
||||||
|
.map(File::open)
|
||||||
|
.transpose()
|
||||||
|
.map_err(Error::InitramfsFile)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Must byte-match the cmdline that populate_fw_cfg sends to the guest,
|
||||||
|
// otherwise the launch measurement will diverge.
|
||||||
|
let cmdline = Self::generate_cmdline(payload)?
|
||||||
|
.as_cstring()
|
||||||
|
.map_err(Error::CmdLineCreate)?;
|
||||||
|
|
||||||
|
Some(MeasuredBootInfo {
|
||||||
|
kernel,
|
||||||
|
initramfs,
|
||||||
|
cmdline,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
return Self::load_igvm(
|
return Self::load_igvm(
|
||||||
igvm_file,
|
igvm_file,
|
||||||
memory_manager,
|
memory_manager,
|
||||||
cpu_manager,
|
cpu_manager,
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "kvm",
|
||||||
|
feature = "sev_snp",
|
||||||
|
feature = "fw_cfg",
|
||||||
|
target_arch = "x86_64"
|
||||||
|
))]
|
||||||
|
measured_boot,
|
||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
&payload.host_data,
|
&payload.host_data,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user