Skip to content

Commit f593f5a

Browse files
committed
feat(boot): basic support for secure boot via shim protocol
1 parent 92f611e commit f593f5a

13 files changed

Lines changed: 331 additions & 28 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ existing UEFI bootloader or booted by the hardware directly.
1818

1919
Sprout is licensed under Apache 2.0 and is open to modifications and contributions.
2020

21-
**IMPORTANT WARNING**: Sprout does not support UEFI Secure Boot yet.
21+
**IMPORTANT WARNING**: Sprout does not support all of UEFI Secure Boot yet.
2222
See [this issue](https://github.com/edera-dev/sprout/issues/20) for updates.
2323

2424
## Background
@@ -65,13 +65,13 @@ The boot menu mechanism is very rudimentary.
6565
- [x] Load Linux initrd from disk
6666
- [x] Basic boot menu
6767
- [x] BLS autoconfiguration support
68+
- [x] [Secure Boot support](https://github.com/edera-dev/sprout/issues/20): partial
6869

6970
### Roadmap
7071

7172
- [ ] [Bootloader interface support](https://github.com/edera-dev/sprout/issues/21)
7273
- [ ] [BLS specification conformance](https://github.com/edera-dev/sprout/issues/2)
7374
- [ ] [Full-featured boot menu](https://github.com/edera-dev/sprout/issues/1)
74-
- [ ] [Secure Boot support](https://github.com/edera-dev/sprout/issues/20): work in progress
7575
- [ ] [UKI support](https://github.com/edera-dev/sprout/issues/6): partial
7676
- [ ] [multiboot2 support](https://github.com/edera-dev/sprout/issues/7)
7777
- [ ] [Linux boot protocol (boot without EFI stub)](https://github.com/edera-dev/sprout/issues/7)

docs/windows-setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Prerequisites
44

5-
- Secure Boot disabled
5+
- Secure Boot is disabled or configured to allow Sprout
66
- UEFI Windows installation
77

88
## Step 1: Base Installation

src/actions/chainload.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
3636

3737
// Resolve the path to the image to chainload.
3838
let resolved = utils::resolve_path(
39-
context.root().loaded_image_path()?,
39+
Some(context.root().loaded_image_path()?),
4040
&context.stamp(&configuration.path),
4141
)
4242
.context("unable to resolve chainload path")?;
@@ -90,8 +90,9 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
9090
// If an initrd is provided, register it with the EFI stack.
9191
let mut initrd_handle = None;
9292
if let Some(linux_initrd) = initrd {
93-
let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd)
94-
.context("unable to read linux initrd")?;
93+
let content =
94+
utils::read_file_contents(Some(context.root().loaded_image_path()?), &linux_initrd)
95+
.context("unable to read linux initrd")?;
9596
let handle =
9697
MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice())
9798
.context("unable to register linux initrd")?;

src/actions/edera.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn register_media_loader_file(
9898
// Stamp the path to the file.
9999
let path = context.stamp(path);
100100
// Read the file contents.
101-
let content = utils::read_file_contents(context.root().loaded_image_path()?, &path)
101+
let content = utils::read_file_contents(Some(context.root().loaded_image_path()?), &path)
102102
.context(format!("unable to read {} file", what))?;
103103
// Register the media loader.
104104
let handle = MediaLoaderHandle::register(guid, content.into_boxed_slice())

src/actions/splash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -
143143
// Stamp the image path value.
144144
let image = context.stamp(&configuration.image);
145145
// Read the image contents.
146-
let image = read_file_contents(context.root().loaded_image_path()?, &image)?;
146+
let image = read_file_contents(Some(context.root().loaded_image_path()?), &image)?;
147147
// Decode the image as a PNG.
148148
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
149149
.decode()

src/config/loader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn load_raw_config(options: &SproutOptions) -> Result<Vec<u8>> {
1919
info!("configuration file: {}", options.config);
2020

2121
// Read the contents of the sprout config file.
22-
let content = utils::read_file_contents(&path, &options.config)
22+
let content = utils::read_file_contents(Some(&path), &options.config)
2323
.context("unable to read sprout config file")?;
2424
// Return the contents of the sprout config file.
2525
Ok(content)

src/drivers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result
2626

2727
// Resolve the path to the driver image.
2828
let resolved = utils::resolve_path(
29-
context.root().loaded_image_path()?,
29+
Some(context.root().loaded_image_path()?),
3030
&context.stamp(&driver.path),
3131
)
3232
.context("unable to resolve path to driver")?;

src/generators/bls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
4949
let path = context.stamp(&bls.path);
5050

5151
// Resolve the path to the BLS directory.
52-
let bls_resolved = utils::resolve_path(context.root().loaded_image_path()?, &path)
52+
let bls_resolved = utils::resolve_path(Some(context.root().loaded_image_path()?), &path)
5353
.context("unable to resolve bls path")?;
5454

5555
// Construct a filesystem path to the BLS entries directory.

src/integrations/shim.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
use crate::integrations::shim::hook::SecurityHook;
12
use crate::utils;
23
use crate::utils::ResolvedPath;
4+
use crate::utils::variables::VariableController;
35
use anyhow::{Context, Result, anyhow, bail};
46
use std::ffi::c_void;
57
use uefi::Handle;
68
use uefi::boot::LoadImageSource;
7-
use uefi::proto::device_path::DevicePath;
9+
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
10+
use uefi::proto::device_path::{DevicePath, FfiDevicePath};
811
use uefi::proto::unsafe_protocol;
12+
use uefi_raw::table::runtime::VariableVendor;
913
use uefi_raw::{Guid, Status, guid};
1014

15+
/// Security hook support.
16+
mod hook;
17+
1118
/// Support for the shim loader application for Secure Boot.
1219
pub struct ShimSupport;
1320

@@ -17,6 +24,12 @@ pub enum ShimInput<'a> {
1724
OwnedDataBuffer(Option<&'a ResolvedPath>, Vec<u8>),
1825
/// Data loaded into a buffer and ready to be verified.
1926
DataBuffer(Option<&'a ResolvedPath>, &'a [u8]),
27+
/// Low-level data buffer provided by the security hook.
28+
SecurityHookBuffer(Option<*const FfiDevicePath>, &'a [u8]),
29+
/// Low-level owned data buffer provided by the security hook.
30+
SecurityHookOwnedBuffer(Option<*const FfiDevicePath>, Vec<u8>),
31+
/// Low-level path provided by the security hook.
32+
SecurityHookPath(*const FfiDevicePath),
2033
/// Data is provided as a resolved path. We will need to load the data to verify it.
2134
/// The output will them return the loaded data.
2235
ResolvedPath(&'a ResolvedPath),
@@ -27,6 +40,9 @@ impl<'a> ShimInput<'a> {
2740
pub fn buffer(&self) -> Option<&[u8]> {
2841
match self {
2942
ShimInput::OwnedDataBuffer(_, data) => Some(data),
43+
ShimInput::SecurityHookOwnedBuffer(_, data) => Some(data),
44+
ShimInput::SecurityHookBuffer(_, data) => Some(data),
45+
ShimInput::SecurityHookPath(_) => None,
3046
ShimInput::DataBuffer(_, data) => Some(data),
3147
ShimInput::ResolvedPath(_) => None,
3248
}
@@ -37,7 +53,14 @@ impl<'a> ShimInput<'a> {
3753
match self {
3854
ShimInput::OwnedDataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()),
3955
ShimInput::DataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()),
56+
ShimInput::SecurityHookBuffer(path, _) => {
57+
path.map(|it| unsafe { DevicePath::from_ffi_ptr(it) })
58+
}
59+
ShimInput::SecurityHookPath(path) => unsafe { Some(DevicePath::from_ffi_ptr(*path)) },
4060
ShimInput::ResolvedPath(path) => Some(path.full_path.as_ref()),
61+
ShimInput::SecurityHookOwnedBuffer(path, _) => {
62+
path.map(|it| unsafe { DevicePath::from_ffi_ptr(it) })
63+
}
4164
}
4265
}
4366

@@ -51,9 +74,33 @@ impl<'a> ShimInput<'a> {
5174
Ok(ShimInput::OwnedDataBuffer(root, data.to_vec()))
5275
}
5376

77+
ShimInput::SecurityHookPath(ffi_path) => {
78+
// Acquire the file path.
79+
let Some(path) = self.file_path() else {
80+
bail!("unable to convert security hook path to device path");
81+
};
82+
// Convert the underlying path to a string.
83+
let path = path
84+
.to_string(DisplayOnly(false), AllowShortcuts(false))
85+
.context("unable to convert device path to string")?;
86+
let path = utils::resolve_path(None, &path.to_string())
87+
.context("unable to resolve path")?;
88+
// Read the file path.
89+
let data = path.read_file()?;
90+
Ok(ShimInput::SecurityHookOwnedBuffer(Some(ffi_path), data))
91+
}
92+
93+
ShimInput::SecurityHookBuffer(_, _) => {
94+
bail!("unable to convert security hook buffer to owned data buffer")
95+
}
96+
5497
ShimInput::ResolvedPath(path) => {
5598
Ok(ShimInput::OwnedDataBuffer(Some(path), path.read_file()?))
5699
}
100+
101+
ShimInput::SecurityHookOwnedBuffer(path, data) => {
102+
Ok(ShimInput::SecurityHookOwnedBuffer(path, data))
103+
}
57104
}
58105
}
59106
}
@@ -83,6 +130,10 @@ struct ShimLockProtocol {
83130
}
84131

85132
impl ShimSupport {
133+
/// Variable controller for the shim lock.
134+
const SHIM_LOCK_VARIABLES: VariableController =
135+
VariableController::new(VariableVendor(Self::SHIM_LOCK_GUID));
136+
86137
/// GUID for the shim lock protocol.
87138
const SHIM_LOCK_GUID: Guid = guid!("605dab50-e046-4300-abb6-3dd810dd8b23");
88139
/// GUID for the shim image loader protocol.
@@ -103,7 +154,7 @@ impl ShimSupport {
103154
}
104155

105156
/// Use the shim to validate the `input`, returning [ShimVerificationOutput] when complete.
106-
pub fn validate(input: ShimInput) -> Result<ShimVerificationOutput> {
157+
pub fn verify(input: ShimInput) -> Result<ShimVerificationOutput> {
107158
// Acquire the handle to the shim lock protocol.
108159
let handle = utils::find_handle(&Self::SHIM_LOCK_GUID)
109160
.context("unable to find shim lock protocol")?
@@ -117,21 +168,27 @@ impl ShimSupport {
117168
ShimInput::OwnedDataBuffer(_, _data) => {
118169
bail!("owned data buffer is not supported in the verification function");
119170
}
171+
ShimInput::SecurityHookBuffer(_, _) => None,
172+
ShimInput::SecurityHookOwnedBuffer(_, _) => None,
120173
ShimInput::DataBuffer(_, _) => None,
121174
ShimInput::ResolvedPath(path) => Some(path.read_file()?),
175+
ShimInput::SecurityHookPath(_) => None,
122176
};
123177

124178
// Convert the input to a buffer.
125179
// If the input provides the data buffer, we will use that.
126180
// Otherwise, we will use the data loaded by this function.
127-
let buffer = match input {
128-
ShimInput::OwnedDataBuffer(_root, _data) => {
129-
bail!("owned data buffer is not supported in the verification function");
130-
}
131-
ShimInput::DataBuffer(_root, data) => data,
181+
let buffer = match &input {
182+
ShimInput::OwnedDataBuffer(_root, data) => data,
183+
ShimInput::DataBuffer(_root, data) => *data,
132184
ShimInput::ResolvedPath(_path) => maybe_loaded_data
133185
.as_deref()
134186
.context("expected data buffer to be loaded already")?,
187+
ShimInput::SecurityHookBuffer(_, data) => data,
188+
ShimInput::SecurityHookOwnedBuffer(_, data) => data,
189+
ShimInput::SecurityHookPath(_) => {
190+
bail!("security hook path input not supported in the verification function")
191+
}
135192
};
136193

137194
// Check if the buffer is too large to verify.
@@ -168,12 +225,19 @@ impl ShimSupport {
168225

169226
// Determines whether LoadImage in Boot Services must be patched.
170227
// Version 16 of the shim doesn't require extra effort to load Secure Boot binaries.
171-
// If the image loader is installed, we can skip over the security override.
172-
let requires_security_override = shim_loaded && !shim_loader_available;
228+
// If the image loader is installed, we can skip over the security hook.
229+
let requires_security_hook = shim_loaded && !shim_loader_available;
173230

174-
// If the security override is required, we will bail for now.
175-
if requires_security_override {
176-
bail!("shim image loader protocol is not available, please upgrade to shim version 16");
231+
// If the security hook is required, we will bail for now.
232+
if requires_security_hook {
233+
// Install the security hook, if possible. If it's not, this is necessary to continue
234+
// so we should bail.
235+
let installed = SecurityHook::install().context("unable to install security hook")?;
236+
if !installed {
237+
bail!("unable to install security hook require for this platform");
238+
}
239+
// Retain the shim protocol after load.
240+
Self::retain()?
177241
}
178242

179243
// Converts the shim input to an owned data buffer.
@@ -188,6 +252,21 @@ impl ShimSupport {
188252
};
189253

190254
// Loads the image using Boot Services LoadImage function.
191-
uefi::boot::load_image(current_image, source).context("unable to load image")
255+
let result = uefi::boot::load_image(current_image, source).context("unable to load image");
256+
257+
// If the security override is required, we will uninstall the security hook.
258+
if requires_security_hook {
259+
SecurityHook::uninstall().context("unable to uninstall security hook")?;
260+
}
261+
result
262+
}
263+
264+
/// Set the ShimRetainProtocol variable to indicate that shim should retain the protocols
265+
/// for the full lifetime of boot services.
266+
pub fn retain() -> Result<()> {
267+
Self::SHIM_LOCK_VARIABLES
268+
.set_bool("ShimRetainProtocol", true)
269+
.context("unable to retain shim protocol")?;
270+
Ok(())
192271
}
193272
}

0 commit comments

Comments
 (0)