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).
2023-10-01 13:21:11 -04:00
|
|
|
use log::{error, warn};
|
2023-05-22 17:28:59 -04:00
|
|
|
use sha2::{Digest, Sha256};
|
2023-10-02 02:33:04 -04:00
|
|
|
use uefi::{fs::FileSystem, prelude::*, CString16, Result};
|
2023-05-22 17:28:59 -04:00
|
|
|
|
2023-10-02 02:33:04 -04:00
|
|
|
use crate::common::{boot_linux_unchecked, extract_string, get_cmdline, get_secure_boot_status};
|
2023-07-23 12:46:47 -04:00
|
|
|
use linux_bootloader::pe_section::pe_section;
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
use linux_bootloader::uefi_helpers::booted_image_file;
|
2023-05-22 17:28:59 -04:00
|
|
|
|
|
|
|
type Hash = sha2::digest::Output<Sha256>;
|
|
|
|
|
|
|
|
/// 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<Hash> {
|
|
|
|
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<Self> {
|
|
|
|
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")?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
/// Verify some data against its expected hash.
|
2023-05-22 17:28:59 -04:00
|
|
|
///
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
/// In case of a mismatch:
|
|
|
|
/// * 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.
|
|
|
|
fn check_hash(data: &[u8], expected_hash: Hash, name: &str, secure_boot: bool) -> uefi::Result<()> {
|
|
|
|
let hash_correct = Sha256::digest(data) == expected_hash;
|
|
|
|
if !hash_correct {
|
|
|
|
if secure_boot {
|
|
|
|
error!("{name} hash does not match!");
|
|
|
|
return Err(Status::SECURITY_VIOLATION.into());
|
|
|
|
} else {
|
|
|
|
warn!("{name} hash does not match! Continuing anyway.");
|
|
|
|
}
|
2023-05-22 17:28:59 -04:00
|
|
|
}
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
Ok(())
|
2023-05-22 17:28:59 -04:00
|
|
|
}
|
|
|
|
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
pub fn boot_linux(handle: Handle, mut system_table: SystemTable<Boot>) -> uefi::Result<()> {
|
2023-05-22 17:28:59 -04:00
|
|
|
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?")
|
|
|
|
};
|
|
|
|
|
2023-10-02 02:33:04 -04:00
|
|
|
let secure_boot_enabled = get_secure_boot_status(system_table.runtime_services());
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
|
2023-05-22 17:28:59 -04:00
|
|
|
let kernel_data;
|
|
|
|
let initrd_data;
|
|
|
|
|
|
|
|
{
|
2023-10-12 06:12:34 -04:00
|
|
|
let file_system = system_table
|
2023-05-22 17:28:59 -04:00
|
|
|
.boot_services()
|
|
|
|
.get_image_file_system(handle)
|
|
|
|
.expect("Failed to get file system handle");
|
2023-10-12 06:12:34 -04:00
|
|
|
let mut file_system = FileSystem::new(file_system);
|
2023-05-22 17:28:59 -04:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2023-10-02 02:33:04 -04:00
|
|
|
let cmdline = get_cmdline(
|
|
|
|
&config.cmdline,
|
|
|
|
system_table.boot_services(),
|
|
|
|
secure_boot_enabled,
|
|
|
|
);
|
|
|
|
|
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).
2023-10-01 13:21:11 -04:00
|
|
|
check_hash(
|
|
|
|
&kernel_data,
|
|
|
|
config.kernel_hash,
|
|
|
|
"Kernel",
|
|
|
|
secure_boot_enabled,
|
|
|
|
)?;
|
|
|
|
check_hash(
|
|
|
|
&initrd_data,
|
|
|
|
config.initrd_hash,
|
|
|
|
"Initrd",
|
|
|
|
secure_boot_enabled,
|
|
|
|
)?;
|
2023-05-22 17:28:59 -04:00
|
|
|
|
2023-10-02 02:33:04 -04:00
|
|
|
boot_linux_unchecked(handle, system_table, kernel_data, &cmdline, initrd_data)
|
2023-05-22 17:28:59 -04:00
|
|
|
}
|