stub: make handling of insecure boot more explicit
When Secure Boot is not available (unsupported or disabled), Lanzaboote will attempt to boot kernels and initrds even when they fail the hash verification. Previously, this would happen by falling back to use LoadImage on the kernel, which fails if Secure Boot is available, as the kernel is not signed. The SecureBoot variable offers a more explicit way of checking whether Secure Boot is available. If the firmware supports Secure Boot, it initializes this variable to 1 if it is enabled, and to 0 if it is disabled. Applications are not supposed to modify this variable, and in particular, since only trusted applications are loaded when Secure Boot is active, we can assume it is never changed to 0 or deleted if Secure Boot is active. Hence, we can be sure of Secure Boot being inactive if this variable is absent or set to 0, and thus treat all hash verification errors as non-fatal and proceed to boot arbitrary kernels and initrds (a warning is still logged in this case). In all other cases, we treat all hash verification failures as fatal security violations, as it must be done in the case where Secure Boot is active (it is expected that this does not lead to any false positives in practice, unless there are bigger problems anyway).
This commit is contained in:
parent
62ffd894f0
commit
db39223a7c
|
@ -144,7 +144,7 @@ let
|
||||||
machine.crash()
|
machine.crash()
|
||||||
machine.start()
|
machine.start()
|
||||||
'' + (if useSecureBoot then ''
|
'' + (if useSecureBoot then ''
|
||||||
machine.wait_for_console_text("Hash mismatch")
|
machine.wait_for_console_text("hash does not match")
|
||||||
'' else ''
|
'' else ''
|
||||||
# Just check that the system came up.
|
# Just check that the system came up.
|
||||||
print(machine.succeed("bootctl", timeout=120))
|
print(machine.succeed("bootctl", timeout=120))
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
|
|
||||||
#[cfg(feature = "thin")]
|
#[cfg(feature = "thin")]
|
||||||
{
|
{
|
||||||
status = thin::boot_linux(handle, system_table)
|
status = thin::boot_linux(handle, system_table).status()
|
||||||
}
|
}
|
||||||
|
|
||||||
status
|
status
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use alloc::vec::Vec;
|
use log::{error, warn};
|
||||||
use log::warn;
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use uefi::fs::FileSystem;
|
use uefi::{fs::FileSystem, guid, prelude::*, table::runtime::VariableVendor, CString16, Result};
|
||||||
use uefi::{prelude::*, proto::loaded_image::LoadedImage, CStr16, CString16, Result};
|
|
||||||
|
|
||||||
use crate::common::{boot_linux_unchecked, extract_string};
|
use crate::common::{boot_linux_unchecked, extract_string};
|
||||||
use linux_bootloader::pe_section::pe_section;
|
use linux_bootloader::pe_section::pe_section;
|
||||||
use linux_bootloader::{linux_loader::InitrdLoader, uefi_helpers::booted_image_file};
|
use linux_bootloader::uefi_helpers::booted_image_file;
|
||||||
|
|
||||||
type Hash = sha2::digest::Output<Sha256>;
|
type Hash = sha2::digest::Output<Sha256>;
|
||||||
|
|
||||||
|
@ -59,53 +57,25 @@ impl EmbeddedConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Boot the Linux kernel via the UEFI PE loader.
|
/// Verify some data against its expected hash.
|
||||||
///
|
///
|
||||||
/// This should only succeed when UEFI Secure Boot is off (or
|
/// In case of a mismatch:
|
||||||
/// broken...), because the Lanzaboote tool does not sign the kernel.
|
/// * If Secure Boot is active, an error message is logged, and the SECURITY_VIOLATION error is returned to stop the boot.
|
||||||
///
|
/// * If Secure Boot is not active, only a warning is logged, and the boot process is allowed to continue.
|
||||||
/// In essence, we can use this routine to detect whether Secure Boot
|
fn check_hash(data: &[u8], expected_hash: Hash, name: &str, secure_boot: bool) -> uefi::Result<()> {
|
||||||
/// is actually enabled.
|
let hash_correct = Sha256::digest(data) == expected_hash;
|
||||||
fn boot_linux_uefi(
|
if !hash_correct {
|
||||||
handle: Handle,
|
if secure_boot {
|
||||||
system_table: SystemTable<Boot>,
|
error!("{name} hash does not match!");
|
||||||
kernel_data: Vec<u8>,
|
return Err(Status::SECURITY_VIOLATION.into());
|
||||||
kernel_cmdline: &CStr16,
|
} else {
|
||||||
initrd_data: Vec<u8>,
|
warn!("{name} hash does not match! Continuing anyway.");
|
||||||
) -> 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)?;
|
Ok(())
|
||||||
|
|
||||||
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<Boot>) -> Status {
|
pub fn boot_linux(handle: Handle, mut system_table: SystemTable<Boot>) -> uefi::Result<()> {
|
||||||
uefi_services::init(&mut system_table).unwrap();
|
uefi_services::init(&mut system_table).unwrap();
|
||||||
|
|
||||||
// SAFETY: We get a slice that represents our currently running
|
// SAFETY: We get a slice that represents our currently running
|
||||||
|
@ -121,6 +91,40 @@ pub fn boot_linux(handle: Handle, mut system_table: SystemTable<Boot>) -> Status
|
||||||
.expect("Failed to extract configuration from binary. Did you run lzbt?")
|
.expect("Failed to extract configuration from binary. Did you run lzbt?")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The firmware initialized SecureBoot to 1 if performing signature checks, and 0 if it doesn't.
|
||||||
|
// Applications are not supposed to modify this variable (in particular, don't change the value from 1 to 0).
|
||||||
|
let secure_boot_enabled = system_table
|
||||||
|
.runtime_services()
|
||||||
|
.get_variable(
|
||||||
|
cstr16!("SecureBoot"),
|
||||||
|
&VariableVendor(guid!("8be4df61-93ca-11d2-aa0d-00e098032b8c")),
|
||||||
|
&mut [1],
|
||||||
|
)
|
||||||
|
.and_then(|(value, _)| match value {
|
||||||
|
[0] => Ok(false),
|
||||||
|
[1] => Ok(true),
|
||||||
|
[v] => {
|
||||||
|
warn!(
|
||||||
|
"Unexpected value of SecureBoot variable: {v}. Performing verification anyway."
|
||||||
|
);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
_ => Err(Status::BAD_BUFFER_SIZE.into()),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
if err.status() == Status::NOT_FOUND {
|
||||||
|
warn!("SecureBoot variable not found. Assuming Secure Boot is not supported.");
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
warn!("Failed to read SecureBoot variable: {err}. Performing verification anyway.");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !secure_boot_enabled {
|
||||||
|
warn!("Secure Boot is not active!");
|
||||||
|
}
|
||||||
|
|
||||||
let kernel_data;
|
let kernel_data;
|
||||||
let initrd_data;
|
let initrd_data;
|
||||||
|
|
||||||
|
@ -139,18 +143,19 @@ pub fn boot_linux(handle: Handle, mut system_table: SystemTable<Boot>) -> Status
|
||||||
.expect("Failed to read initrd file into memory");
|
.expect("Failed to read initrd file into memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_kernel_hash_correct = Sha256::digest(&kernel_data) == config.kernel_hash;
|
check_hash(
|
||||||
let is_initrd_hash_correct = Sha256::digest(&initrd_data) == config.initrd_hash;
|
&kernel_data,
|
||||||
|
config.kernel_hash,
|
||||||
|
"Kernel",
|
||||||
|
secure_boot_enabled,
|
||||||
|
)?;
|
||||||
|
check_hash(
|
||||||
|
&initrd_data,
|
||||||
|
config.initrd_hash,
|
||||||
|
"Initrd",
|
||||||
|
secure_boot_enabled,
|
||||||
|
)?;
|
||||||
|
|
||||||
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(
|
boot_linux_unchecked(
|
||||||
handle,
|
handle,
|
||||||
system_table,
|
system_table,
|
||||||
|
@ -158,38 +163,4 @@ pub fn boot_linux(handle: Handle, mut system_table: SystemTable<Boot>) -> Status
|
||||||
&config.cmdline,
|
&config.cmdline,
|
||||||
initrd_data,
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue