devices: fw_cfg: Don't modify kernel header for KVM SEV-SNP guests

For KVM SEV-SNP guests, the VMM should not modify the kernel
boot header before sending it via fw_cfg. The guest firmware is expected
to set fields like type_of_loader itself.

For upcoming measured boot logic for SEV-SNP, modifying `type_of_loader`
causes the kernel hash computed by the VMM to diverge from the hash that
`sev-snp-measure` (and the guest firmware) compute, resulting in a
launch measurement mismatch.

This matches QEMU's behavior, which skips kernel header modifications
for confidential guests so the data sent via fw_cfg matches the
original kernel file provided by the user.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
Ruben Hakobyan
2026-04-29 16:55:16 -07:00
committed by Rob Bradford
parent ffeee2880f
commit 9f1247fe60
2 changed files with 50 additions and 10 deletions

View File

@@ -441,12 +441,17 @@ impl FwCfg {
initramfs: Option<File>,
cmdline: Option<std::ffi::CString>,
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
#[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool,
) -> Result<()> {
if let Some(mem_size) = mem_size {
self.add_e820(mem_size)?;
}
if let Some(kernel) = kernel {
self.add_kernel_data(&kernel)?;
self.add_kernel_data(
&kernel,
#[cfg(target_arch = "x86_64")]
kvm_sev_snp_enabled,
)?;
}
if let Some(cmdline) = cmdline {
self.add_kernel_cmdline(cmdline);
@@ -631,24 +636,37 @@ impl FwCfg {
}
}
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
pub fn add_kernel_data(
&mut self,
file: &File,
#[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool,
) -> Result<()> {
let mut buffer = vec![0u8; size_of::<boot_params>()];
file.read_exact_at(&mut buffer, 0)?;
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
#[cfg(target_arch = "x86_64")]
{
// must set to 4 for backwards compatibility
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
if bp.hdr.setup_sects == 0 {
bp.hdr.setup_sects = 4;
// For SEV-SNP guests on KVM, don't modify the kernel header so the
// bytes sent via fw_cfg match what the VMM hashes for the launch digest.
// The guest firmware handles these fields itself.
if !kvm_sev_snp_enabled {
if bp.hdr.setup_sects == 0 {
bp.hdr.setup_sects = 4;
}
bp.hdr.type_of_loader = 0xff;
}
// wildcard boot loader type
bp.hdr.type_of_loader = 0xff;
}
#[cfg(target_arch = "aarch64")]
let kernel_start = bp.text_offset;
#[cfg(target_arch = "x86_64")]
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
let kernel_start = {
let sects = if bp.hdr.setup_sects == 0 {
4
} else {
bp.hdr.setup_sects
};
(sects as usize + 1) * 512
};
#[cfg(target_arch = "x86_64")]
if kernel_start <= buffer.len() {

View File

@@ -1139,6 +1139,7 @@ impl Vm {
fw_cfg_config: &FwCfgConfig,
device_manager: &Arc<Mutex<DeviceManager>>,
config: &Arc<Mutex<VmConfig>>,
#[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool,
) -> Result<()> {
let mut e820_option: Option<usize> = None;
if fw_cfg_config.e820 {
@@ -1222,6 +1223,8 @@ impl Vm {
initramfs_option,
cmdline_option,
fw_cfg_item_list_option,
#[cfg(target_arch = "x86_64")]
kvm_sev_snp_enabled,
)
.map_err(Error::ErrorPopulatingFwCfg)?;
Ok(())
@@ -2739,7 +2742,26 @@ impl Vm {
.map(|p| p.fw_cfg_config.clone())
.unwrap_or_default()
.ok_or(Error::VmMissingConfig)?;
Self::populate_fw_cfg(&fw_cfg_config, &self.device_manager, &self.config)?;
#[cfg(target_arch = "x86_64")]
let kvm_sev_snp_enabled = {
#[cfg(feature = "sev_snp")]
{
self.config.lock().unwrap().is_sev_snp_enabled()
&& self.hypervisor.hypervisor_type() == hypervisor::HypervisorType::Kvm
}
#[cfg(not(feature = "sev_snp"))]
{
false
}
};
Self::populate_fw_cfg(
&fw_cfg_config,
&self.device_manager,
&self.config,
#[cfg(target_arch = "x86_64")]
kvm_sev_snp_enabled,
)?;
if fw_cfg_config.acpi_tables {
let tpm_enabled = self.config.lock().unwrap().tpm.is_some();