basic functionality

This commit is contained in:
2023-03-16 22:08:38 +01:00
commit fc3f5f27b4
12 changed files with 591 additions and 0 deletions

89
src/initrd.rs Normal file
View File

@ -0,0 +1,89 @@
use core::{ffi::c_void, mem::MaybeUninit};
use uefi::{
guid,
prelude::BootServices,
proto::device_path::{build, DevicePath},
Identify, Status,
};
const LOADFILE2_GUID: uefi::Guid = guid!("4006c0c1-fcb3-403e-996d-4a6c8724e06d");
const LINUX_INITRD_DEVICE_GUID: uefi::Guid = guid!("5568e427-68fc-4f3d-ac74-ca555231cc68");
/// Extends the LoadFile2 interface with initrd buffer location and size
pub struct Loader {
pub _handler: *const c_void,
pub buf: *const c_void,
pub size: usize,
}
/// Creates a loader from buffer memory location and size
pub fn make_loader(pointer: *const c_void, size: usize) -> Loader {
Loader {
_handler: initrd_loadfile2 as *const c_void,
buf: pointer as *const c_void,
size: size as usize,
}
}
/// Installs the initrd virtual device
/// Uses a buffer to build the device path
/// Creates a callback to initrd_loadfile2 for the kernel
pub fn install_initrd(
boot_services: &BootServices,
buf: &mut [MaybeUninit<u8>; 256],
mut loader: Loader,
) {
unsafe {
// Define new virtual device
let initrd_handle = boot_services
.install_protocol_interface(
None,
&DevicePath::GUID,
make_initrd_device_path(buf).as_ffi_ptr() as *mut c_void,
)
.unwrap();
// Install LoadFile2 protocol and callback
boot_services
.install_protocol_interface(
Some(initrd_handle),
&LOADFILE2_GUID,
core::ptr::addr_of_mut!(loader) as *mut c_void,
)
.unwrap();
}
}
/// Creates a device path for a virtual device with the Linux initrd vendor GUID
pub fn make_initrd_device_path<'a>(buf: &'a mut [MaybeUninit<u8>; 256]) -> &'a DevicePath {
&build::DevicePathBuilder::with_buf(buf)
.push(&build::media::Vendor {
vendor_guid: LINUX_INITRD_DEVICE_GUID,
vendor_defined_data: &[],
})
.unwrap()
.finalize()
.unwrap()
}
/// LoadFile2 protocol callback
/// Returns expected size if invalid buffer
/// Copies data if valid
fn initrd_loadfile2(
this: *const Loader,
_proto: *const c_void,
_boot_policy: bool,
size: *mut usize,
buf: *mut c_void,
) -> Status {
unsafe {
// Invalid buffer or insufficient size
if (*this).size > *size || buf == core::ptr::null_mut() {
// Update size parameter to specify expected space
*size = (*this).size;
return Status::BUFFER_TOO_SMALL;
}
core::ptr::copy_nonoverlapping((*this).buf, buf, (*this).size);
}
Status::SUCCESS
}

22
src/lib.rs Normal file
View File

@ -0,0 +1,22 @@
#![no_std]
use linux::start_linux;
use pe::get_loader_sections;
use uefi::table::{Boot, SystemTable};
use uefi::{Handle, Status};
mod initrd;
mod linux;
mod pe;
mod secureboot;
mod unicode;
pub fn bootloader(image_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
uefi_services::init(&mut system_table).unwrap();
let boot_services = system_table.boot_services();
let sections = get_loader_sections(boot_services).unwrap();
start_linux(image_handle, boot_services, sections).unwrap();
boot_services.stall(1_000_000_000);
Status::SUCCESS
}

73
src/linux.rs Normal file
View File

