Skip to content
Merged
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
36 changes: 34 additions & 2 deletions crates/stage0/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) = {
Expand Down Expand Up @@ -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<u8> {
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;
Expand Down