Skip to content

Commit 2093269

Browse files
committed
feat(safety): bail if secure boot is enabled early
1 parent 40e2d1b commit 2093269

File tree

5 files changed

+170
-37
lines changed

5 files changed

+170
-37
lines changed
Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::platform::timer::PlatformTimer;
22
use crate::utils::device_path_subpath;
3+
use crate::utils::variables::{VariableClass, VariableController};
34
use anyhow::{Context, Result};
45
use uefi::proto::device_path::DevicePath;
5-
use uefi::{CString16, Guid, guid};
6-
use uefi_raw::table::runtime::{VariableAttributes, VariableVendor};
6+
use uefi::{Guid, guid};
7+
use uefi_raw::table::runtime::VariableVendor;
78

89
/// The name of the bootloader to tell the system.
910
const LOADER_NAME: &str = "Sprout";
@@ -13,7 +14,9 @@ pub struct BootloaderInterface;
1314

1415
impl BootloaderInterface {
1516
/// Bootloader Interface GUID from https://systemd.io/BOOT_LOADER_INTERFACE
16-
const VENDOR: VariableVendor = VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
17+
const VENDOR: VariableController = VariableController::new(VariableVendor(guid!(
18+
"4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
19+
)));
1720

1821
/// Tell the system that Sprout was initialized at the current time.
1922
pub fn mark_init(timer: &PlatformTimer) -> Result<()> {
@@ -35,23 +38,39 @@ impl BootloaderInterface {
3538
fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> {
3639
// Measure the elapsed time since the hardware timer was started.
3740
let elapsed = timer.elapsed_since_lifetime();
38-
Self::set_cstr16(key, &elapsed.as_micros().to_string())
41+
Self::VENDOR.set_cstr16(
42+
key,
43+
&elapsed.as_micros().to_string(),
44+
VariableClass::BootAndRuntimeTemporary,
45+
)
3946
}
4047

4148
/// Tell the system what loader is being used.
4249
pub fn set_loader_info() -> Result<()> {
43-
Self::set_cstr16("LoaderInfo", LOADER_NAME)
50+
Self::VENDOR.set_cstr16(
51+
"LoaderInfo",
52+
LOADER_NAME,
53+
VariableClass::BootAndRuntimeTemporary,
54+
)
4455
}
4556

4657
/// Tell the system the relative path to the partition root of the current bootloader.
4758
pub fn set_loader_path(path: &DevicePath) -> Result<()> {
4859
let subpath = device_path_subpath(path).context("unable to get loader path subpath")?;
49-
Self::set_cstr16("LoaderImageIdentifier", &subpath)
60+
Self::VENDOR.set_cstr16(
61+
"LoaderImageIdentifier",
62+
&subpath,
63+
VariableClass::BootAndRuntimeTemporary,
64+
)
5065
}
5166

5267
/// Tell the system what the partition GUID of the ESP Sprout was booted from is.
5368
pub fn set_partition_guid(guid: &Guid) -> Result<()> {
54-
Self::set_cstr16("LoaderDevicePartUUID", &guid.to_string())
69+
Self::VENDOR.set_cstr16(
70+
"LoaderDevicePartUUID",
71+
&guid.to_string(),
72+
VariableClass::BootAndRuntimeTemporary,
73+
)
5574
}
5675

5776
/// Tell the system what boot entries are available.
@@ -69,17 +88,29 @@ impl BootloaderInterface {
6988
// Write the bytes (including the null terminator) into the data buffer.
7089
data.extend_from_slice(&encoded);
7190
}
72-
Self::set("LoaderEntries", &data)
91+
Self::VENDOR.set(
92+
"LoaderEntries",
93+
&data,
94+
VariableClass::BootAndRuntimeTemporary,
95+
)
7396
}
7497

7598
/// Tell the system what the default boot entry is.
7699
pub fn set_default_entry(entry: String) -> Result<()> {
77-
Self::set_cstr16("LoaderEntryDefault", &entry)
100+
Self::VENDOR.set_cstr16(
101+
"LoaderEntryDefault",
102+
&entry,
103+
VariableClass::BootAndRuntimeTemporary,
104+
)
78105
}
79106

80107
/// Tell the system what the selected boot entry is.
81108
pub fn set_selected_entry(entry: String) -> Result<()> {
82-
Self::set_cstr16("LoaderEntrySelected", &entry)
109+
Self::VENDOR.set_cstr16(
110+
"LoaderEntrySelected",
111+
&entry,
112+
VariableClass::BootAndRuntimeTemporary,
113+
)
83114
}
84115

85116
/// Tell the system about the UEFI firmware we are running on.
@@ -91,35 +122,18 @@ impl BootloaderInterface {
91122
uefi::system::firmware_revision() >> 16,
92123
uefi::system::firmware_revision() & 0xFFFFF,
93124
);
94-
Self::set_cstr16("LoaderFirmwareInfo", &firmware_info)?;
125+
Self::VENDOR.set_cstr16(
126+
"LoaderFirmwareInfo",
127+
&firmware_info,
128+
VariableClass::BootAndRuntimeTemporary,
129+
)?;
95130

96131
// Format the firmware revision into something human-readable.
97132
let firmware_type = format!("UEFI {:02}", uefi::system::firmware_revision());
98-
Self::set_cstr16("LoaderFirmwareType", &firmware_type)
99-
}
100-
101-
/// The [VariableAttributes] for bootloader interface variables.
102-
fn attributes() -> VariableAttributes {
103-
VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS
104-
}
105-
106-
/// Set a bootloader interface variable specified by `key` to `value`.
107-
fn set(key: &str, value: &[u8]) -> Result<()> {
108-
let name =
109-
CString16::try_from(key).context("unable to convert variable name to CString16")?;
110-
uefi::runtime::set_variable(&name, &Self::VENDOR, Self::attributes(), value)
111-
.with_context(|| format!("unable to set efi variable {}", key))?;
112-
Ok(())
113-
}
114-
115-
/// Set a bootloader interface variable specified by `key` to `value`, converting the value to
116-
/// a [CString16].
117-
fn set_cstr16(key: &str, value: &str) -> Result<()> {
118-
// Encode the value as a CString16 little endian.
119-
let encoded = value
120-
.encode_utf16()
121-
.flat_map(|c| c.to_le_bytes())
122-
.collect::<Vec<u8>>();
123-
Self::set(key, &encoded)
133+
Self::VENDOR.set_cstr16(
134+
"LoaderFirmwareType",
135+
&firmware_type,
136+
VariableClass::BootAndRuntimeTemporary,
137+
)
124138
}
125139
}

