Skip to content
Open
3 changes: 3 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,7 @@ dependencies = [
"guestmem",
"guid",
"hcl_compat_uefi_nvram_storage",
"hyperv_secure_boot_templates",
"inspect",
"jiff",
"local_clock",
Expand Down Expand Up @@ -3259,7 +3260,9 @@ dependencies = [
"thiserror 2.0.16",
"tracing",
"ucs2 0.0.0",
"uefi_nvram_specvars",
"uefi_nvram_storage",
"uefi_specs",
"vmcore",
"wchar",
"zerocopy",
Expand Down
4 changes: 4 additions & 0 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2562,6 +2562,7 @@ async fn new_underhill_vm(
}
}
};
let base_secure_boot_template_vars = base_vars.clone();

// check if vmgs includes custom UEFI JSON
let custom_uefi_json_data = if let Some(vmgs_client) = vmgs_client.as_ref() {
Expand All @@ -2574,6 +2575,7 @@ async fn new_underhill_vm(
} else {
None
};
let custom_uefi_config_present = custom_uefi_json_data.is_some();

// obtain the final custom uefi vars by applying the delta onto
// the base vars
Expand All @@ -2599,7 +2601,9 @@ async fn new_underhill_vm(
};

let config = firmware_uefi_resources::UefiConfig {
base_secure_boot_template_vars,
custom_uefi_vars,
custom_uefi_config_present,
secure_boot: dps.general.secure_boot_enabled,
initial_generation_id,
use_mmio: cfg!(not(guest_arch = "x86_64")),
Expand Down
16 changes: 13 additions & 3 deletions openvmm/openvmm_entry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,7 @@ async fn vm_config_from_command_line(
);
}

let custom_uefi_vars = {
let (base_secure_boot_template_vars, custom_uefi_vars, custom_uefi_config_present) = {
use firmware_uefi_custom_vars::CustomVars;

// load base vars from specified template, or use an empty set of base
Expand All @@ -1199,22 +1199,30 @@ async fn vm_config_from_command_line(
},
None => CustomVars::default(),
};
let base_secure_boot_template_vars = base_vars.clone();

// TODO: fallback to VMGS read if no command line flag was given

let custom_uefi_json_data = match &opt.custom_uefi_json {
Some(file) => Some(fs_err::read(file).context("opening custom uefi json file")?),
None => None,
};
let custom_uefi_config_present = custom_uefi_json_data.is_some();

// obtain the final custom uefi vars by applying the delta onto the base vars
match custom_uefi_json_data {
let custom_uefi_vars = match custom_uefi_json_data {
Some(data) => {
let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?;
base_vars.apply_delta(delta)?
}
None => base_vars,
}
};

(
base_secure_boot_template_vars,
custom_uefi_vars,
custom_uefi_config_present,
)
};

if opt.uefi {
Expand All @@ -1230,7 +1238,9 @@ async fn vm_config_from_command_line(
};
chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new(
arch,
base_secure_boot_template_vars,
custom_uefi_vars,
custom_uefi_config_present,
opt.secure_boot,
log_level,
None,
Expand Down
5 changes: 4 additions & 1 deletion petri/src/vm/openvmm/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ impl PetriVmConfigOpenVmm {
// OpenhclUefi uses BaseChipsetType::HclHost, so it does not need this.
if matches!(firmware, Firmware::Uefi { .. }) {
let uefi_cfg = firmware.uefi_config();
let custom_uefi_vars =
let base_secure_boot_template_vars =
uefi_cfg.map_or_else(Default::default, |c| match (arch, c.secure_boot_template) {
(MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
hyperv_secure_boot_templates::x64::microsoft_windows()
Expand All @@ -411,6 +411,7 @@ impl PetriVmConfigOpenVmm {
) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
(_, None) => Default::default(),
});
let custom_uefi_vars = base_secure_boot_template_vars.clone();
let secure_boot = uefi_cfg.is_some_and(|c| c.secure_boot_enabled);
let log_level = match uefi_cfg
.map(|c| c.efi_diagnostics_log_level)
Expand All @@ -433,7 +434,9 @@ impl PetriVmConfigOpenVmm {
MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
},
base_secure_boot_template_vars,
custom_uefi_vars,
false,
secure_boot,
log_level,
diagnostics_rate_limit,
Expand Down
1 change: 1 addition & 0 deletions vm/devices/firmware/firmware_uefi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fuzzing = []
firmware_uefi_custom_vars.workspace = true
firmware_uefi_resources.workspace = true
hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] }
hyperv_secure_boot_templates.workspace = true
uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] }
uefi_specs.workspace = true
uefi_nvram_specvars.workspace = true
Expand Down
85 changes: 83 additions & 2 deletions vm/devices/firmware/firmware_uefi/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ use firmware_uefi_resources::UefiDeviceHandle;
use firmware_uefi_resources::UefiLoggerHandleKind;
use firmware_uefi_resources::UefiVsmConfigHandleKind;
use firmware_uefi_resources::UefiWatchdogPlatformHandleKind;
use hcl_compat_uefi_nvram_storage::BaseSecureBootTemplateVariables;
use hcl_compat_uefi_nvram_storage::HclCompatNvram;
use std::borrow::Cow;
use thiserror::Error;
use uefi_nvram_specvars::signature_list::SignatureData;
use uefi_nvram_specvars::signature_list::SignatureList;
use vm_resource::AsyncResolveResource;
use vm_resource::ResolveError;
use vm_resource::ResourceResolver;
Expand Down Expand Up @@ -57,6 +61,62 @@ pub enum ResolveUefiDeviceError {
Init(#[from] crate::UefiInitError),
}

fn base_secure_boot_template_variables(
custom_vars: &firmware_uefi_custom_vars::CustomVars,
) -> Option<BaseSecureBootTemplateVariables> {
let signatures = custom_vars.signatures.as_ref()?;

let mut pk = Vec::new();
extend_signature_var(std::iter::once(&signatures.pk), &mut pk);

let mut kek = Vec::new();
extend_signature_var(&signatures.kek, &mut kek);

let mut db = Vec::new();
extend_signature_var(&signatures.db, &mut db);

let mut dbx = Vec::new();
extend_signature_var(&signatures.dbx, &mut dbx);

Some(BaseSecureBootTemplateVariables::new(pk, kek, db, dbx))
}

fn extend_signature_var<'a>(
signatures: impl IntoIterator<Item = &'a firmware_uefi_custom_vars::Signature>,
data: &mut Vec<u8>,
) {
use firmware_uefi_custom_vars::Signature;
use uefi_specs::hyperv::nvram::vars::MSFT_SECURE_BOOT_PRODUCTION_GUID;

for signature in signatures {
match signature {
Signature::X509(certs) => {
for cert in certs {
SignatureList::X509(SignatureData::new_x509(
MSFT_SECURE_BOOT_PRODUCTION_GUID,
Cow::Borrowed(cert.0.as_slice()),
))
.extend_as_spec_signature_list(data);
}
}
Signature::Sha256(digests) => {
SignatureList::Sha256(
digests
.iter()
.map(|digest| {
SignatureData::new_sha256(
MSFT_SECURE_BOOT_PRODUCTION_GUID,
Cow::Borrowed(&digest.0),
)
})
.collect(),
)
.extend_as_spec_signature_list(data);
}
}
}
}

// The ACPI GPE0 line to use for generation ID. This must match the value in
// the DSDT.
const GPE0_LINE_GENERATION_ID: u32 = 0;
Expand Down Expand Up @@ -133,12 +193,33 @@ impl AsyncResolveResource<ChipsetDeviceHandleKind, UefiDeviceHandle> for UefiDev
}
};

