blob: c05825c1de52f5d5421a2cf88124184ee7058391 [file] [log] [blame]
use crate::handover;
use core::convert::TryInto;
use core::ffi::c_void;
use core::mem;
use log::{error, info};
use uefi::prelude::*;
use uefi::proto::loaded_image::LoadedImage;
use uefi::table::boot::MemoryType;
use uefi::table::{Boot, SystemTable};
use uefi::{Char16, Handle, Result, Status};
// TODO, copied from uefi-rs so that we can set some of the non-public
// options. Should just make them public...
#[repr(C)]
struct MyLoadedImage {
revision: u32,
parent_handle: Handle,
system_table: *const c_void,
// Source location of the image
device_handle: Handle,
_file_path: *const c_void, // TODO: not supported yet
_reserved: *const c_void,
// Image load options
load_options_size: u32,
load_options: *const Char16,
// Location where image was loaded
image_base: *const c_void,
image_size: u64,
image_code_type: MemoryType,
image_data_type: MemoryType,
unload: extern "efiapi" fn(image_handle: Handle) -> Status,
}
fn get_pe_entry_point(data: &[u8]) -> Option<u32> {
let pe_header_offset =
u32::from_le_bytes(data.get(0x3c..0x3c + 4)?.try_into().ok()?) as usize;
let pe_header = &data.get(pe_header_offset..)?;
Some(u32::from_le_bytes(
pe_header.get(0x28..0x28 + 4)?.try_into().ok()?,
))
}
fn modify_loaded_image(
image: Handle,
system_table: &SystemTable<Boot>,
kernel_data: &[u8],
cmdline_ucs2: &[Char16],
) -> Result<()> {
let bt = system_table.boot_services();
let li = bt.handle_protocol::<LoadedImage>(image).log_warning()?;
let li: &mut LoadedImage = unsafe { &mut *li.get() };
let li: &mut MyLoadedImage =
unsafe { &mut *(li as *mut LoadedImage as *mut MyLoadedImage) };
li.image_base = kernel_data.as_ptr() as *const c_void;
li.image_size = if let Ok(size) = kernel_data.len().try_into() {
size
} else {
error!("kernel data is too big");
return Status::LOAD_ERROR.into();
};
li.load_options = cmdline_ucs2.as_ptr();
li.load_options_size = if let Ok(size) = (2 * cmdline_ucs2.len()).try_into()
{
size
} else {
error!("command-line data is too big");
return Status::LOAD_ERROR.into();
};
Status::SUCCESS.into()
}
/// Hand off control to the Linux EFI stub.
///
/// As mentioned in [1], the preferred method for loading the kernel
/// on UEFI is to build in the EFI stub and run it as a normal PE/COFF
/// executable. This is indeed much simpler than trying to use the EFI
/// handover protocol, which is not fully documented. The kernel's PE
/// header does not require any relocations to be performed, so the
/// only thing we need to get from the header is the entry point.
///
/// Note that we can't use LoadImage+StartImage for this, because with
/// secure boot enabled it would try to verify the signature of the
/// kernel which would fail unless we signed the kernel in the way
/// UEFI expects. Since we have already verified the kernel via the
/// vboot structures (as well as the command line parameters), this
/// would be an unnecessary verification.
///
/// [1]: kernel.org/doc/html/latest/x86/boot.html#efi-handover-protocol-deprecated
pub fn execute_linux_efi_stub(
kernel_data: &[u8],
crdyboot_image: Handle,
system_table: SystemTable<Boot>,
cmdline_ucs2: &[Char16],
) -> Result<()> {
let entry_point_offset =
if let Some(offset) = get_pe_entry_point(kernel_data) {
offset
} else {
error!("failed to get PE entry point");
return Status::LOAD_ERROR.into();
};
// Ideally we could create a new image here, but I'm not sure
// there's any way to do that without calling LoadImage, which we
// can't do due to secure boot. This is the same method shim uses:
// modify the existing image's parameters.
modify_loaded_image(
crdyboot_image,
&system_table,
kernel_data,
cmdline_ucs2,
)
.log_warning()?;
let entry_point = ((kernel_data.as_ptr() as u64)
+ entry_point_offset as u64) as *const ();
unsafe {
type Entrypoint = unsafe extern "efiapi" fn(Handle, SystemTable<Boot>);
let entry_point: Entrypoint = mem::transmute(entry_point);
(entry_point)(crdyboot_image, system_table);
}
unreachable!("kernel returned control");
}
pub fn execute_linux_kernel(
kernel_data: &[u8],
crdyboot_image: Handle,
system_table: SystemTable<Boot>,
cmdline: &str,
cmdline_ucs2: &[Char16],
) -> Result<()> {
let is_64bit = match mem::size_of::<usize>() {
8 => true,
4 => false,
other => panic!("invalid size of usize: {}", other),
};
if is_64bit {
execute_linux_efi_stub(
kernel_data,
crdyboot_image,
system_table,
cmdline_ucs2,
)
} else {
info!("using handover!");
handover::execute_linux_kernel_32(
kernel_data,
crdyboot_image,
system_table,
cmdline,
)
}
}