linux_bootloader/
linux_loader.rs

1//! This module implements the protocols to hand an initrd to the
2//! Linux kernel.
3//!
4//! XXX The initrd signature validation is vulnerable to TOCTOU,
5//! because we read the initrd multiple times. The code needs to be
6//! restructured to solve this.
7
8use core::{ffi::c_void, pin::Pin, ptr::slice_from_raw_parts_mut};
9
10use alloc::{boxed::Box, vec::Vec};
11use uefi::{
12    prelude::BootServices,
13    proto::{
14        device_path::{DevicePath, FfiDevicePath},
15        unsafe_protocol,
16    },
17    Handle, Identify, Result, ResultExt, Status,
18};
19
20/// The Linux kernel's initrd loading device path.
21///
22/// The Linux kernel points us to
23/// [u-boot](https://github.com/u-boot/u-boot/commit/ec80b4735a593961fe701cc3a5d717d4739b0fd0#diff-1f940face4d1cf74f9d2324952759404d01ee0a81612b68afdcba6b49803bdbbR28)
24/// for documentation.
25// XXX This should actually be something like:
26// static const struct {
27// 	struct efi_vendor_dev_path	vendor;
28// 	struct efi_generic_dev_path	end;
29// } __packed initrd_dev_path = {
30// 	{
31// 		{
32// 			EFI_DEV_MEDIA,
33// 			EFI_DEV_MEDIA_VENDOR,
34// 			sizeof(struct efi_vendor_dev_path),
35// 		},
36// 		LINUX_EFI_INITRD_MEDIA_GUID
37// 	}, {
38// 		EFI_DEV_END_PATH,
39// 		EFI_DEV_END_ENTIRE,
40// 		sizeof(struct efi_generic_dev_path)
41// 	}
42// };
43static mut DEVICE_PATH_PROTOCOL: [u8; 24] = [
44    0x04, 0x03, 0x14, 0x00, 0x27, 0xe4, 0x68, 0x55, 0xfc, 0x68, 0x3d, 0x4f, 0xac, 0x74, 0xca, 0x55,
45    0x52, 0x31, 0xcc, 0x68, 0x7f, 0xff, 0x04, 0x00,
46];
47
48/// The UEFI LoadFile2 protocol.
49///
50/// This protocol has a single method to load a file.
51#[repr(C)]
52#[unsafe_protocol("4006c0c1-fcb3-403e-996d-4a6c8724e06d")]
53struct LoadFile2Protocol {
54    load_file: unsafe extern "efiapi" fn(
55        this: &mut LoadFile2Protocol,
56        file_path: *const FfiDevicePath,
57        boot_policy: bool,
58        buffer_size: *mut usize,
59        buffer: *mut c_void,
60    ) -> Status,
61
62    // This is not part of the official protocol struct.
63    initrd_data: Vec<u8>,
64}
65
66impl LoadFile2Protocol {
67    fn load_file(
68        &mut self,
69        _file_path: *const FfiDevicePath,
70        _boot_policy: bool,
71        buffer_size: *mut usize,
72        buffer: *mut c_void,
73    ) -> Result<()> {
74        if buffer.is_null() || unsafe { *buffer_size } < self.initrd_data.len() {
75            unsafe {
76                *buffer_size = self.initrd_data.len();
77            }
78            return Err(Status::BUFFER_TOO_SMALL.into());
79        };
80
81        unsafe {
82            *buffer_size = self.initrd_data.len();
83        }
84
85        let output_slice: &mut [u8] =
86            unsafe { &mut *slice_from_raw_parts_mut(buffer as *mut u8, *buffer_size) };
87
88        output_slice.copy_from_slice(&self.initrd_data);
89
90        Ok(())
91    }
92}
93
94unsafe extern "efiapi" fn raw_load_file(
95    this: &mut LoadFile2Protocol,
96    file_path: *const FfiDevicePath,
97    boot_policy: bool,
98    buffer_size: *mut usize,
99    buffer: *mut c_void,
100) -> Status {
101    this.load_file(file_path, boot_policy, buffer_size, buffer)
102        .status()
103}
104
105/// A RAII wrapper to install and uninstall the Linux initrd loading
106/// protocol.
107///
108/// **Note:** You need to call [`InitrdLoader::uninstall`], before
109/// this is dropped.
110pub struct InitrdLoader {
111    proto: Pin<Box<LoadFile2Protocol>>,
112    handle: Handle,
113    registered: bool,
114}
115
116impl InitrdLoader {
117    /// Create a new [`InitrdLoader`].
118    ///
119    /// `handle` is the handle where the protocols are registered
120    /// on. `file` is the file that is served to Linux.
121    pub fn new(boot_services: &BootServices, handle: Handle, initrd_data: Vec<u8>) -> Result<Self> {
122        let mut proto = Box::pin(LoadFile2Protocol {
123            load_file: raw_load_file,
124            initrd_data,
125        });
126
127        // Linux finds the right handle by looking for something that
128        // implements the device path protocol for the specific device
129        // path.
130        unsafe {
131            let dp_proto: *mut u8 = DEVICE_PATH_PROTOCOL.as_mut_ptr();
132
133            boot_services.install_protocol_interface(
134                Some(handle),
135                &DevicePath::GUID,
136                dp_proto as *mut c_void,
137            )?;
138
139            let lf_proto: *mut LoadFile2Protocol = proto.as_mut().get_mut();
140
141            boot_services.install_protocol_interface(
142                Some(handle),
143                &LoadFile2Protocol::GUID,
144                lf_proto as *mut c_void,
145            )?;
146        }
147
148        Ok(InitrdLoader {
149            handle,
150            proto,
151            registered: true,
152        })
153    }
154
155    pub fn uninstall(&mut self, boot_services: &BootServices) -> Result<()> {
156        // This should only be called once.
157        assert!(self.registered);
158
159        unsafe {
160            let dp_proto: *mut u8 = &mut DEVICE_PATH_PROTOCOL[0];
161            boot_services.uninstall_protocol_interface(
162                self.handle,
163                &DevicePath::GUID,
164                dp_proto as *mut c_void,
165            )?;
166
167            let lf_proto: *mut LoadFile2Protocol = self.proto.as_mut().get_mut();
168
169            boot_services.uninstall_protocol_interface(
170                self.handle,
171                &LoadFile2Protocol::GUID,
172                lf_proto as *mut c_void,
173            )?;
174        }
175
176        self.registered = false;
177
178        Ok(())
179    }
180}
181
182impl Drop for InitrdLoader {
183    fn drop(&mut self) {
184        // Dropped without unregistering!
185        assert!(!self.registered);
186    }
187}