Skip to content

Commit

Permalink
stub: Match systemd-stub measurement behavior
Browse files Browse the repository at this point in the history
systemd-stub measures both the section name and the section contents.
Furthermore it measures sections in a predefined order.
  • Loading branch information
hesiod committed Jun 12, 2024
1 parent b627ccd commit 8e46bea
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 26 deletions.
105 changes: 79 additions & 26 deletions rust/uefi/linux-bootloader/src/measure.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use alloc::{string::ToString, vec::Vec};
use alloc::{collections::BTreeMap, ffi::CString, string::ToString, vec::Vec};
use log::info;
use uefi::{
cstr16,
proto::tcg::PcrIndex,
table::{runtime::VariableAttributes, Boot, SystemTable},
CString16,
};

use crate::{
Expand All @@ -26,43 +27,95 @@ pub fn measure_image(system_table: &SystemTable<Boot>, image: PeInMemory) -> uef
let pe = goblin::pe::PE::parse(pe_binary).map_err(|_err| uefi::Status::LOAD_ERROR)?;

let mut measurements = 0;
for section in pe.sections {
let section_name = section.name().map_err(|_err| uefi::Status::UNSUPPORTED)?;
if let Ok(unified_section) = UnifiedSection::try_from(section_name) {
// UNSTABLE: && in the previous if is an unstable feature
// https://github.com/rust-lang/rust/issues/53667
if unified_section.should_be_measured() {
// Here, perform the TPM log event in ASCII.
if let Some(data) = pe_section_data(pe_binary, &section) {
info!("Measuring section `{}`...", section_name);
if tpm_log_event_ascii(
boot_services,
TPM_PCR_INDEX_KERNEL_IMAGE,
data,
section_name,
)? {
measurements += 1;
}
}

// Match behaviour of systemd-stub (see src/boot/efi/stub.c in systemd)
// The encoding as well as the ordering of measurements is critical.
//
// References:
//
// "TPM2 PCR Measurements Made by systemd", https://systemd.io/TPM2_PCR_MEASUREMENTS/
// Section: PCR Measurements Made by systemd-stub (UEFI)
// - PCR 11, EV_IPL, “PE Section Name”
// - PCR 11, EV_IPL, “PE Section Data”
//
// Unified Kernel Image (UKI) specification, UAPI Group,
// https://uapi-group.org/specifications/specs/unified_kernel_image/#uki-tpm-pcr-measurements
//
// Citing from "UKI TPM PCR Measurements":
// On systems with a Trusted Platform Module (TPM) the UEFI boot stub shall measure the sections listed above,
// starting from the .linux section, in the order as listed (which should be considered the canonical order).
// The .pcrsig section is not measured.
//
// For each section two measurements shall be made into PCR 11 with the event code EV_IPL:
// - The section name in ASCII (including one trailing NUL byte)
// - The (binary) section contents
//
// The above should be repeated for every section defined above, so that the measurements are interleaved:
// section name followed by section data, followed by the next section name and its section data, and so on.

// NOTE: The order of measurements is important, so the use of BTreeMap is intentional here.
let ordered_sections: BTreeMap<_, _> = pe
.sections
.iter()
.filter_map(|section| {
let section_name = section.name().ok()?;
let unified_section = UnifiedSection::try_from(section_name).ok()?;
if !unified_section.should_be_measured() {
return None;
}
Some((unified_section, section))
})
.collect();

for (_unified_section, section) in ordered_sections {
// Unwrapping is safe: We already read the section name above
let section_name = section.name().unwrap();

info!("Measuring section `{}`...", section_name);

// First measure the section name itself
// This needs to be an UTF-8 encoded string with a trailing null byte
//
// As per reference:
// "Measured hash covers the PE section name in ASCII (including a trailing NUL byte!)."
let section_name_cs_utf8 = CString::new(section_name).unwrap();

if tpm_log_event_ascii(
boot_services,
TPM_PCR_INDEX_KERNEL_IMAGE,
section_name_cs_utf8.as_bytes_with_nul(),
section_name,
)? {
measurements += 1;
}

// Then measure the section contents.
let Some(data) = pe_section_data(pe_binary, section) else {
continue;
};

if tpm_log_event_ascii(
boot_services,
TPM_PCR_INDEX_KERNEL_IMAGE,
data,
section_name,
)? {
measurements += 1;
}
}

if measurements > 0 {
let pcr_index_encoded = TPM_PCR_INDEX_KERNEL_IMAGE
.0
.to_string()
.encode_utf16()
.flat_map(|c| c.to_le_bytes())
.collect::<Vec<u8>>();
let pcr_index_encoded =
CString16::try_from(TPM_PCR_INDEX_KERNEL_IMAGE.0.to_string().as_str())
.map_err(|_err| uefi::Status::UNSUPPORTED)?;

// If we did some measurements, expose a variable encoding the PCR where
// we have done the measurements.
runtime_services.set_variable(
cstr16!("StubPcrKernelImage"),
&BOOT_LOADER_VENDOR_UUID,
VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS,
&pcr_index_encoded,
pcr_index_encoded.as_bytes(),
)?;
}

Expand Down
1 change: 1 addition & 0 deletions rust/uefi/linux-bootloader/src/unified_sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// This is the canonical order in which they are measured into TPM
/// PCR 11.
/// !!! DO NOT REORDER !!!
#[derive(PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum UnifiedSection {
Linux = 0,
Expand Down

0 comments on commit 8e46bea

Please sign in to comment.