From 7ecafb29470c58d7aad00ae771234cdfa2c02da5 Mon Sep 17 00:00:00 2001 From: nikstur Date: Mon, 22 May 2023 23:28:59 +0200 Subject: [PATCH] stub: add fat variant A compile time feature is introduced that allows to build "fat" stubs that can be used to build "fat" UKIs. "fat" here means that the actual kernel and initrd are embedded in the PE binary, not only the file path and hash. This brings us one step closer to feature partiy with systemd-stub and thus one step closer to replacing it fully. Such a "fat" or "real" UKI is also interesting for image-based deployments of NixOS. --- README.md | 7 ++ flake.nix | 69 +++++++++--- nix/modules/uki.nix | 61 +++++++++++ nix/tests/stub.nix | 44 ++++++++ rust/stub/Cargo.toml | 7 +- rust/stub/src/common.rs | 35 ++++++ rust/stub/src/fat.rs | 67 ++++++++++++ rust/stub/src/main.rs | 235 ++++------------------------------------ rust/stub/src/thin.rs | 193 +++++++++++++++++++++++++++++++++ 9 files changed, 488 insertions(+), 230 deletions(-) create mode 100644 nix/modules/uki.nix create mode 100644 nix/tests/stub.nix create mode 100644 rust/stub/src/common.rs create mode 100644 rust/stub/src/fat.rs create mode 100644 rust/stub/src/thin.rs diff --git a/README.md b/README.md index 9aaeb3d..3d19d14 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,13 @@ on the ESP. The chain of trust is maintained by validating the signature on the Linux kernel and embedding a cryptographic hash of the initrd into the signed UKI. +The stub is available in a "thin" and a "fat" variant. The "thin" variant is +the one described above and is tailor made for NixOS. The "fat" variant aims to +work exactly like the `systemd-stub`---in fact, it's supposed to eventually +replace it. The "thin" variant is the default, you can build it from the stub +directory with `cargo build`. The "fat" variant needs to be enabled at build +time with `cargo build --no-default-features --features fat`. + The stub lives in `rust/stub`. ### Fwupd diff --git a/flake.nix b/flake.nix index b511a82..41765cb 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,17 @@ } ); + flake.nixosModules.uki = moduleWithSystem ( + perSystem@{ config }: + { lib, ... }: { + imports = [ + ./nix/modules/uki.nix + ]; + + boot.loader.uki.stub = lib.mkDefault "${perSystem.config.packages.fatStub}/bin/lanzaboote_stub.efi"; + } + ); + systems = [ "x86_64-linux" @@ -81,7 +92,7 @@ craneLib = crane.lib.x86_64-linux.overrideToolchain uefi-rust-stable; # Build attributes for a Rust application. - buildRustApp = + buildRustApp = lib.makeOverridable ( { src , target ? null , doCheck ? true @@ -126,7 +137,8 @@ }); rustfmt = craneLib.cargoFmt (commonArgs // { inherit cargoArtifacts; }); - }; + } + ); stubCrane = buildRustApp { src = craneLib.cleanCargoSource ./rust/stub; @@ -134,7 +146,14 @@ doCheck = false; }; + fatStubCrane = stubCrane.override { + extraArgs = { + cargoExtraArgs = "--no-default-features --features fat"; + }; + }; + stub = stubCrane.package; + fatStub = fatStubCrane.package; toolCrane = buildRustApp { src = ./rust/tool; @@ -164,7 +183,7 @@ in { packages = { - inherit stub; + inherit stub fatStub; tool = wrappedTool; lzbt = wrappedTool; }; @@ -173,15 +192,27 @@ inherit (config.packages) tool; }; - checks = { - toolClippy = toolCrane.clippy; - stubClippy = stubCrane.clippy; - toolFmt = toolCrane.rustfmt; - stubFmt = stubCrane.rustfmt; - } // (import ./nix/tests/lanzaboote.nix { - inherit pkgs; - lanzabooteModule = self.nixosModules.lanzaboote; - }); + checks = + let + nixosLib = import (pkgs.path + "/nixos/lib") { }; + runTest = module: nixosLib.runTest { + imports = [ module ]; + hostPkgs = pkgs; + }; + in + { + toolClippy = toolCrane.clippy; + stubClippy = stubCrane.clippy; + fatStubClippy = fatStubCrane.clippy; + toolFmt = toolCrane.rustfmt; + stubFmt = stubCrane.rustfmt; + } // (import ./nix/tests/lanzaboote.nix { + inherit pkgs; + lanzabooteModule = self.nixosModules.lanzaboote; + }) // (import ./nix/tests/stub.nix { + inherit pkgs runTest; + ukiModule = self.nixosModules.uki; + }); pre-commit = { check.enable = true; @@ -193,9 +224,17 @@ }; devShells.default = pkgs.mkShell { - shellHook = '' - ${config.pre-commit.installationScript} - ''; + shellHook = + let + systemdUkify = pkgs.systemdMinimal.override { + withEfi = true; + withUkify = true; + }; + in + '' + ${config.pre-commit.installationScript} + export PATH=$PATH:${systemdUkify}/lib/systemd + ''; packages = let diff --git a/nix/modules/uki.nix b/nix/modules/uki.nix new file mode 100644 index 0000000..5c465b6 --- /dev/null +++ b/nix/modules/uki.nix @@ -0,0 +1,61 @@ +# This module introduces a simple boot loader installer that installs a UKI, +# leveraging bootspec. It is only designed to be useful in tests where +# rebuilding is unlikely/hard. + +{ config, lib, pkgs, ... }: + +let + cfg = config.boot.loader.uki; +in +{ + options.boot.loader.uki = { + enable = lib.mkEnableOption "UKI"; + + stub = lib.mkOption { + type = lib.types.path; + description = "Path to the UKI stub to use."; + }; + }; + + config = + let + systemdUkify = pkgs.systemdMinimal.override { + withEfi = true; + withUkify = true; + }; + in + lib.mkIf cfg.enable { + boot.bootspec.enable = true; + boot.loader.external = { + enable = true; + installHook = + let + bootspecNamespace = ''"org.nixos.bootspec.v1"''; + installer = pkgs.writeShellApplication { + name = "install-uki"; + runtimeInputs = with pkgs; [ jq systemd binutils ]; + text = '' + boot_json=/nix/var/nix/profiles/system-1-link/boot.json + kernel=$(jq -r '.${bootspecNamespace}.kernel' "$boot_json") + initrd=$(jq -r '.${bootspecNamespace}.initrd' "$boot_json") + init=$(jq -r '.${bootspecNamespace}.init' "$boot_json") + + ${systemdUkify}/lib/systemd/ukify \ + "$kernel" \ + "$initrd" \ + --stub=${cfg.stub} \ + --cmdline="init=$init ${builtins.toString config.boot.kernelParams}" \ + --os-release="@${config.system.build.etc}/etc/os-release" \ + --output=uki.efi + + esp=${config.boot.loader.efi.efiSysMountPoint} + + bootctl install --esp-path="$esp" + install uki.efi "$esp"/EFI/Linux/ + ''; + }; + in + "${installer}/bin/install-uki"; + }; + }; +} diff --git a/nix/tests/stub.nix b/nix/tests/stub.nix new file mode 100644 index 0000000..ee44b3f --- /dev/null +++ b/nix/tests/stub.nix @@ -0,0 +1,44 @@ +{ pkgs, runTest, ukiModule }: + +let + common = _: { + imports = [ ukiModule ]; + + virtualisation = { + useBootLoader = true; + useEFIBoot = true; + }; + + boot.loader.uki.enable = true; + boot.loader.efi = { + canTouchEfiVariables = true; + }; + }; +in +{ + # This test serves as a baseline to make sure that the custom boot installer + # script defined in the ukiModule works with the upstream systemd-stub. When + # this test fails something is very wrong. + systemd-stub = runTest { + name = "systemd-stub"; + nodes.machine = _: { + imports = [ common ]; + boot.loader.uki.stub = "${pkgs.systemd}/lib/systemd/boot/efi/linuxx64.efi.stub"; + }; + testScript = '' + machine.start() + print(machine.succeed("bootctl status")) + ''; + }; + + fatStub = runTest { + name = "fat-stub"; + nodes.machine = _: { + imports = [ common ]; + }; + testScript = '' + machine.start() + print(machine.succeed("bootctl status")) + ''; + }; +} diff --git a/rust/stub/Cargo.toml b/rust/stub/Cargo.toml index f06d981..363c501 100644 --- a/rust/stub/Cargo.toml +++ b/rust/stub/Cargo.toml @@ -16,10 +16,15 @@ bitflags = "2.2.1" log = { version = "0.4.17", default-features = false, features = [ "max_level_info", "release_max_level_warn" ]} # Use software implementation because the UEFI target seems to need it. -sha2 = { version = "0.10.6", default-features = false, features = ["force-soft"] } +sha2 = { version = "0.10.6", default-features = false, features = ["force-soft"], optional = true } # SHA1 for TPM TCG interface version 1. sha1_smol = "1.0.0" +[features] +default = [ "thin" ] +thin = ["dep:sha2"] +fat = [] + [profile.release] opt-level = "s" lto = true diff --git a/rust/stub/src/common.rs b/rust/stub/src/common.rs new file mode 100644 index 0000000..9c567b7 --- /dev/null +++ b/rust/stub/src/common.rs @@ -0,0 +1,35 @@ +use alloc::vec::Vec; +use uefi::{prelude::*, CStr16, CString16, Result}; + +use crate::linux_loader::InitrdLoader; +use crate::pe_loader::Image; +use crate::pe_section::pe_section_as_string; + +/// Extract a string, stored as UTF-8, from a PE section. +pub fn extract_string(pe_data: &[u8], section: &str) -> Result { + let string = pe_section_as_string(pe_data, section).ok_or(Status::INVALID_PARAMETER)?; + + Ok(CString16::try_from(string.as_str()).map_err(|_| Status::INVALID_PARAMETER)?) +} + +/// 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. +pub fn boot_linux_unchecked( + handle: Handle, + system_table: SystemTable, + kernel_data: Vec, + kernel_cmdline: &CStr16, + initrd_data: Vec, +) -> 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.to_result() +} diff --git a/rust/stub/src/fat.rs b/rust/stub/src/fat.rs new file mode 100644 index 0000000..4a39f0a --- /dev/null +++ b/rust/stub/src/fat.rs @@ -0,0 +1,67 @@ +use alloc::vec::Vec; +use uefi::{prelude::*, CString16, Result}; + +use crate::common::{boot_linux_unchecked, extract_string}; +use crate::pe_section::pe_section; +use crate::uefi_helpers::booted_image_file; + +/// Extract bytes from a PE section. +pub fn extract_bytes(pe_data: &[u8], section: &str) -> Result> { + let bytes: Vec = pe_section(pe_data, section) + .ok_or(Status::INVALID_PARAMETER)? + .try_into() + .map_err(|_| Status::INVALID_PARAMETER)?; + + Ok(bytes) +} + +/// The configuration that is embedded at build time. +/// +/// After this stub is built, configuration need to be embedded into the binary by adding PE +/// sections. This struct represents that information. +struct EmbeddedConfiguration { + /// The kernel command-line. + cmdline: CString16, + + /// The kernel as raw bytes. + kernel: Vec, + + /// The initrd as raw bytes. + initrd: Vec, +} + +impl EmbeddedConfiguration { + fn new(file_data: &[u8]) -> Result { + Ok(Self { + kernel: extract_bytes(file_data, ".linux")?, + initrd: extract_bytes(file_data, ".initrd")?, + cmdline: extract_string(file_data, ".cmdline")?, + }) + } +} + +pub fn boot_linux(handle: Handle, mut system_table: SystemTable) -> Status { + uefi_services::init(&mut system_table).unwrap(); + + // 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. + let config = unsafe { + EmbeddedConfiguration::new( + booted_image_file(system_table.boot_services()) + .unwrap() + .as_slice(), + ) + .expect("Failed to extract configuration from binary.") + }; + + boot_linux_unchecked( + handle, + system_table, + config.kernel, + &config.cmdline, + config.initrd, + ) + .status() +} diff --git a/rust/stub/src/main.rs b/rust/stub/src/main.rs index 391f2b0..7beb836 100644 --- a/rust/stub/src/main.rs +++ b/rust/stub/src/main.rs @@ -4,6 +4,7 @@ extern crate alloc; +mod common; mod efivars; mod linux_loader; mod measure; @@ -13,19 +14,19 @@ mod tpm; mod uefi_helpers; mod unified_sections; -use alloc::vec::Vec; +#[cfg(feature = "fat")] +mod fat; + +#[cfg(feature = "thin")] +mod thin; + use efivars::{export_efi_variables, get_loader_features, EfiLoaderFeatures}; -use log::{info, warn}; +use log::info; use measure::measure_image; -use pe_loader::Image; -use pe_section::{pe_section, pe_section_as_string}; -use sha2::{Digest, Sha256}; use tpm::tpm_available; -use uefi::{prelude::*, proto::loaded_image::LoadedImage, CStr16, CString16, Result}; +use uefi::prelude::*; -use crate::{linux_loader::InitrdLoader, uefi_helpers::booted_image_file}; - -type Hash = sha2::digest::Output; +use crate::uefi_helpers::booted_image_file; /// Print the startup logo on boot. fn print_logo() { @@ -42,177 +43,12 @@ fn print_logo() { ); } -/// The configuration that is embedded at build time. -/// -/// After lanzaboote is built, lzbt needs to embed configuration -/// into the binary. This struct represents that information. -struct EmbeddedConfiguration { - /// The filename of the kernel to be booted. This filename is - /// relative to the root of the volume that contains the - /// lanzaboote binary. - kernel_filename: CString16, - - /// The cryptographic hash of the kernel. - kernel_hash: Hash, - - /// The filename of the initrd to be passed to the kernel. See - /// `kernel_filename` for how to interpret these filenames. - initrd_filename: CString16, - - /// The cryptographic hash of the initrd. This hash is computed - /// over the whole PE binary, not only the embedded initrd. - initrd_hash: Hash, - - /// The kernel command-line. - cmdline: CString16, -} - -/// Extract a string, stored as UTF-8, from a PE section. -fn extract_string(pe_data: &[u8], section: &str) -> Result { - let string = pe_section_as_string(pe_data, section).ok_or(Status::INVALID_PARAMETER)?; - - Ok(CString16::try_from(string.as_str()).map_err(|_| Status::INVALID_PARAMETER)?) -} - -/// Extract a Blake3 hash from a PE section. -fn extract_hash(pe_data: &[u8], section: &str) -> Result { - let array: [u8; 32] = pe_section(pe_data, section) - .ok_or(Status::INVALID_PARAMETER)? - .try_into() - .map_err(|_| Status::INVALID_PARAMETER)?; - - Ok(array.into()) -} - -impl EmbeddedConfiguration { - fn new(file_data: &[u8]) -> Result { - Ok(Self { - kernel_filename: extract_string(file_data, ".kernelp")?, - kernel_hash: extract_hash(file_data, ".kernelh")?, - - initrd_filename: extract_string(file_data, ".initrdp")?, - initrd_hash: extract_hash(file_data, ".initrdh")?, - - cmdline: extract_string(file_data, ".cmdline")?, - }) - } -} - -/// 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, - kernel_data: Vec, - kernel_cmdline: &CStr16, - initrd_data: Vec, -) -> 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.to_result() -} - -/// 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, - kernel_data: Vec, - kernel_cmdline: &CStr16, - initrd_data: Vec, -) -> 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::(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.to_result() -} - #[entry] fn main(handle: Handle, mut system_table: SystemTable) -> Status { uefi_services::init(&mut system_table).unwrap(); print_logo(); - // 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. - 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?") - }; - - let kernel_data; - let initrd_data; - - { - let mut file_system = system_table - .boot_services() - .get_image_file_system(handle) - .expect("Failed to get file system handle"); - - kernel_data = file_system - .read(&*config.kernel_filename) - .expect("Failed to read kernel file into memory"); - initrd_data = file_system - .read(&*config.initrd_filename) - .expect("Failed to read initrd file into memory"); - } - - 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 { - warn!("Hash mismatch for kernel!"); - } - - if !is_initrd_hash_correct { - warn!("Hash mismatch for initrd!"); - } - if tpm_available(system_table.boot_services()) { info!("TPM available, will proceed to measurements."); unsafe { @@ -237,46 +73,17 @@ fn main(handle: Handle, mut system_table: SystemTable) -> Status { } export_efi_variables(&system_table).expect("Failed to export stub EFI variables"); - if is_kernel_hash_correct && is_initrd_hash_correct { - boot_linux_unchecked( - handle, - system_table, - kernel_data, - &config.cmdline, - initrd_data, - ) - .status() - } else { - // 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. + let status; - warn!("Trying to continue as non-Secure Boot. This will fail when Secure Boot is enabled."); - - boot_linux_uefi( - handle, - system_table, - kernel_data, - &config.cmdline, - initrd_data, - ) - .status() + #[cfg(feature = "fat")] + { + status = fat::boot_linux(handle, system_table) } + + #[cfg(feature = "thin")] + { + status = thin::boot_linux(handle, system_table) + } + + status } diff --git a/rust/stub/src/thin.rs b/rust/stub/src/thin.rs new file mode 100644 index 0000000..d66e056 --- /dev/null +++ b/rust/stub/src/thin.rs @@ -0,0 +1,193 @@ +use alloc::vec::Vec; +use log::warn; +use sha2::{Digest, Sha256}; +use uefi::{prelude::*, proto::loaded_image::LoadedImage, CStr16, CString16, Result}; + +use crate::common::{boot_linux_unchecked, extract_string}; +use crate::pe_section::pe_section; +use crate::{linux_loader::InitrdLoader, uefi_helpers::booted_image_file}; + +type Hash = sha2::digest::Output; + +/// The configuration that is embedded at build time. +/// +/// After this stub is built, lzbt needs to embed configuration into the binary by adding PE +/// sections. This struct represents that information. +struct EmbeddedConfiguration { + /// The filename of the kernel to be booted. This filename is + /// relative to the root of the volume that contains the + /// lanzaboote binary. + kernel_filename: CString16, + + /// The cryptographic hash of the kernel. + kernel_hash: Hash, + + /// The filename of the initrd to be passed to the kernel. See + /// `kernel_filename` for how to interpret these filenames. + initrd_filename: CString16, + + /// The cryptographic hash of the initrd. This hash is computed + /// over the whole PE binary, not only the embedded initrd. + initrd_hash: Hash, + + /// The kernel command-line. + cmdline: CString16, +} + +/// Extract a SHA256 hash from a PE section. +fn extract_hash(pe_data: &[u8], section: &str) -> Result { + let array: [u8; 32] = pe_section(pe_data, section) + .ok_or(Status::INVALID_PARAMETER)? + .try_into() + .map_err(|_| Status::INVALID_PARAMETER)?; + + Ok(array.into()) +} + +impl EmbeddedConfiguration { + fn new(file_data: &[u8]) -> Result { + Ok(Self { + kernel_filename: extract_string(file_data, ".kernelp")?, + kernel_hash: extract_hash(file_data, ".kernelh")?, + + initrd_filename: extract_string(file_data, ".initrdp")?, + initrd_hash: extract_hash(file_data, ".initrdh")?, + + cmdline: extract_string(file_data, ".cmdline")?, + }) + } +} + +/// 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, + kernel_data: Vec, + kernel_cmdline: &CStr16, + initrd_data: Vec, +) -> 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::(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.to_result() +} + +pub fn boot_linux(handle: Handle, mut system_table: SystemTable) -> Status { + uefi_services::init(&mut system_table).unwrap(); + + // 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. + let config = unsafe { + EmbeddedConfiguration::new( + booted_image_file(system_table.boot_services()) + .unwrap() + .as_slice(), + ) + .expect("Failed to extract configuration from binary. Did you run lzbt?") + }; + + let kernel_data; + let initrd_data; + + { + let mut file_system = system_table + .boot_services() + .get_image_file_system(handle) + .expect("Failed to get file system handle"); + + kernel_data = file_system + .read(&*config.kernel_filename) + .expect("Failed to read kernel file into memory"); + initrd_data = file_system + .read(&*config.initrd_filename) + .expect("Failed to read initrd file into memory"); + } + + 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 { + warn!("Hash mismatch for kernel!"); + } + + if !is_initrd_hash_correct { + warn!("Hash mismatch for initrd!"); + } + + if is_kernel_hash_correct && is_initrd_hash_correct { + boot_linux_unchecked( + handle, + system_table, + kernel_data, + &config.cmdline, + initrd_data, + ) + .status() + } else { + // 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. + + warn!("Trying to continue as non-Secure Boot. This will fail when Secure Boot is enabled."); + + boot_linux_uefi( + handle, + system_table, + kernel_data, + &config.cmdline, + initrd_data, + ) + .status() + } +}