2022-11-21 05:44:04 -05:00
|
|
|
#![no_main]
|
|
|
|
#![no_std]
|
2023-01-22 09:22:03 -05:00
|
|
|
#![deny(unsafe_op_in_unsafe_fn)]
|
2022-11-21 05:44:04 -05:00
|
|
|
|
2022-11-21 11:52:06 -05:00
|
|
|
extern crate alloc;
|
2022-11-21 10:22:44 -05:00
|
|
|
|
2022-11-22 18:25:52 -05:00
|
|
|
mod linux_loader;
|
2023-01-22 09:22:03 -05:00
|
|
|
mod pe_loader;
|
2022-11-22 10:18:12 -05:00
|
|
|
mod pe_section;
|
2022-11-22 10:24:09 -05:00
|
|
|
mod uefi_helpers;
|
2023-04-28 21:51:39 -04:00
|
|
|
mod efivars;
|
2022-11-22 10:18:12 -05:00
|
|
|
|
2023-02-02 09:54:27 -05:00
|
|
|
use alloc::vec::Vec;
|
2023-04-28 21:51:39 -04:00
|
|
|
use log::{info, warn, debug};
|
2023-01-22 09:22:03 -05:00
|
|
|
use pe_loader::Image;
|
2022-11-27 19:37:16 -05:00
|
|
|
use pe_section::{pe_section, pe_section_as_string};
|
2023-01-13 20:31:31 -05:00
|
|
|
use sha2::{Digest, Sha256};
|
2022-11-21 11:52:06 -05:00
|
|
|
use uefi::{
|
|
|
|
prelude::*,
|
|
|
|
proto::{
|
2023-02-02 10:29:48 -05:00
|
|
|
loaded_image::LoadedImage,
|
2023-03-14 19:36:50 -04:00
|
|
|
media::file::{File, FileAttribute, FileMode},
|
2022-11-21 11:52:06 -05:00
|
|
|
},
|
2023-02-02 09:54:27 -05:00
|
|
|
CStr16, CString16, Result,
|
2022-11-21 11:52:06 -05:00
|
|
|
};
|
2023-04-28 21:51:39 -04:00
|
|
|
use efivars::{EfiLoaderFeatures, export_efi_variables, get_loader_features};
|
2022-11-21 10:22:44 -05:00
|
|
|
|
2022-11-23 05:29:40 -05:00
|
|
|
use crate::{
|
|
|
|
linux_loader::InitrdLoader,
|
2023-01-23 12:02:01 -05:00
|
|
|
uefi_helpers::{booted_image_file, read_all},
|
2022-11-23 05:29:40 -05:00
|
|
|
};
|
2022-11-22 10:18:12 -05:00
|
|
|
|
2023-01-13 20:31:31 -05:00
|
|
|
type Hash = sha2::digest::Output<Sha256>;
|
|
|
|
|
2022-11-25 12:08:54 -05:00
|
|
|
/// Print the startup logo on boot.
|
2023-02-20 19:28:25 -05:00
|
|
|
fn print_logo() {
|
|
|
|
info!(
|
2022-11-25 12:08:54 -05:00
|
|
|
"
|
2023-02-20 19:28:25 -05:00
|
|
|
_ _ _
|
|
|
|
| | | | | |
|
|
|
|
| | __ _ _ __ ______ _| |__ ___ ___ | |_ ___
|
|
|
|
| |/ _` | '_ \\|_ / _` | '_ \\ / _ \\ / _ \\| __/ _ \\
|
|
|
|
| | (_| | | | |/ / (_| | |_) | (_) | (_) | || __/
|
|
|
|
|_|\\__,_|_| |_/___\\__,_|_.__/ \\___/ \\___/ \\__\\___|
|
|
|
|
|
2022-11-21 10:22:44 -05:00
|
|
|
"
|
2023-02-20 19:28:25 -05:00
|
|
|
);
|
2022-11-21 10:22:44 -05:00
|
|
|
}
|
2022-11-21 05:44:04 -05:00
|
|
|
|
2022-11-25 12:05:44 -05:00
|
|
|
/// The configuration that is embedded at build time.
|
|
|
|
///
|
2023-01-30 10:54:59 -05:00
|
|
|
/// After lanzaboote is built, lzbt needs to embed configuration
|
2022-11-25 12:05:44 -05:00
|
|
|
/// into the binary. This struct represents that information.
|
2022-11-23 09:40:52 -05:00
|
|
|
struct EmbeddedConfiguration {
|
2022-11-25 12:05:44 -05:00
|
|
|
/// The filename of the kernel to be booted. This filename is
|
|
|
|
/// relative to the root of the volume that contains the
|
|
|
|
/// lanzaboote binary.
|
2022-11-23 09:40:52 -05:00
|
|
|
kernel_filename: CString16,
|
2022-11-25 12:05:44 -05:00
|
|
|
|
2022-11-27 19:37:16 -05:00
|
|
|
/// The cryptographic hash of the kernel.
|
|
|
|
kernel_hash: Hash,
|
|
|
|
|
2022-11-25 12:05:44 -05:00
|
|
|
/// The filename of the initrd to be passed to the kernel. See
|
|
|
|
/// `kernel_filename` for how to interpret these filenames.
|
2022-11-23 09:40:52 -05:00
|
|
|
initrd_filename: CString16,
|
2022-11-27 19:37:16 -05:00
|
|
|
|
|
|
|
/// The cryptographic hash of the initrd. This hash is computed
|
|
|
|
/// over the whole PE binary, not only the embedded initrd.
|
|
|
|
initrd_hash: Hash,
|
2023-01-23 12:02:01 -05:00
|
|
|
|
|
|
|
/// The kernel command-line.
|
|
|
|
cmdline: CString16,
|
2022-11-27 19:37:16 -05:00
|
|
|
}
|
|
|
|
|
2023-01-23 12:02:01 -05:00
|
|
|
/// Extract a string, stored as UTF-8, from a PE section.
|
2023-03-14 19:36:50 -04:00
|
|
|
fn extract_string(pe_data: &[u8], section: &str) -> Result<CString16> {
|
|
|
|
let string = pe_section_as_string(pe_data, section).ok_or(Status::INVALID_PARAMETER)?;
|
2022-11-27 19:37:16 -05:00
|
|
|
|
2023-01-23 12:02:01 -05:00
|
|
|
Ok(CString16::try_from(string.as_str()).map_err(|_| Status::INVALID_PARAMETER)?)
|
2022-11-27 19:37:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Extract a Blake3 hash from a PE section.
|
2023-03-14 19:36:50 -04:00
|
|
|
fn extract_hash(pe_data: &[u8], section: &str) -> Result<Hash> {
|
|
|
|
let array: [u8; 32] = pe_section(pe_data, section)
|
2022-11-27 19:37:16 -05:00
|
|
|
.ok_or(Status::INVALID_PARAMETER)?
|
|
|
|
.try_into()
|
|
|
|
.map_err(|_| Status::INVALID_PARAMETER)?;
|
|
|
|
|
|
|
|
Ok(array.into())
|
2022-11-23 09:40:52 -05:00
|
|
|
}
|
|
|
|
|
2022-11-23 11:57:43 -05:00
|
|
|
impl EmbeddedConfiguration {
|
2023-03-14 19:36:50 -04:00
|
|
|
fn new(file_data: &[u8]) -> Result<Self> {
|
2022-11-23 09:40:52 -05:00
|
|
|
Ok(Self {
|
2023-03-14 19:36:50 -04:00
|
|
|
kernel_filename: extract_string(file_data, ".kernelp")?,
|
|
|
|
kernel_hash: extract_hash(file_data, ".kernelh")?,
|
2022-11-27 19:37:16 -05:00
|
|
|
|
2023-03-14 19:36:50 -04:00
|
|
|
initrd_filename: extract_string(file_data, ".initrdp")?,
|
|
|
|
initrd_hash: extract_hash(file_data, ".initrdh")?,
|
2023-01-23 12:02:01 -05:00
|
|
|
|
2023-03-14 19:36:50 -04:00
|
|
|
cmdline: extract_string(file_data, ".cmdline")?,
|
2022-11-23 09:40:52 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-02 09:54:27 -05:00
|
|
|
/// Boot the Linux kernel without checking the PE signature.
|
|
|
|
///
|
|
|
|
/// We assume that the caller has made sure that the image is safe to
|
|
|
|
/// be loaded using other means.
|
|
|
|
fn boot_linux_unchecked(
|
|
|
|
handle: Handle,
|
|
|
|
system_table: SystemTable<Boot>,
|
|
|
|
kernel_data: Vec<u8>,
|
|
|
|
kernel_cmdline: &CStr16,
|
|
|
|
initrd_data: Vec<u8>,
|
|
|
|
) -> uefi::Result<()> {
|
|
|
|
let kernel =
|
|
|
|
Image::load(system_table.boot_services(), &kernel_data).expect("Failed to load the kernel");
|
|
|
|
|
|
|
|
let mut initrd_loader = InitrdLoader::new(system_table.boot_services(), handle, initrd_data)?;
|
|
|
|
|
|
|
|
let status = unsafe { kernel.start(handle, &system_table, kernel_cmdline) };
|
|
|
|
|
|
|
|
initrd_loader.uninstall(system_table.boot_services())?;
|
|
|
|
status.into()
|
|
|
|
}
|
|
|
|
|
2023-02-02 10:29:48 -05:00
|
|
|
/// Boot the Linux kernel via the UEFI PE loader.
|
|
|
|
///
|
|
|
|
/// This should only succeed when UEFI Secure Boot is off (or
|
|
|
|
/// broken...), because the Lanzaboote tool does not sign the kernel.
|
|
|
|
///
|
|
|
|
/// In essence, we can use this routine to detect whether Secure Boot
|
|
|
|
/// is actually enabled.
|
|
|
|
fn boot_linux_uefi(
|
|
|
|
handle: Handle,
|
|
|
|
system_table: SystemTable<Boot>,
|
|
|
|
kernel_data: Vec<u8>,
|
|
|
|
kernel_cmdline: &CStr16,
|
|
|
|
initrd_data: Vec<u8>,
|
|
|
|
) -> uefi::Result<()> {
|
|
|
|
let kernel_handle = system_table.boot_services().load_image(
|
|
|
|
handle,
|
|
|
|
uefi::table::boot::LoadImageSource::FromBuffer {
|
|
|
|
buffer: &kernel_data,
|
|
|
|
file_path: None,
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let mut kernel_image = system_table
|
|
|
|
.boot_services()
|
|
|
|
.open_protocol_exclusive::<LoadedImage>(kernel_handle)?;
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
kernel_image.set_load_options(
|
|
|
|
kernel_cmdline.as_ptr() as *const u8,
|
|
|
|
// This unwrap is "safe" in the sense that any
|
|
|
|
// command-line that doesn't fit 4G is surely broken.
|
|
|
|
u32::try_from(kernel_cmdline.num_bytes()).unwrap(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut initrd_loader = InitrdLoader::new(system_table.boot_services(), handle, initrd_data)?;
|
|
|
|
|
|
|
|
let status = system_table
|
|
|
|
.boot_services()
|
|
|
|
.start_image(kernel_handle)
|
|
|
|
.status();
|
|
|
|
|
|
|
|
initrd_loader.uninstall(system_table.boot_services())?;
|
|
|
|
status.into()
|
|
|
|
}
|
|
|
|
|
2022-11-21 05:44:04 -05:00
|
|
|
#[entry]
|
2022-11-21 11:52:06 -05:00
|
|
|
fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
2022-11-21 05:44:04 -05:00
|
|
|
uefi_services::init(&mut system_table).unwrap();
|
|
|
|
|
2023-02-20 19:28:25 -05:00
|
|
|
print_logo();
|
2022-11-21 09:36:39 -05:00
|
|
|
|
2023-03-15 16:53:19 -04:00
|
|
|
// SAFETY: We get a slice that represents our currently running
|
|
|
|
// image and then parse the PE data structures from it. This is
|
|
|
|
// safe, because we don't touch any data in the data sections that
|
|
|
|
// might conceivably change while we look at the slice.
|
2023-03-14 19:36:50 -04:00
|
|
|
let config: EmbeddedConfiguration = unsafe {
|
|
|
|
EmbeddedConfiguration::new(
|
|
|
|
booted_image_file(system_table.boot_services())
|
|
|
|
.unwrap()
|
|
|
|
.as_slice(),
|
|
|
|
)
|
|
|
|
.expect("Failed to extract configuration from binary. Did you run lzbt?")
|
|
|
|
};
|
2022-11-23 09:40:52 -05:00
|
|
|
|
2022-11-27 14:34:39 -05:00
|
|
|
let kernel_data;
|
|
|
|
let initrd_data;
|
2022-11-27 14:04:41 -05:00
|
|
|
|
|
|
|
{
|
2022-11-25 20:31:01 -05:00
|
|
|
let mut file_system = system_table
|
|
|
|
.boot_services()
|
|
|
|
.get_image_file_system(handle)
|
2022-11-25 20:47:21 -05:00
|
|
|
.expect("Failed to get file system handle");
|
|
|
|
let mut root = file_system
|
|
|
|
.open_volume()
|
|
|
|
.expect("Failed to find ESP root directory");
|
2022-11-25 20:31:01 -05:00
|
|
|
|
2022-11-27 14:34:39 -05:00
|
|
|
let mut kernel_file = root
|
2022-11-25 20:31:01 -05:00
|
|
|
.open(
|
|
|
|
&config.kernel_filename,
|
|
|
|
FileMode::Read,
|
|
|
|
FileAttribute::empty(),
|
|
|
|
)
|
2022-11-25 20:47:21 -05:00
|
|
|
.expect("Failed to open kernel file for reading")
|
2022-11-25 20:31:01 -05:00
|
|
|
.into_regular_file()
|
2022-11-25 20:47:21 -05:00
|
|
|
.expect("Kernel is not a regular file");
|
2022-11-21 11:52:06 -05:00
|
|
|
|
2022-11-27 14:34:39 -05:00
|
|
|
kernel_data = read_all(&mut kernel_file).expect("Failed to read kernel file into memory");
|
|
|
|
|
|
|
|
let mut initrd_file = root
|
2022-11-27 14:04:41 -05:00
|
|
|
.open(
|
|
|
|
&config.initrd_filename,
|
|
|
|
FileMode::Read,
|
|
|
|
FileAttribute::empty(),
|
|
|
|
)
|
|
|
|
.expect("Failed to open initrd for reading")
|
|
|
|
.into_regular_file()
|
|
|
|
.expect("Initrd is not a regular file");
|
2022-11-27 14:34:39 -05:00
|
|
|
|
|
|
|
initrd_data = read_all(&mut initrd_file).expect("Failed to read kernel file into memory");
|
2022-11-27 14:04:41 -05:00
|
|
|
}
|
2022-11-23 06:11:20 -05:00
|
|
|
|
2023-02-02 09:54:27 -05:00
|
|
|
let is_kernel_hash_correct = Sha256::digest(&kernel_data) == config.kernel_hash;
|
|
|
|
let is_initrd_hash_correct = Sha256::digest(&initrd_data) == config.initrd_hash;
|
|
|
|
|
|
|
|
if !is_kernel_hash_correct {
|
2023-02-20 19:28:25 -05:00
|
|
|
warn!("Hash mismatch for kernel!");
|
2022-11-27 19:37:16 -05:00
|
|
|
}
|
|
|
|
|
2023-02-02 09:54:27 -05:00
|
|
|
if !is_initrd_hash_correct {
|
2023-02-20 19:28:25 -05:00
|
|
|
warn!("Hash mismatch for initrd!");
|
2022-11-27 19:37:16 -05:00
|
|
|
}
|
|
|
|
|
2023-04-28 21:51:39 -04:00
|
|
|
if let Ok(features) = get_loader_features(system_table.runtime_services()) {
|
|
|
|
if !features.contains(EfiLoaderFeatures::RandomSeed) {
|
|
|
|
// FIXME: process random seed then on the disk.
|
|
|
|
debug!("Random seed is available, but lanzaboote does not support it yet.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export_efi_variables(&system_table)
|
|
|
|
.expect("Failed to export stub EFI variables");
|
|
|
|
|
2023-02-02 09:54:27 -05:00
|
|
|
if is_kernel_hash_correct && is_initrd_hash_correct {
|
|
|
|
boot_linux_unchecked(
|
|
|
|
handle,
|
|
|
|
system_table,
|
|
|
|
kernel_data,
|
|
|
|
&config.cmdline,
|
|
|
|
initrd_data,
|
|
|
|
)
|
|
|
|
.status()
|
|
|
|
} else {
|
2023-02-02 10:29:48 -05:00
|
|
|
// There is no good way to detect whether Secure Boot is
|
|
|
|
// enabled. This is unfortunate, because we want to give the
|
|
|
|
// user a way to recover from hash mismatches when Secure Boot
|
|
|
|
// is off.
|
|
|
|
//
|
|
|
|
// So in case we get a hash mismatch, we will try to load the
|
|
|
|
// Linux image using LoadImage. What happens then depends on
|
|
|
|
// whether Secure Boot is enabled:
|
|
|
|
//
|
|
|
|
// **With Secure Boot**, the firmware will reject loading the
|
|
|
|
// image with status::SECURITY_VIOLATION.
|
|
|
|
//
|
|
|
|
// **Without Secure Boot**, the firmware will just load the
|
|
|
|
// Linux kernel.
|
|
|
|
//
|
|
|
|
// This is the behavior we want. A slight turd is that we
|
|
|
|
// increase the attack surface here by exposing the unverfied
|
|
|
|
// Linux image to the UEFI firmware. But in case the PE loader
|
|
|
|
// of the firmware is broken, we have little hope of security
|
|
|
|
// anyway.
|
|
|
|
|
2023-02-20 19:28:25 -05:00
|
|
|
warn!("Trying to continue as non-Secure Boot. This will fail when Secure Boot is enabled.");
|
2023-02-02 10:29:48 -05:00
|
|
|
|
|
|
|
boot_linux_uefi(
|
|
|
|
handle,
|
|
|
|
system_table,
|
|
|
|
kernel_data,
|
|
|
|
&config.cmdline,
|
|
|
|
initrd_data,
|
|
|
|
)
|
|
|
|
.status()
|
2023-02-02 09:54:27 -05:00
|
|
|
}
|
2022-11-21 05:44:04 -05:00
|
|
|
}
|