@ -0,0 +1,73 @@
use core::{ffi::c_void, mem::MaybeUninit, ptr::slice_from_raw_parts};
use uefi::{
prelude::BootServices, proto::loaded_image::LoadedImage, table::boot::LoadImageSource, Handle,
Result,
};
use crate::{
initrd::{install_initrd, make_loader},
pe::BootSections,
secureboot::install_security_override,
unicode::convert_8_to_16,
};
/// Sets up linux image and initrd virtual device
/// Reads cmdline
/// Runs kernel
pub fn start_linux(
image_handle: Handle,
boot_services: &BootServices,
sections: BootSections,
) -> Result {
// Override Secure Boot loader
let uninstall = install_security_override(boot_services);
// Load kernel as image from memory
let handle = unsafe {
boot_services
.load_image(
image_handle,
LoadImageSource::FromBuffer {
buffer: &*slice_from_raw_parts::<u8>(
sections.linux.pointer,
sections.linux.size as usize,
),
file_path: None,
},
)
.unwrap()
};
// Remove secure boot override
uninstall();
let mut linux_image = boot_services
.open_protocol_exclusive::<LoadedImage>(handle)
.unwrap();
// Set cmdline for kernel image
let (cmdline_utf16, cmdline_utf16_size) = convert_8_to_16(
boot_services,
sections.cmdline.pointer,
sections.cmdline.size as usize,
);
unsafe { linux_image.set_load_options(cmdline_utf16, cmdline_utf16_size as u32) };
// Define loader for virtual initrd device
// Has to be defined here in order not to be discarded after install_initrd exit
let loader = make_loader(
sections.initrd.pointer as *const c_void,
sections.initrd.size as usize,
);
// Install virtual initrd device
// Buffer is used to build the device path
let mut buf = [MaybeUninit::<u8>::uninit(); 256];
install_initrd(boot_services, &mut buf, loader);
// Run kernel image
boot_services.start_image(handle).unwrap();
Ok(())
}

13
src/main.rs Normal file
View File

@ -0,0 +1,13 @@
#![no_main]
#![no_std]
use barnacle::bootloader;
use uefi::prelude::entry;
use uefi::table::{Boot, SystemTable};
use uefi::{Handle, Status};
#[entry]
fn main(image_handle: Handle, system_table: SystemTable<Boot>) -> Status {
bootloader(image_handle, system_table)
}

93
src/pe.rs Normal file
View File

@ -0,0 +1,93 @@
use uefi::prelude::BootServices;
use uefi::proto::loaded_image::LoadedImage;
use uefi::{Error, Result, Status};
const LINUX_SNAME: [u8; 8] = [0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0, 0];
const CMDLINE_SNAME: [u8; 8] = [0x2e, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65];
const INITRD_SNAME: [u8; 8] = [0x2e, 0x69, 0x6e, 0x69, 0x74, 0x72, 0x64, 0];
pub struct PESection {
pub name: [u8; 8],
pub size: u32,
pub pointer: *const u8,
}
struct TempSections {
pub linux: Option<PESection>,
pub cmdline: Option<PESection>,
pub initrd: Option<PESection>,
}
pub struct BootSections {
pub linux: PESection,
pub cmdline: PESection,
pub initrd: PESection,
}
/// Extracts .linux, .initrd, and .cmdline sections from UKI
pub fn get_loader_sections(boot_services: &BootServices) -> Result<BootSections> {
// Obtain current image (UKI) memory location (and size)
let loaded_image =
boot_services.open_protocol_exclusive::<LoadedImage>(boot_services.image_handle())?;
let (image_ptr, _image_size) = loaded_image.info();
let mut tmp_sections = TempSections {
linux: None,
cmdline: None,
initrd: None,
};
let (section_table, section_count) = unsafe {
// PE table offset from start at 60 bytes offset, 4 bytes long
let pe_offset = *(image_ptr.add(0x3c) as *const u32);
let pe_ptr = image_ptr.add(pe_offset as usize).add(4);
// Section table is 260 bytes after PE table
// Number of sections at 2 bytes offset (in PE), 2 bytes long
(pe_ptr.add(0x104), *(pe_ptr.add(2) as *const u16))
};
for i in 0..section_count {
let section = unsafe {
// Each row is 40 bytes long
let section_row_ptr = section_table.add((i * 40) as usize) as *const u8;
// Section name at 0 bytes offset, 8 bytes long
let mut name = [0; 8];
for j in 0..8 {
name[j] = *section_row_ptr.add(j);
}
// Section VMA at 12 bytes offset, 4 bytes long
let section_vma = *(section_row_ptr.add(12) as *const u32);
PESection {
name,
// Section size at 8 bytes offset, 4 bytes long
size: *(section_row_ptr.add(8) as *const u32),
pointer: image_ptr.add(section_vma as usize) as *const u8,
}
};
// Section identification
if section.name == LINUX_SNAME {
tmp_sections.linux = Some(section);
} else if section.name == CMDLINE_SNAME {
tmp_sections.cmdline = Some(section);
} else if section.name == INITRD_SNAME {
tmp_sections.initrd = Some(section);
}
}
// All sections are needed, error out if not found
if tmp_sections.cmdline.is_none()
|| tmp_sections.linux.is_none()
|| tmp_sections.initrd.is_none()
{
return Err(Error::new(Status::INVALID_PARAMETER, ()));
}
Ok(BootSections {
linux: tmp_sections.linux.unwrap(),
cmdline: tmp_sections.cmdline.unwrap(),
initrd: tmp_sections.initrd.unwrap(),
})
}

