bootmgr_rs_core/boot/secure_boot.rs
1// SPDX-FileCopyrightText: 2025 some100 <ootinnyoo@outlook.com>
2// SPDX-License-Identifier: MIT
3
4//! Secure Boot support module.
5//!
6//! Secure Boot interacts with the loading of images through the `SecurityArch` and
7//! `Security2Arch` protocol. In firmware, if any of these two are published, `LoadImage`
8//! must use those protocols on every image that is loaded. The `Security2Arch` protocol
9//! takes priority.
10//!
11//! Internally, the `FileAuthentication` and `FileAuthenticationState` methods are used for
12//! verifying images. These methods can also be replaced with our own custom made ones, mainly
13//! also using Shim.
14//!
15//! Even though Shim is the main consumer of this type of module, the overall architecture is
16//! very pluggable and custom validators not simply delegating to Shim can be used as well.
17//!
18//! This hooks onto `SecurityArch` and `Security2Arch` in order to replace their
19//! authenticators with custom ones using Shim or any other validator.
20//!
21//! These hooks are temporary and should be uninstalled after the image is loaded. This is done
22//! automatically through the `SecurityOverrideGuard` struct.
23
24use core::cell::Cell;
25use core::ptr::NonNull;
26
27use thiserror::Error;
28use uefi::{cstr16, proto::device_path::DevicePath, runtime::VariableVendor};
29
30use crate::{
31 BootResult, boot::secure_boot::security_override::SecurityOverrideInner,
32 system::variable::get_variable,
33};
34
35pub mod security_hooks;
36pub mod security_override;
37pub mod shim;
38
39/// An `Error` that may result from validating an image with Secure Boot.
40#[derive(Error, Debug)]
41pub enum SecureBootError {
42 /// Neither a device path nor a file buffer were specified to the image.
43 #[error("DevicePath and file buffer were both None")]
44 NoDevicePathOrFile,
45
46 /// A validator was not installed, but the security hooks were installed.
47 #[error("Validator was not installed")]
48 NoValidator,
49}
50
51/// The function signature for a validator.
52pub(super) type Validator = fn(
53 ctx: Option<NonNull<u8>>,
54 device_path: Option<&DevicePath>,
55 file_buffer: Option<&mut [u8]>,
56 file_size: usize,
57) -> BootResult<()>;
58
59/// An instance of [`SecurityOverrideInner`] that lasts for the lifetime of the program.
60///
61/// This is mandatory mainly due to the security hooks. We cannot provide arbitrary context to the security hooks, as the
62/// function signature is constant and decided by the UEFI spec. Without a global static state, the security hooks cannot
63/// possibly know of the existence of what custom validator we have installed, or where the original security validators
64/// are.
65///
66/// To partially counter the risk that a singular static state brings, this static is not exposed to anywhere other than
67/// [`SecurityOverrideGuard`]. This may need to be changed more than once in case `LoadImage` fails, and the override
68/// is still installed.
69static SECURITY_OVERRIDE: SecurityOverride = SecurityOverride {
70 inner: Cell::new(None),
71};
72
73/// The security override, for installing a custom validator.
74struct SecurityOverride {
75 /// The inner [`SecurityOverrideInner`] wrapped around a [`Cell`] for safety.
76 inner: Cell<Option<SecurityOverrideInner>>,
77}
78
79impl SecurityOverride {
80 /// Return a copy of the inner [`SecurityOverrideInner`].
81 ///
82 /// # Panics
83 ///
84 /// This will panic if the [`Cell`] is not yet initialized.
85 /// However, this is not possible since the [`Cell`] is always initalized at the start
86 /// of the program as a static. Therefore, this method cannot actually panic.
87 const fn get(&self) -> SecurityOverrideInner {
88 self.inner
89 .get()
90 .expect("The static Cell should always be initialized at the start of the program")
91 }
92}
93
94// SAFETY: uefi is a single threaded environment there is no notion of thread safety
95unsafe impl Sync for SecurityOverride {}
96
97/// A guard for [`SecurityOverride`]. When created, it will install a validator. When the
98/// override is eventually dropped, the validator will be uninstalled.
99pub(super) struct SecurityOverrideGuard;
100
101impl SecurityOverrideGuard {
102 /// Create a new [`SecurityOverrideGuard`]. Installs a validator and returns the guard.
103 ///
104 /// When the returned guard is dropped, the security override is automatically uninstalled.
105 pub(super) fn new(validator: Validator, validator_ctx: Option<NonNull<u8>>) -> Self {
106 install_security_override(validator, validator_ctx);
107 Self
108 }
109}
110
111impl Drop for SecurityOverrideGuard {
112 fn drop(&mut self) {
113 uninstall_security_override();
114 }
115}
116
117/// Tests if secure boot is enabled through a UEFI variable.
118#[must_use = "Has no effect if the result is unused"]
119fn secure_boot_enabled() -> bool {
120 matches!(
121 get_variable::<bool>(cstr16!("SecureBoot"), Some(VariableVendor::GLOBAL_VARIABLE)),
122 Ok(true)
123 )
124}
125
126/// Installs a security override given a [`Validator`] and optionally a `validator_ctx`.
127///
128/// You should use the [`SecurityOverrideGuard`] to safely ensure the override is dropped.
129fn install_security_override(validator: Validator, validator_ctx: Option<NonNull<u8>>) {
130 let security_override = &SECURITY_OVERRIDE;
131
132 security_override
133 .inner
134 .set(Some(SecurityOverrideInner::new(validator, validator_ctx)));
135}
136
137/// Uninstalls the security override. Should be used after installing the security override.
138///
139/// You should use the [`SecurityOverrideGuard`] to safely ensure the override is dropped.
140fn uninstall_security_override() {
141 let security_override = &SECURITY_OVERRIDE;
142
143 security_override.get().uninstall_validator();
144 security_override.inner.take();
145}