Merge pull request #21 from nix-community/boot-file-integrity
Verify Kernel/Initrd Integrity using Blake3
This commit is contained in:
commit
06da27529f
33
flake.nix
33
flake.nix
|
@ -54,21 +54,12 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
# This is basically an empty EFI application that we use as a
|
|
||||||
# carrier for the initrd.
|
|
||||||
initrdStubCrane = buildRustApp {
|
|
||||||
src = ./rust/initrd-stub;
|
|
||||||
target = "x86_64-unknown-uefi";
|
|
||||||
doCheck = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
lanzabooteCrane = buildRustApp {
|
lanzabooteCrane = buildRustApp {
|
||||||
src = ./rust/lanzaboote;
|
src = ./rust/lanzaboote;
|
||||||
target = "x86_64-unknown-uefi";
|
target = "x86_64-unknown-uefi";
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
initrd-stub = initrdStubCrane.package;
|
|
||||||
lanzaboote = lanzabooteCrane.package;
|
lanzaboote = lanzabooteCrane.package;
|
||||||
|
|
||||||
lanzatoolCrane = buildRustApp {
|
lanzatoolCrane = buildRustApp {
|
||||||
|
@ -87,8 +78,7 @@
|
||||||
makeWrapper ${lanzatool-unwrapped}/bin/lanzatool $out/bin/lanzatool \
|
makeWrapper ${lanzatool-unwrapped}/bin/lanzatool $out/bin/lanzatool \
|
||||||
--set PATH ${lib.makeBinPath [ pkgs.binutils-unwrapped pkgs.sbsigntool ]} \
|
--set PATH ${lib.makeBinPath [ pkgs.binutils-unwrapped pkgs.sbsigntool ]} \
|
||||||
--set RUST_BACKTRACE full \
|
--set RUST_BACKTRACE full \
|
||||||
--set LANZABOOTE_STUB ${lanzaboote}/bin/lanzaboote.efi \
|
--set LANZABOOTE_STUB ${lanzaboote}/bin/lanzaboote.efi
|
||||||
--set LANZABOOTE_INITRD_STUB ${initrd-stub}/bin/initrd-stub.efi \
|
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
overlays.default = final: prev: {
|
overlays.default = final: prev: {
|
||||||
|
@ -98,7 +88,7 @@
|
||||||
nixosModules.lanzaboote = import ./nix/lanzaboote.nix;
|
nixosModules.lanzaboote = import ./nix/lanzaboote.nix;
|
||||||
|
|
||||||
packages.x86_64-linux = {
|
packages.x86_64-linux = {
|
||||||
inherit initrd-stub lanzaboote lanzatool;
|
inherit lanzaboote lanzatool;
|
||||||
default = lanzatool;
|
default = lanzatool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,7 +139,10 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
mkUnsignedTest = { name, path }: mkSecureBootTest {
|
|
||||||
|
# Execute a boot test that is intended to fail.
|
||||||
|
#
|
||||||
|
mkUnsignedTest = { name, path, appendCrap ? false }: mkSecureBootTest {
|
||||||
inherit name;
|
inherit name;
|
||||||
testScript = ''
|
testScript = ''
|
||||||
import json
|
import json
|
||||||
|
@ -166,10 +159,14 @@
|
||||||
src_path = ${path.src}
|
src_path = ${path.src}
|
||||||
dst_path = ${path.dst}
|
dst_path = ${path.dst}
|
||||||
machine.succeed(f"cp -rf {src_path} {dst_path}")
|
machine.succeed(f"cp -rf {src_path} {dst_path}")
|
||||||
|
'' + lib.optionalString appendCrap ''
|
||||||
|
machine.succeed(f"echo Foo >> {dst_path}")
|
||||||
|
'' +
|
||||||
|
''
|
||||||
machine.succeed("sync")
|
machine.succeed("sync")
|
||||||
machine.crash()
|
machine.crash()
|
||||||
machine.start()
|
machine.start()
|
||||||
machine.wait_for_console_text("panicked")
|
machine.wait_for_console_text("Hash mismatch")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
@ -221,13 +218,21 @@
|
||||||
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
|
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The initrd is not directly signed. Its hash is embedded
|
||||||
|
# into lanzaboote. To make integrity verification fail, we
|
||||||
|
# actually have to modify the initrd. Appending crap to the
|
||||||
|
# end is a harmless way that would make the kernel still
|
||||||
|
# accept it.
|
||||||
is-initrd-secured = mkUnsignedTest {
|
is-initrd-secured = mkUnsignedTest {
|
||||||
name = "unsigned-initrd-do-not-boot-under-secureboot";
|
name = "unsigned-initrd-do-not-boot-under-secureboot";
|
||||||
path = {
|
path = {
|
||||||
src = "bootspec.get('initrd')";
|
src = "bootspec.get('initrd')";
|
||||||
dst = "convert_to_esp(bootspec.get('initrd'))";
|
dst = "convert_to_esp(bootspec.get('initrd'))";
|
||||||
};
|
};
|
||||||
|
appendCrap = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
is-kernel-secured = mkUnsignedTest {
|
is-kernel-secured = mkUnsignedTest {
|
||||||
name = "unsigned-kernel-do-not-boot-under-secureboot";
|
name = "unsigned-kernel-do-not-boot-under-secureboot";
|
||||||
path = {
|
path = {
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[build]
|
|
||||||
target = "x86_64-unknown-uefi"
|
|
|
@ -1,104 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit_field"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "initrd-stub"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"uefi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.47"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.103"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ucs2"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bad643914094137d475641b6bab89462505316ec2ce70907ad20102d28a79ab8"
|
|
||||||
dependencies = [
|
|
||||||
"bit_field",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uefi"
|
|
||||||
version = "0.18.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07b87700863d65dd4841556be3374d8d4f9f8dbb577ad93a39859e70b3b91f35"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"log",
|
|
||||||
"ucs2",
|
|
||||||
"uefi-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uefi-macros"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "275f054a1d9fd7e43a2ce91cc24298a87b281117dea8afc120ae95faa0e96b94"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "initrd-stub"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
uefi = { version = "0.18.0", default-features = false, features = [ ] }
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
opt-level = "s"
|
|
||||||
lto = true
|
|
|
@ -1,19 +0,0 @@
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
#![feature(abi_efiapi)]
|
|
||||||
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
use uefi::{
|
|
||||||
prelude::{entry, Boot, SystemTable},
|
|
||||||
Handle, Status,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(_info: &PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main(_handle: Handle, mut _system_table: SystemTable<Boot>) -> Status {
|
|
||||||
Status::UNSUPPORTED
|
|
||||||
}
|
|
|
@ -2,6 +2,18 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
@ -14,12 +26,37 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519-compact"
|
name = "ed25519-compact"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
@ -41,6 +78,7 @@ dependencies = [
|
||||||
name = "lanzaboote"
|
name = "lanzaboote"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"blake3",
|
||||||
"ed25519-compact",
|
"ed25519-compact",
|
||||||
"goblin",
|
"goblin",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -11,6 +11,9 @@ log = "0.4.17"
|
||||||
ed25519-compact = { version = "2.0.2", default-features = false, features = [] }
|
ed25519-compact = { version = "2.0.2", default-features = false, features = [] }
|
||||||
goblin = { version = "0.6.0", default-features = false, features = [ "pe64", "alloc" ]}
|
goblin = { version = "0.6.0", default-features = false, features = [ "pe64", "alloc" ]}
|
||||||
|
|
||||||
|
# We don't want the assembly implementations for now.
|
||||||
|
blake3 = { version = "1.3.3", default-features = false, features = [ "pure" ]}
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -5,22 +5,18 @@
|
||||||
//! because we read the initrd multiple times. The code needs to be
|
//! because we read the initrd multiple times. The code needs to be
|
||||||
//! restructured to solve this.
|
//! restructured to solve this.
|
||||||
|
|
||||||
use core::{ffi::c_void, ops::Range, pin::Pin, ptr::slice_from_raw_parts_mut};
|
use core::{ffi::c_void, pin::Pin, ptr::slice_from_raw_parts_mut};
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::{boxed::Box, vec::Vec};
|
||||||
use uefi::{
|
use uefi::{
|
||||||
prelude::BootServices,
|
prelude::BootServices,
|
||||||
proto::{
|
proto::{
|
||||||
device_path::{DevicePath, FfiDevicePath},
|
device_path::{DevicePath, FfiDevicePath},
|
||||||
media::file::RegularFile,
|
|
||||||
Protocol,
|
Protocol,
|
||||||
},
|
},
|
||||||
table::boot::LoadImageSource,
|
|
||||||
unsafe_guid, Handle, Identify, Result, ResultExt, Status,
|
unsafe_guid, Handle, Identify, Result, ResultExt, Status,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::uefi_helpers::read_all;
|
|
||||||
|
|
||||||
/// The Linux kernel's initrd loading device path.
|
/// The Linux kernel's initrd loading device path.
|
||||||
///
|
///
|
||||||
/// The Linux kernel points us to
|
/// The Linux kernel points us to
|
||||||
|
@ -65,23 +61,10 @@ struct LoadFile2Protocol {
|
||||||
) -> Status,
|
) -> Status,
|
||||||
|
|
||||||
// This is not part of the official protocol struct.
|
// This is not part of the official protocol struct.
|
||||||
file: RegularFile,
|
initrd_data: Vec<u8>,
|
||||||
range: Range<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoadFile2Protocol {
|
impl LoadFile2Protocol {
|
||||||
fn initrd_start(&self) -> usize {
|
|
||||||
self.range.start
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initrd_size(&self) -> usize {
|
|
||||||
if self.range.is_empty() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
self.range.end - self.range.start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_file(
|
fn load_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
_file_path: *const FfiDevicePath,
|
_file_path: *const FfiDevicePath,
|
||||||
|
@ -89,24 +72,21 @@ impl LoadFile2Protocol {
|
||||||
buffer_size: *mut usize,
|
buffer_size: *mut usize,
|
||||||
buffer: *mut c_void,
|
buffer: *mut c_void,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if buffer.is_null() || unsafe { *buffer_size } < self.initrd_size() {
|
if buffer.is_null() || unsafe { *buffer_size } < self.initrd_data.len() {
|
||||||
unsafe {
|
unsafe {
|
||||||
*buffer_size = self.initrd_size();
|
*buffer_size = self.initrd_data.len();
|
||||||
}
|
}
|
||||||
return Err(Status::BUFFER_TOO_SMALL.into());
|
return Err(Status::BUFFER_TOO_SMALL.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
self.file
|
|
||||||
.set_position(self.initrd_start().try_into().unwrap())?;
|
|
||||||
unsafe {
|
unsafe {
|
||||||
*buffer_size = self.initrd_size();
|
*buffer_size = self.initrd_data.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_slice: &mut [u8] =
|
let output_slice: &mut [u8] =
|
||||||
unsafe { &mut *slice_from_raw_parts_mut(buffer as *mut u8, *buffer_size) };
|
unsafe { &mut *slice_from_raw_parts_mut(buffer as *mut u8, *buffer_size) };
|
||||||
|
|
||||||
let read_bytes = self.file.read(output_slice).map_err(|e| e.status())?;
|
output_slice.copy_from_slice(&self.initrd_data);
|
||||||
assert_eq!(read_bytes, unsafe { *buffer_size });
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -134,73 +114,15 @@ pub struct InitrdLoader {
|
||||||
registered: bool,
|
registered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the data range of the initrd in the PE binary.
|
|
||||||
///
|
|
||||||
/// The initrd has to be embedded in the file as a .initrd PE section.
|
|
||||||
fn initrd_location(initrd_efi: &mut RegularFile) -> Result<Range<usize>> {
|
|
||||||
initrd_efi.set_position(0)?;
|
|
||||||
|
|
||||||
let file_data = read_all(initrd_efi)?;
|
|
||||||
let pe_binary = goblin::pe::PE::parse(&file_data).map_err(|_| Status::INVALID_PARAMETER)?;
|
|
||||||
|
|
||||||
pe_binary
|
|
||||||
.sections
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.name().unwrap() == ".initrd")
|
|
||||||
.map(|s| {
|
|
||||||
let section_start: usize = s.pointer_to_raw_data.try_into().unwrap();
|
|
||||||
let section_size: usize = s.size_of_raw_data.try_into().unwrap();
|
|
||||||
|
|
||||||
Range {
|
|
||||||
start: section_start,
|
|
||||||
end: section_start + section_size,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok_or_else(|| Status::END_OF_FILE.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the signature of the initrd.
|
|
||||||
///
|
|
||||||
/// For this to work, the initrd needs to be a PE binary. We misuse
|
|
||||||
/// [`BootServices::load_image`] for this.
|
|
||||||
fn initrd_verify(boot_services: &BootServices, initrd_efi: &mut RegularFile) -> Result<()> {
|
|
||||||
initrd_efi.set_position(0)?;
|
|
||||||
let file_data = read_all(initrd_efi)?;
|
|
||||||
|
|
||||||
let initrd_handle = boot_services.load_image(
|
|
||||||
boot_services.image_handle(),
|
|
||||||
LoadImageSource::FromBuffer {
|
|
||||||
buffer: &file_data,
|
|
||||||
file_path: None,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// If we get here, the security policy allowed loading the
|
|
||||||
// image. This means that it was signed with an acceptable key in
|
|
||||||
// the Secure Boot scenario.
|
|
||||||
|
|
||||||
boot_services.unload_image(initrd_handle)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitrdLoader {
|
impl InitrdLoader {
|
||||||
/// Create a new [`InitrdLoader`].
|
/// Create a new [`InitrdLoader`].
|
||||||
///
|
///
|
||||||
/// `handle` is the handle where the protocols are registered
|
/// `handle` is the handle where the protocols are registered
|
||||||
/// on. `file` is the file that is served to Linux.
|
/// on. `file` is the file that is served to Linux.
|
||||||
pub fn new(
|
pub fn new(boot_services: &BootServices, handle: Handle, initrd_data: Vec<u8>) -> Result<Self> {
|
||||||
boot_services: &BootServices,
|
|
||||||
handle: Handle,
|
|
||||||
mut file: RegularFile,
|
|
||||||
) -> Result<Self> {
|
|
||||||
initrd_verify(boot_services, &mut file)?;
|
|
||||||
|
|
||||||
let range = initrd_location(&mut file)?;
|
|
||||||
let mut proto = Box::pin(LoadFile2Protocol {
|
let mut proto = Box::pin(LoadFile2Protocol {
|
||||||
load_file: raw_load_file,
|
load_file: raw_load_file,
|
||||||
file,
|
initrd_data,
|
||||||
range,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux finds the right handle by looking for something that
|
// Linux finds the right handle by looking for something that
|
||||||
|
|
|
@ -9,7 +9,8 @@ mod linux_loader;
|
||||||
mod pe_section;
|
mod pe_section;
|
||||||
mod uefi_helpers;
|
mod uefi_helpers;
|
||||||
|
|
||||||
use pe_section::pe_section_as_string;
|
use blake3::Hash;
|
||||||
|
use pe_section::{pe_section, pe_section_as_string};
|
||||||
use uefi::{
|
use uefi::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
proto::{
|
proto::{
|
||||||
|
@ -52,9 +53,33 @@ struct EmbeddedConfiguration {
|
||||||
/// lanzaboote binary.
|
/// lanzaboote binary.
|
||||||
kernel_filename: CString16,
|
kernel_filename: CString16,
|
||||||
|
|
||||||
|
/// The cryptographic hash of the kernel.
|
||||||
|
kernel_hash: Hash,
|
||||||
|
|
||||||
/// The filename of the initrd to be passed to the kernel. See
|
/// The filename of the initrd to be passed to the kernel. See
|
||||||
/// `kernel_filename` for how to interpret these filenames.
|
/// `kernel_filename` for how to interpret these filenames.
|
||||||
initrd_filename: CString16,
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a filename from a PE section. The filename is stored as UTF-8.
|
||||||
|
fn extract_filename(file_data: &[u8], section: &str) -> Result<CString16> {
|
||||||
|
let filename = pe_section_as_string(file_data, section).ok_or(Status::INVALID_PARAMETER)?;
|
||||||
|
|
||||||
|
Ok(CString16::try_from(filename.as_str()).map_err(|_| Status::INVALID_PARAMETER)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a Blake3 hash from a PE section.
|
||||||
|
fn extract_hash(file_data: &[u8], section: &str) -> Result<Hash> {
|
||||||
|
let array: [u8; 32] = pe_section(file_data, section)
|
||||||
|
.ok_or(Status::INVALID_PARAMETER)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Status::INVALID_PARAMETER)?;
|
||||||
|
|
||||||
|
Ok(array.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmbeddedConfiguration {
|
impl EmbeddedConfiguration {
|
||||||
|
@ -62,14 +87,12 @@ impl EmbeddedConfiguration {
|
||||||
file.set_position(0)?;
|
file.set_position(0)?;
|
||||||
let file_data = read_all(file)?;
|
let file_data = read_all(file)?;
|
||||||
|
|
||||||
let kernel_filename =
|
|
||||||
pe_section_as_string(&file_data, ".kernelp").ok_or(Status::INVALID_PARAMETER)?;
|
|
||||||
let initrd_filename =
|
|
||||||
pe_section_as_string(&file_data, ".initrdp").ok_or(Status::INVALID_PARAMETER)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
kernel_filename: CString16::try_from(kernel_filename.as_str()).unwrap(),
|
kernel_filename: extract_filename(&file_data, ".kernelp")?,
|
||||||
initrd_filename: CString16::try_from(initrd_filename.as_str()).unwrap(),
|
kernel_hash: extract_hash(&file_data, ".kernelh")?,
|
||||||
|
|
||||||
|
initrd_filename: extract_filename(&file_data, ".initrdp")?,
|
||||||
|
initrd_hash: extract_hash(&file_data, ".initrdh")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +107,10 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
EmbeddedConfiguration::new(&mut booted_image_file(system_table.boot_services()).unwrap())
|
EmbeddedConfiguration::new(&mut booted_image_file(system_table.boot_services()).unwrap())
|
||||||
.expect("Failed to extract configuration from binary. Did you run lanzatool?");
|
.expect("Failed to extract configuration from binary. Did you run lanzatool?");
|
||||||
|
|
||||||
let mut kernel_file;
|
let kernel_data;
|
||||||
let initrd = {
|
let initrd_data;
|
||||||
|
|
||||||
|
{
|
||||||
let mut file_system = system_table
|
let mut file_system = system_table
|
||||||
.boot_services()
|
.boot_services()
|
||||||
.get_image_file_system(handle)
|
.get_image_file_system(handle)
|
||||||
|
@ -94,7 +119,7 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
.open_volume()
|
.open_volume()
|
||||||
.expect("Failed to find ESP root directory");
|
.expect("Failed to find ESP root directory");
|
||||||
|
|
||||||
kernel_file = root
|
let mut kernel_file = root
|
||||||
.open(
|
.open(
|
||||||
&config.kernel_filename,
|
&config.kernel_filename,
|
||||||
FileMode::Read,
|
FileMode::Read,
|
||||||
|
@ -104,23 +129,41 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
.into_regular_file()
|
.into_regular_file()
|
||||||
.expect("Kernel is not a regular file");
|
.expect("Kernel is not a regular file");
|
||||||
|
|
||||||
root.open(
|
kernel_data = read_all(&mut kernel_file).expect("Failed to read kernel file into memory");
|
||||||
|
|
||||||
|
let mut initrd_file = root
|
||||||
|
.open(
|
||||||
&config.initrd_filename,
|
&config.initrd_filename,
|
||||||
FileMode::Read,
|
FileMode::Read,
|
||||||
FileAttribute::empty(),
|
FileAttribute::empty(),
|
||||||
)
|
)
|
||||||
.expect("Failed to open initrd for reading")
|
.expect("Failed to open initrd for reading")
|
||||||
.into_regular_file()
|
.into_regular_file()
|
||||||
.expect("Initrd is not a regular file")
|
.expect("Initrd is not a regular file");
|
||||||
};
|
|
||||||
|
initrd_data = read_all(&mut initrd_file).expect("Failed to read kernel file into memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if blake3::hash(&kernel_data) != config.kernel_hash {
|
||||||
|
system_table
|
||||||
|
.stdout()
|
||||||
|
.output_string(cstr16!("Hash mismatch for kernel. Refusing to load!\r\n"))
|
||||||
|
.unwrap();
|
||||||
|
return Status::SECURITY_VIOLATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if blake3::hash(&initrd_data) != config.initrd_hash {
|
||||||
|
system_table
|
||||||
|
.stdout()
|
||||||
|
.output_string(cstr16!("Hash mismatch for initrd. Refusing to load!\r\n"))
|
||||||
|
.unwrap();
|
||||||
|
return Status::SECURITY_VIOLATION;
|
||||||
|
}
|
||||||
|
|
||||||
let kernel_cmdline =
|
let kernel_cmdline =
|
||||||
booted_image_cmdline(system_table.boot_services()).expect("Failed to fetch command line");
|
booted_image_cmdline(system_table.boot_services()).expect("Failed to fetch command line");
|
||||||
|
|
||||||
let kernel_handle = {
|
let kernel_handle = {
|
||||||
let kernel_data =
|
|
||||||
read_all(&mut kernel_file).expect("Failed to read kernel file into memory");
|
|
||||||
|
|
||||||
system_table
|
system_table
|
||||||
.boot_services()
|
.boot_services()
|
||||||
.load_image(
|
.load_image(
|
||||||
|
@ -145,7 +188,7 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut initrd_loader = InitrdLoader::new(system_table.boot_services(), handle, initrd)
|
let mut initrd_loader = InitrdLoader::new(system_table.boot_services(), handle, initrd_data)
|
||||||
.expect("Failed to load the initrd. It may not be there or it is not signed");
|
.expect("Failed to load the initrd. It may not be there or it is not signed");
|
||||||
let status = system_table
|
let status = system_table
|
||||||
.boot_services()
|
.boot_services()
|
||||||
|
|
|
@ -8,6 +8,18 @@ version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -31,6 +43,35 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -74,6 +115,33 @@ dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -83,6 +151,16 @@ dependencies = [
|
||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "goblin"
|
name = "goblin"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -129,6 +207,7 @@ name = "lanzatool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
"goblin",
|
"goblin",
|
||||||
"nix",
|
"nix",
|
||||||
|
@ -305,6 +384,12 @@ version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
|
@ -339,6 +424,12 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
|
|
@ -13,3 +13,4 @@ nix = { version = "0.25.0", default-features = false, features = [ "fs" ] }
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
serde_json = "1.0.89"
|
serde_json = "1.0.89"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
|
blake3 = "1.3.3"
|
||||||
|
|
|
@ -59,14 +59,11 @@ impl Commands {
|
||||||
fn install(args: InstallCommand) -> Result<()> {
|
fn install(args: InstallCommand) -> Result<()> {
|
||||||
let lanzaboote_stub =
|
let lanzaboote_stub =
|
||||||
std::env::var("LANZABOOTE_STUB").context("Failed to read LANZABOOTE_STUB env variable")?;
|
std::env::var("LANZABOOTE_STUB").context("Failed to read LANZABOOTE_STUB env variable")?;
|
||||||
let initrd_stub = std::env::var("LANZABOOTE_INITRD_STUB")
|
|
||||||
.context("Failed to read LANZABOOTE_INITRD_STUB env variable")?;
|
|
||||||
|
|
||||||
let key_pair = KeyPair::new(&args.public_key, &args.private_key);
|
let key_pair = KeyPair::new(&args.public_key, &args.private_key);
|
||||||
|
|
||||||
install::Installer::new(
|
install::Installer::new(
|
||||||
PathBuf::from(lanzaboote_stub),
|
PathBuf::from(lanzaboote_stub),
|
||||||
PathBuf::from(initrd_stub),
|
|
||||||
key_pair,
|
key_pair,
|
||||||
args.pki_bundle,
|
args.pki_bundle,
|
||||||
args.auto_enroll,
|
args.auto_enroll,
|
||||||
|
|
|
@ -14,7 +14,6 @@ use crate::signature::KeyPair;
|
||||||
|
|
||||||
pub struct Installer {
|
pub struct Installer {
|
||||||
lanzaboote_stub: PathBuf,
|
lanzaboote_stub: PathBuf,
|
||||||
initrd_stub: PathBuf,
|
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
_pki_bundle: Option<PathBuf>,
|
_pki_bundle: Option<PathBuf>,
|
||||||
_auto_enroll: bool,
|
_auto_enroll: bool,
|
||||||
|
@ -25,7 +24,6 @@ pub struct Installer {
|
||||||
impl Installer {
|
impl Installer {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
lanzaboote_stub: PathBuf,
|
lanzaboote_stub: PathBuf,
|
||||||
initrd_stub: PathBuf,
|
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
_pki_bundle: Option<PathBuf>,
|
_pki_bundle: Option<PathBuf>,
|
||||||
_auto_enroll: bool,
|
_auto_enroll: bool,
|
||||||
|
@ -34,7 +32,6 @@ impl Installer {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
lanzaboote_stub,
|
lanzaboote_stub,
|
||||||
initrd_stub,
|
|
||||||
key_pair,
|
key_pair,
|
||||||
_pki_bundle,
|
_pki_bundle,
|
||||||
_auto_enroll,
|
_auto_enroll,
|
||||||
|
@ -68,14 +65,10 @@ impl Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_generation(&self, generation: &Generation) -> Result<()> {
|
fn install_generation(&self, generation: &Generation) -> Result<()> {
|
||||||
println!("Reading bootspec...");
|
|
||||||
|
|
||||||
let bootspec = &generation.bootspec;
|
let bootspec = &generation.bootspec;
|
||||||
|
|
||||||
let esp_paths = EspPaths::new(&self.esp, generation)?;
|
let esp_paths = EspPaths::new(&self.esp, generation)?;
|
||||||
|
|
||||||
println!("Assembling lanzaboote image...");
|
|
||||||
|
|
||||||
let kernel_cmdline =
|
let kernel_cmdline =
|
||||||
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
|
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
|
||||||
|
|
||||||
|
@ -87,6 +80,31 @@ impl Installer {
|
||||||
// TODO(Raito): prove to niksnur this is actually acceptable.
|
// TODO(Raito): prove to niksnur this is actually acceptable.
|
||||||
let secure_temp_dir = tempdir()?;
|
let secure_temp_dir = tempdir()?;
|
||||||
|
|
||||||
|
println!("Appending secrets to initrd...");
|
||||||
|
|
||||||
|
let initrd_location = secure_temp_dir.path().join("initrd");
|
||||||
|
copy(&bootspec.initrd, &initrd_location)?;
|
||||||
|
if let Some(initrd_secrets_script) = &bootspec.initrd_secrets {
|
||||||
|
append_initrd_secrets(initrd_secrets_script, &initrd_location)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemd_boot = bootspec
|
||||||
|
.toplevel
|
||||||
|
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
|
||||||
|
|
||||||
|
[
|
||||||
|
(&systemd_boot, &esp_paths.efi_fallback),
|
||||||
|
(&systemd_boot, &esp_paths.systemd_boot),
|
||||||
|
(&bootspec.kernel, &esp_paths.kernel),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.try_for_each(|(from, to)| install_signed(&self.key_pair, from, to))?;
|
||||||
|
|
||||||
|
// The initrd doesn't need to be signed. Lanzaboote has its
|
||||||
|
// hash embedded and will refuse loading it when the hash
|
||||||
|
// mismatches.
|
||||||
|
install(&initrd_location, &esp_paths.initrd).context("Failed to install initrd to ESP")?;
|
||||||
|
|
||||||
let lanzaboote_image = pe::lanzaboote_image(
|
let lanzaboote_image = pe::lanzaboote_image(
|
||||||
&secure_temp_dir,
|
&secure_temp_dir,
|
||||||
&self.lanzaboote_stub,
|
&self.lanzaboote_stub,
|
||||||
|
@ -98,40 +116,17 @@ impl Installer {
|
||||||
)
|
)
|
||||||
.context("Failed to assemble stub")?;
|
.context("Failed to assemble stub")?;
|
||||||
|
|
||||||
println!("Wrapping initrd into a PE binary...");
|
install_signed(
|
||||||
|
&self.key_pair,
|
||||||
|
&lanzaboote_image,
|
||||||
|
&esp_paths.lanzaboote_image,
|
||||||
|
)
|
||||||
|
.context("Failed to install lanzaboote")?;
|
||||||
|
|
||||||
let initrd_location = secure_temp_dir.path().join("initrd");
|
// Sync files to persistent storage. This may improve the
|
||||||
copy(&bootspec.initrd, &initrd_location)?;
|
// chance of a consistent boot directory in case the system
|
||||||
if let Some(initrd_secrets_script) = &bootspec.initrd_secrets {
|
// crashes.
|
||||||
append_initrd_secrets(initrd_secrets_script, &initrd_location)?;
|
|
||||||
}
|
|
||||||
let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location)
|
|
||||||
.context("Failed to assemble stub")?;
|
|
||||||
|
|
||||||
println!("Sign and copy files to EFI system partition...");
|
|
||||||
|
|
||||||
let systemd_boot = bootspec
|
|
||||||
.toplevel
|
|
||||||
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
|
|
||||||
|
|
||||||
let files_to_copy_and_sign = [
|
|
||||||
(&systemd_boot, &esp_paths.efi_fallback),
|
|
||||||
(&systemd_boot, &esp_paths.systemd_boot),
|
|
||||||
(&lanzaboote_image, &esp_paths.lanzaboote_image),
|
|
||||||
(&bootspec.kernel, &esp_paths.kernel),
|
|
||||||
(&wrapped_initrd, &esp_paths.initrd),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (from, to) in files_to_copy_and_sign {
|
|
||||||
println!("Signing {}...", to.display());
|
|
||||||
|
|
||||||
ensure_parent_dir(to);
|
|
||||||
self.key_pair.sign_and_copy(from, to).with_context(|| {
|
|
||||||
format!("Failed to copy and sign file from {:?} to {:?}", from, to)
|
|
||||||
})?;
|
|
||||||
// Call sync to improve the likelihood that file is actually written to disk
|
|
||||||
sync();
|
sync();
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Successfully installed lanzaboote to '{}'",
|
"Successfully installed lanzaboote to '{}'",
|
||||||
|
@ -142,6 +137,38 @@ impl Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install a PE file. The PE gets signed in the process.
|
||||||
|
///
|
||||||
|
/// The file is only signed and copied if it doesn't exist at the destination
|
||||||
|
fn install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> {
|
||||||
|
if to.exists() {
|
||||||
|
println!("{} already exists, skipping...", to.display());
|
||||||
|
} else {
|
||||||
|
println!("Signing and installing {}...", to.display());
|
||||||
|
ensure_parent_dir(to);
|
||||||
|
key_pair
|
||||||
|
.sign_and_copy(from, to)
|
||||||
|
.with_context(|| format!("Failed to copy and sign file from {:?} to {:?}", from, to))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install an arbitrary file
|
||||||
|
///
|
||||||
|
/// The file is only copied if it doesn't exist at the destination
|
||||||
|
fn install(from: &Path, to: &Path) -> Result<()> {
|
||||||
|
if to.exists() {
|
||||||
|
println!("{} already exists, skipping...", to.display());
|
||||||
|
} else {
|
||||||
|
println!("Installing {}...", to.display());
|
||||||
|
ensure_parent_dir(to);
|
||||||
|
copy(from, to)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn append_initrd_secrets(
|
pub fn append_initrd_secrets(
|
||||||
append_initrd_secrets_path: &Path,
|
append_initrd_secrets_path: &Path,
|
||||||
initrd_path: &PathBuf,
|
initrd_path: &PathBuf,
|
||||||
|
|
|
@ -12,6 +12,11 @@ use crate::utils;
|
||||||
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
/// Attach all information that lanzaboote needs into the PE binary.
|
||||||
|
///
|
||||||
|
/// When this function is called the referenced files already need to
|
||||||
|
/// be present in the ESP. This is required, because we need to read
|
||||||
|
/// them to compute hashes.
|
||||||
pub fn lanzaboote_image(
|
pub fn lanzaboote_image(
|
||||||
target_dir: &TempDir,
|
target_dir: &TempDir,
|
||||||
lanzaboote_stub: &Path,
|
lanzaboote_stub: &Path,
|
||||||
|
@ -21,49 +26,67 @@ pub fn lanzaboote_image(
|
||||||
initrd_path: &Path,
|
initrd_path: &Path,
|
||||||
esp: &Path,
|
esp: &Path,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
// objcopy copies files into the PE binary. That's why we have to write the contents
|
// objcopy can only copy files into the PE binary. That's why we
|
||||||
// of some bootspec properties to disk
|
// have to write the contents of some bootspec properties to disk.
|
||||||
let (kernel_cmdline_file, _) =
|
let kernel_cmdline_file = write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?;
|
||||||
write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?;
|
|
||||||
let (kernel_path_file, _) = write_to_tmp(
|
let kernel_path_file = write_to_tmp(
|
||||||
target_dir,
|
target_dir,
|
||||||
"kernel-esp-path",
|
"kernel-esp-path",
|
||||||
esp_relative_path_string(esp, kernel_path),
|
esp_relative_path_string(esp, kernel_path),
|
||||||
)?;
|
)?;
|
||||||
let (initrd_path_file, _) = write_to_tmp(
|
let kernel_hash_file = write_to_tmp(
|
||||||
|
target_dir,
|
||||||
|
"kernel-hash",
|
||||||
|
file_hash(kernel_path)?.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let initrd_path_file = write_to_tmp(
|
||||||
target_dir,
|
target_dir,
|
||||||
"initrd-esp-path",
|
"initrd-esp-path",
|
||||||
esp_relative_path_string(esp, initrd_path),
|
esp_relative_path_string(esp, initrd_path),
|
||||||
)?;
|
)?;
|
||||||
|
let initrd_hash_file = write_to_tmp(
|
||||||
|
target_dir,
|
||||||
|
"initrd-hash",
|
||||||
|
file_hash(initrd_path)?.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
let os_release_offs = stub_offset(lanzaboote_stub)?;
|
let os_release_offs = stub_offset(lanzaboote_stub)?;
|
||||||
let kernel_cmdline_offs = os_release_offs + file_size(os_release)?;
|
let kernel_cmdline_offs = os_release_offs + file_size(os_release)?;
|
||||||
let initrd_path_offs = kernel_cmdline_offs + file_size(&kernel_cmdline_file)?;
|
let initrd_path_offs = kernel_cmdline_offs + file_size(&kernel_cmdline_file)?;
|
||||||
let kernel_path_offs = initrd_path_offs + file_size(&initrd_path_file)?;
|
let kernel_path_offs = initrd_path_offs + file_size(&initrd_path_file)?;
|
||||||
|
let initrd_hash_offs = kernel_path_offs + file_size(&kernel_path_file)?;
|
||||||
|
let kernel_hash_offs = initrd_hash_offs + file_size(&initrd_hash_file)?;
|
||||||
|
|
||||||
let sections = vec![
|
let sections = vec![
|
||||||
s(".osrel", os_release, os_release_offs),
|
s(".osrel", os_release, os_release_offs),
|
||||||
s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs),
|
s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs),
|
||||||
s(".initrdp", initrd_path_file, initrd_path_offs),
|
s(".initrdp", initrd_path_file, initrd_path_offs),
|
||||||
s(".kernelp", kernel_path_file, kernel_path_offs),
|
s(".kernelp", kernel_path_file, kernel_path_offs),
|
||||||
|
s(".initrdh", initrd_hash_file, initrd_hash_offs),
|
||||||
|
s(".kernelh", kernel_hash_file, kernel_hash_offs),
|
||||||
];
|
];
|
||||||
|
|
||||||
wrap_in_pe(target_dir, "lanzaboote-stub.efi", lanzaboote_stub, sections)
|
wrap_in_pe(target_dir, "lanzaboote-stub.efi", lanzaboote_stub, sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_initrd(target_dir: &TempDir, initrd_stub: &Path, initrd: &Path) -> Result<PathBuf> {
|
/// Compute the blake3 hash of a file.
|
||||||
let initrd_offs = stub_offset(initrd_stub)?;
|
fn file_hash(file: &Path) -> Result<blake3::Hash> {
|
||||||
let sections = vec![s(".initrd", initrd, initrd_offs)];
|
Ok(blake3::hash(&fs::read(file)?))
|
||||||
wrap_in_pe(target_dir, "wrapped-initrd.exe", initrd_stub, sections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take a PE binary stub and attach sections to it.
|
||||||
|
///
|
||||||
|
/// The result is then written to a new file. Returns the filename of
|
||||||
|
/// the generated file.
|
||||||
fn wrap_in_pe(
|
fn wrap_in_pe(
|
||||||
target_dir: &TempDir,
|
target_dir: &TempDir,
|
||||||
filename: &str,
|
output_filename: &str,
|
||||||
stub: &Path,
|
stub: &Path,
|
||||||
sections: Vec<Section>,
|
sections: Vec<Section>,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
let image_path = target_dir.path().join(filename);
|
let image_path = target_dir.path().join(output_filename);
|
||||||
let _ = fs::OpenOptions::new()
|
let _ = fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
|
@ -117,21 +140,26 @@ fn s(name: &'static str, file_path: impl AsRef<Path>, offset: u64) -> Section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a `u8` slice to a temporary file.
|
||||||
fn write_to_tmp(
|
fn write_to_tmp(
|
||||||
secure_temp: &TempDir,
|
secure_temp: &TempDir,
|
||||||
filename: &str,
|
filename: &str,
|
||||||
contents: impl AsRef<[u8]>,
|
contents: impl AsRef<[u8]>,
|
||||||
) -> Result<(PathBuf, fs::File)> {
|
) -> Result<PathBuf> {
|
||||||
|
let path = secure_temp.path().join(filename);
|
||||||
|
|
||||||
let mut tmpfile = fs::OpenOptions::new()
|
let mut tmpfile = fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.mode(0o600)
|
.mode(0o600)
|
||||||
.open(secure_temp.path().join(filename))
|
.open(&path)
|
||||||
.context("Failed to create tempfile")?;
|
.context("Failed to create tempfile")?;
|
||||||
|
|
||||||
tmpfile
|
tmpfile
|
||||||
.write_all(contents.as_ref())
|
.write_all(contents.as_ref())
|
||||||
.context("Failed to write to tempfile")?;
|
.context("Failed to write to tempfile")?;
|
||||||
Ok((secure_temp.path().join(filename), tmpfile))
|
|
||||||
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn esp_relative_path_string(esp: &Path, path: &Path) -> String {
|
fn esp_relative_path_string(esp: &Path, path: &Path) -> String {
|
||||||
|
|
Loading…
Reference in New Issue