let nvram_storage = Box::new(HclCompatNvram::new(
let nvram_storage = HclCompatNvram::new(
vmm_core::emuplat::hcl_compat_uefi_nvram_storage::VmgsStorageBackendAdapter(
nvram_storage,
),
storage_quirks,
));
);
let nvram_storage = if config.secure_boot {
let baseline_configured = config.base_secure_boot_template_vars.signatures.is_some();
tracing::info!(
baseline_configured,
baseline_revision = if baseline_configured {
hyperv_secure_boot_templates::BASELINE_REVISION
} else {
"none"
},
custom_uefi_config_present = config.custom_uefi_config_present,
"secure boot configuration"
);

match base_secure_boot_template_variables(&config.base_secure_boot_template_vars) {
Some(template) => nvram_storage.with_base_secure_boot_template_variables(template),
None => nvram_storage,
}
} else {
nvram_storage
};
let nvram_storage = Box::new(nvram_storage);

let gm = input.encrypted_guest_memory.clone();
let runtime_deps = UefiRuntimeDeps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ impl NvramServices {
};

if !is_restoring {
nvram.inject_vars_on_first_boot(custom_vars).await?;
nvram
.inject_vars_on_first_boot(custom_vars, secure_boot_enabled)
.await?;
nvram.inject_hyperv_vars().await?;
nvram.setup_secure_boot(secure_boot_enabled).await?;
}
Expand All @@ -104,6 +106,7 @@ impl NvramServices {
async fn inject_vars_on_first_boot(
&mut self,
custom_vars: CustomVars,
secure_boot_enabled: bool,
) -> Result<(), NvramSetupError> {
// "First boot" is marked by having no variables in nvram storage
if !self
Expand Down Expand Up @@ -160,6 +163,9 @@ impl NvramServices {
}

self.inject_custom_vars(custom_vars).await?;
if secure_boot_enabled {
self.services.after_custom_vars_injected();
}

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ impl<S: VmmNvramStorage> NvramSpecServices<S> {
self.storage.is_empty().await
}

/// Called after custom UEFI variables are injected on first boot,
/// when secure boot is enabled.
pub fn after_custom_vars_injected(&self) {
self.storage.after_custom_vars_injected()
}

/// Update "SetupMode" based on the current value of "PK"
///
/// From UEFI spec section 32.3
Expand Down
2 changes: 2 additions & 0 deletions vm/devices/firmware/firmware_uefi_resources/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ pub struct UefiConfig {
pub command_set: UefiCommandSet,
pub diagnostics_log_level: LogLevel,
pub diagnostics_rate_limit: Option<u32>,
pub base_secure_boot_template_vars: CustomVars,
pub custom_uefi_config_present: bool,
}

/// Resource kind for the platform-provided UEFI logger.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ save_restore = [ "dep:vmcore", "uefi_nvram_storage/save_restore"]

[dependencies]
hcl_compat_uefi_nvram_resources.workspace = true
uefi_nvram_specvars.workspace = true
uefi_nvram_storage.workspace = true
uefi_specs.workspace = true
vmcore = { workspace = true, optional = true }

cvm_tracing.workspace = true
Expand Down
Loading
Loading