diff --git a/crates/stage0/src/main.rs b/crates/stage0/src/main.rs index 16aecb5..acc6ba2 100644 --- a/crates/stage0/src/main.rs +++ b/crates/stage0/src/main.rs @@ -41,8 +41,8 @@ use sha2::{Digest, Sha256}; use uefi::boot; use uefi::prelude::*; use uefi::proto::loaded_image::LoadedImage; -use uefi::runtime::{self, ResetType}; -use uefi::CString16; +use uefi::runtime::{self, ResetType, VariableVendor}; +use uefi::{CStr16, CString16}; /// PCR extended with SHA-256 of the loaded payload (matches stage1's binary PCR). const PCR_BINARY: u8 = 14; @@ -80,6 +80,14 @@ fn run() -> Result<(), Status> { timing::init(); crate::slog!("stage0: version: {}", env!("CARGO_PKG_VERSION")); + // Fail closed unless the platform is in the state the attestation model assumes: UEFI Secure + // Boot enabled AND Deployed Mode. Enforcing it here means "authentic stage0 ran" -- proven by + // stage0's pinned firmware measurement -- implies the SB/deployed state held, even on firmware + // that does not measure DeployedMode into PCR 7. State only: the release signing cert is + // ephemeral (unknown at build time), so key identity is bound by the per-provider/per-arch + // golden PCR set (PCR 7 == the db), not here. + require_secure_boot_deployed()?; + // Bring the network up once (DHCP), then fetch metadata. Metadata and payload // both ride the raw-TCP4 HTTP client (http.rs). let (binary, digest, opts) = { @@ -131,6 +139,30 @@ fn run() -> Result<(), Status> { Ok(()) } +/// Fail-closed gate: require UEFI Secure Boot enabled AND Deployed Mode, both read straight from +/// the firmware variables (reliable here, pre-ExitBootServices -- unlike later in Linux). Rejects +/// if either is not exactly 1, or the variable is absent (Setup/User/Audit mode, or firmware that +/// does not implement Deployed Mode -- in which case the platform cannot make the guarantee). +fn require_secure_boot_deployed() -> Result<(), Status> { + if global_var_u8(uefi::cstr16!("SecureBoot")) != Some(1) { + crate::slog!("stage0: refusing to boot: UEFI Secure Boot is not enabled"); + return Err(Status::SECURITY_VIOLATION); + } + if global_var_u8(uefi::cstr16!("DeployedMode")) != Some(1) { + crate::slog!("stage0: refusing to boot: platform is not in UEFI Deployed Mode"); + return Err(Status::SECURITY_VIOLATION); + } + Ok(()) +} + +/// Read a one-byte UEFI global variable (`SecureBoot` / `DeployedMode` are UINT8). `None` if absent. +fn global_var_u8(name: &CStr16) -> Option { + let mut buf = [0u8; 1]; + runtime::get_variable(name, &VariableVendor::GLOBAL_VARIABLE, &mut buf) + .ok() + .and_then(|(data, _)| data.first().copied()) +} + /// Extend a PCR with `data` via the TCG2-backed TPM transport. fn measure(tpm: &mut vaportpm_attest::Tpm, pcr: u8, data: &[u8]) -> Result<(), Status> { use vaportpm_attest::PcrOps;