src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::options::SproutOptions;
1313
use crate::options::parser::OptionsRepresentable;
1414
use crate::phases::phase;
1515
use crate::platform::timer::PlatformTimer;
16+
use crate::secure::SecureBoot;
1617
use crate::utils::PartitionGuidForm;
1718
use anyhow::{Context, Result, bail};
1819
use log::{error, info};
@@ -57,6 +58,9 @@ pub mod integrations;
5758
/// phases: Hooks into specific parts of the boot process.
5859
pub mod phases;
5960

61+
/// secure: Secure Boot support.
62+
pub mod secure;
63+
6064
/// setup: Code that initializes the UEFI environment for Sprout.
6165
pub mod setup;
6266

@@ -68,6 +72,11 @@ pub mod utils;
6872

6973
/// Run Sprout, returning an error if one occurs.
7074
fn run() -> Result<()> {
75+
// For safety reasons, we will bail early if Secure Boot is enabled.
76+
if SecureBoot::enabled().context("unable to determine Secure Boot status")? {
77+
bail!("Secure Boot is enabled. Sprout does not currently support Secure Boot.");
78+
}
79+
7180
// Start the platform timer.
7281
let timer = PlatformTimer::start();
7382

src/secure.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::utils::variables::VariableController;
2+
use anyhow::Result;
3+
4+
/// Secure boot services.
5+
pub struct SecureBoot;
6+
7+
impl SecureBoot {
8+
/// Checks if Secure Boot is enabled on the system.
9+
/// This might fail if retrieving the variable fails in an irrecoverable way.
10+
pub fn enabled() -> Result<bool> {
11+
// The SecureBoot variable will tell us whether Secure Boot is enabled at all.
12+
VariableController::GLOBAL.get_bool("SecureBoot")
13+
}
14+
}

src/utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ pub mod framebuffer;
1414
/// Support code for the media loader protocol.
1515
pub mod media_loader;
1616

17+
/// Support code for EFI variables.
18+
pub mod variables;
19+
1720
/// Parses the input `path` as a [DevicePath].
1821
/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol.
1922
pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {

src/utils/variables.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use anyhow::{Context, Result};
2+
use uefi::{CString16, guid};
3+
use uefi_raw::Status;
4+
use uefi_raw::table::runtime::{VariableAttributes, VariableVendor};
5+
6+
/// The classification of a variable.
7+
/// This is an abstraction over various variable attributes.
8+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9+
pub enum VariableClass {
10+
/// The variable is available in Boot Services and Runtime Services and is not persistent.
11+
BootAndRuntimeTemporary,
12+
}
13+
14+
impl VariableClass {
15+
/// The [VariableAttributes] for this classification.
16+
fn attributes(&self) -> VariableAttributes {
17+
match self {
18+
VariableClass::BootAndRuntimeTemporary => {
19+
VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS
20+
}
21+
}
22+
}
23+
}
24+
25+
/// Provides access to a particular set of vendor variables.
26+
pub struct VariableController {
27+
/// The GUID of the vendor.
28+
vendor: VariableVendor,
29+
}
30+
31+
impl VariableController {
32+
/// Global variables.
33+
pub const GLOBAL: VariableController = VariableController::new(VariableVendor(guid!(
34+
"8be4df61-93ca-11d2-aa0d-00e098032b8c"
35+
)));
36+
37+
/// Create a new [VariableController] for the `vendor`.
38+
pub const fn new(vendor: VariableVendor) -> Self {
39+
Self { vendor }
40+
}
41+
42+
/// Convert `key` to a variable name as a CString16.
43+
fn name(key: &str) -> Result<CString16> {
44+
CString16::try_from(key).context("unable to convert variable name to CString16")
45+
}
46+
47+
/// Retrieve a boolean value specified by the `key`.
48+
pub fn get_bool(&self, key: &str) -> Result<bool> {
49+
let name = Self::name(key)?;
50+
51+
// Retrieve the variable data, handling variable not existing as false.
52+
match uefi::runtime::get_variable_boxed(&name, &self.vendor) {
53+
Ok((data, _)) => {
54+
// If the variable is zero-length, we treat it as false.
55+
if data.is_empty() {
56+
Ok(false)
57+
} else {
58+
// We treat the variable as true if the first byte is non-zero.
59+
Ok(data[0] > 0)
60+
}
61+
}
62+
63+
Err(error) => {
64+
// If the variable does not exist, we treat it as false.
65+
if error.status() == Status::NOT_FOUND {
66+
Ok(false)
67+
} else {
68+
Err(error).with_context(|| format!("unable to get efi variable {}", key))
69+
}
70+
}
71+
}
72+
}
73+
74+
/// Set a variable specified by `key` to `value`.
75+
/// The variable `class` controls the attributes for the variable.
76+
pub fn set(&self, key: &str, value: &[u8], class: VariableClass) -> Result<()> {
77+
let name = Self::name(key)?;
78+
uefi::runtime::set_variable(&name, &self.vendor, class.attributes(), value)
79+
.with_context(|| format!("unable to set efi variable {}", key))?;
80+
Ok(())
81+
}
82+
83+
/// Set a variable specified by `key` to `value`, converting the value to
84+
/// a [CString16]. The variable `class` controls the attributes for the variable.
85+
pub fn set_cstr16(&self, key: &str, value: &str, class: VariableClass) -> Result<()> {
86+
// Encode the value as a CString16 little endian.
87+
let encoded = value
88+
.encode_utf16()
89+
.flat_map(|c| c.to_le_bytes())
90+
.collect::<Vec<u8>>();
91+
self.set(key, &encoded, class)
92+
}
93+
}

0 commit comments

Comments
 (0)