Skip to content
Open
Show file tree
Hide file tree
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
70 changes: 61 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ uapi-version = "0.4.0"
walkdir = "2.3.2"
signal-hook-registry = "1.4.8"

# EFI-only dependencies
[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
virtfw-libefi = { version = "0.6", features = ["std", "sbdata"] }

[profile.release]
# We assume we're being delivered via e.g. RPM which supports split debuginfo
debug = true
Expand Down
4 changes: 4 additions & 0 deletions src/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::freezethaw::fsfreeze_thaw_cycle;
))]
use crate::grubconfigs::{ensure_grub_permissions, GRUB2DIR};
use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status};
#[cfg(efi_arch)]
use crate::secureboot::validate_secureboot_for_update;
use crate::{ostreeutil, util};
use anyhow::{anyhow, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
Expand Down Expand Up @@ -756,6 +758,8 @@ fn prep_before_update() -> Result<RootContext> {
let path = "/";
let sysroot = Dir::open_ambient_dir(path, ambient_authority()).context("Opening root dir")?;
let device = list_dev_current_root()?;
#[cfg(efi_arch)]
validate_secureboot_for_update()?;
Ok(RootContext::new(sysroot, path, device))
}

Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ mod model;
mod model_legacy;
mod ostreeutil;
mod packagesystem;
#[cfg(efi_arch)]
mod secureboot;
mod sha512string;
mod util;

Expand Down
75 changes: 75 additions & 0 deletions src/secureboot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Module with checks related to Secure Boot.
//!
//! This is primarily used for checking whether it is safe to update a system which has Secure
//! Boot enabled. There can be complications if updating to a new version of shim which is only
//! signed by Microsoft keys which the user's system does not have matching certificates for,
//! so in that scenario we just block updates until the firmware is updated to match (most likely by
//! fwupd).
//!
//! See https://github.com/coreos/bootupd/issues/1099

use anyhow::{bail, Result};
use fn_error_context::context;
use log::info;
use virtfw_libefi::efivar::{ids, sigdb::EfiSigDB};
use virtfw_libefi::sb::certs;
use virtfw_libefi::varstore::sysfs;

/// Check whether Secure Boot is enabled.
fn is_secureboot_enabled() -> Result<bool> {
let Some(var) = sysfs::varstore_read(ids::SECURE_BOOT.name, ids::SECURE_BOOT.guid) else {
bail!("Could not read the Secure Boot EFI var after confirming the system is EFI-booted.");
};

let Some(&is_enabled_byte) = var.data().first() else {
bail!("Secure Boot data is empty or unreadable");
};

Ok(is_enabled_byte != 0)
}

/// Check whether the firmware's signature database contains the Microsoft UEFI CA 2023 certificate.
fn db_contains_ms_2023_cert() -> Result<bool> {
let Some(var) = sysfs::varstore_read(ids::DB.name, ids::DB.guid) else {
// At this stage, we'll have confirmed the user does have Secure Boot enabled.
// Best to be safe and abort any updates if we can't even read the EFI variable.
bail!("Could not read the signature database variable. Assuming it is not safe to update.");
};
let Some(sigdb) = EfiSigDB::new_from_bytes(var.data()) else {
bail!("Failed to parse Secure Boot signature database (unknown layout). Assuming it is not safe to update.");
};

Ok(sigdb
.get_x509_list()
.contains(&certs::MICROSOFT_DB_UEFI_2023))
}

/// Attempt to validate that the system can safely accept an EFI bootloader update (if EFI-booted).
///
/// If Secure Boot is enabled, we can't allow updates if the signature database doesn't contain
/// the Microsoft UEFI CA 2023 certificate.
#[context("Validating Secure Boot certificate compatibility")]
pub(crate) fn validate_secureboot_for_update() -> Result<()> {
if !crate::efi::is_efi_booted()? {
info!("Not EFI-booted, skipping Secure Boot certificate check");
return Ok(());
}

if !is_secureboot_enabled()? {
info!("Secure Boot not enabled, skipping Secure Boot certificate check");
return Ok(());
};

match db_contains_ms_2023_cert() {
Ok(true) => {
info!("Secure Boot DB contains Microsoft UEFI CA 2023 certificate. Safe to update.");
Ok(())
}
Ok(false) => bail!(
"Secure Boot is enabled but the Microsoft UEFI CA 2023 certificate was not \
found in the firmware's signature database. Updating the shim could render this system \
unbootable. Please update your system firmware by using, for example, fwupd."
),
Err(e) => Err(e),
}
}
32 changes: 32 additions & 0 deletions tests/kola/test-secureboot
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
## kola:
## # qemu is the only platform where setting a 'secure-boot' tag will cause
## # secureboot to get turned on for that instance.
## platforms: qemu
## # Mark as exclusive since we are requesting secureboot and that won't take effect when we're
## # running non-exclusively.
## exclusive: true
## tags: secure-boot
## architectures: x86_64
## description: Verify that Secure Boot certificate validation works as intended
#
# See https://github.com/coreos/bootupd/issues/1099
#
# NOTE: we're only testing the positive case here, as testing the negative case
# is non-trivial since we need to generate and use a custom vars file with only the 2011 cert.

set -xeuo pipefail

. $KOLA_EXT_DATA/libtest.sh

if [[ "$(mokutil --sb)" != "SecureBoot enabled" ]]; then
fatal "system is not running with Secure Boot enabled"
fi

bootupctl status > /tmp/out.txt
assert_file_has_content_literal /tmp/out.txt 'Component EFI'

# No need to force an update scenario since this should hit the certificate check
# regardless of whether there's any available updates.
bootupctl update
ok "passed the Secure Boot certificate check when the 2023 Microsoft cert is available"