106
src/secureboot.rs Normal file
View File

@ -0,0 +1,106 @@
use core::ffi::c_void;
use log::info;
use uefi::{guid, prelude::BootServices, proto::Protocol, Identify};
struct SecurityArch {
pub handler: *const c_void,
}
unsafe impl Identify for SecurityArch {
const GUID: uefi::Guid = guid!("A46423E3-4617-49F1-B9FF-D1BFA9115839");
}
impl Protocol for SecurityArch {}
struct Security2Arch {
pub handler: *const c_void,
}
unsafe impl Identify for Security2Arch {
const GUID: uefi::Guid = guid!("94ab2f58-1438-4ef1-9152-18941a3a0e68");
}
impl Protocol for Security2Arch {}
/// Most basic security handler, always returns true
fn security_handler() -> bool {
true
}
/// Stores default security handlers to restore them when uninstalling
struct DefaultSecurityHandlers {
pub security1: Option<*mut *const c_void>,
pub security1_handler: Option<*const c_void>,
pub security2: Option<*mut *const c_void>,
pub security2_handler: Option<*const c_void>,
}
/// Secure Boot override to load kernel image from memory
/// Plagiarized from systemd-stub's "hack"
/// Returns an override uninstaller to restore default handlers
pub fn install_security_override(boot_services: &BootServices) -> impl Fn() -> () {
let mut defaults = DefaultSecurityHandlers {
security1: None,
security1_handler: None,
security2: None,
security2_handler: None,
};
// Obtain SecurityArch protocol and replace handler
boot_services
.get_handle_for_protocol::<SecurityArch>()
.and_then(|h| {
boot_services
.open_protocol_exclusive::<SecurityArch>(h)
.and_then(|mut security| {
defaults.security1 = Some(core::ptr::addr_of_mut!(security.handler));
defaults.security1_handler = Some(security.handler);
security.handler = security_handler as *const c_void;
Ok(())
})
.unwrap_or_else(|_| {
info!("security1 unsupported");
});
Ok(())
})
.unwrap_or_else(|_| {
info!("security1 not found");
});
// Obtain Security2Arch protocol and replace handler
boot_services
.get_handle_for_protocol::<Security2Arch>()
.and_then(|h| {
boot_services
.open_protocol_exclusive::<Security2Arch>(h)
.and_then(|mut security| {
defaults.security2 = Some(core::ptr::addr_of_mut!(security.handler));
defaults.security2_handler = Some(security.handler);
security.handler = security_handler as *const c_void;
Ok(())
})
.unwrap_or_else(|_| {
info!("security2 unsupported");
});
Ok(())
})
.unwrap_or_else(|_| {
info!("security2 not found");
});
// Returns restore function
return move || {
// Restore default SecurityArch handler
defaults.security1.and_then(|s1| {
defaults
.security1_handler
.and_then(|s1h| Some(unsafe { *s1 = s1h }))
});
// Restore default SecurityArch2 handler
defaults.security2.and_then(|s2| {
defaults
.security1_handler
.and_then(|s2h| Some(unsafe { *s2 = s2h }))
});
};
}

18
src/unicode.rs Normal file
View File

@ -0,0 +1,18 @@
use uefi::{prelude::BootServices, table::boot::MemoryType};
/// Converts ascii to basic utf-16
pub fn convert_8_to_16(
boot_services: &BootServices,
text: *const u8,
size: usize,
) -> (*const u8, usize) {
let work_mem = boot_services
.allocate_pool(MemoryType::BOOT_SERVICES_DATA, size * 2)
.unwrap() as *mut u16;
for i in 0..size {
unsafe {
*work_mem.add(i) = *text.add(i) as u16;
}
}
(work_mem as *const u8, size * 2)
}