From fc3f5f27b409f00bf8391ca104d52d3d6f455dae Mon Sep 17 00:00:00 2001 From: Altareos Date: Thu, 16 Mar 2023 22:08:38 +0100 Subject: [PATCH] basic functionality --- .cargo/config.toml | 3 + .gitignore | 4 ++ Cargo.lock | 138 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 20 +++++++ README.md | 12 ++++ src/initrd.rs | 89 +++++++++++++++++++++++++++++ src/lib.rs | 22 ++++++++ src/linux.rs | 73 ++++++++++++++++++++++++ src/main.rs | 13 +++++ src/pe.rs | 93 ++++++++++++++++++++++++++++++ src/secureboot.rs | 106 ++++++++++++++++++++++++++++++++++ src/unicode.rs | 18 ++++++ 12 files changed, 591 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/initrd.rs create mode 100644 src/lib.rs create mode 100644 src/linux.rs create mode 100644 src/main.rs create mode 100644 src/pe.rs create mode 100644 src/secureboot.rs create mode 100644 src/unicode.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..931cc12 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "x86_64-unknown-uefi" +rustflags = ["-C", "link-args=-base:0x0"] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c1c9a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode/ +/target/ +/esp/ +/virtual/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9704c17 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,138 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "barnacle" +version = "0.1.0" +dependencies = [ + "log", + "uefi", + "uefi-services", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[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 = "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.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcada80daa06c42ed5f48c9a043865edea5dc44cbf9ac009fda3b89526e28607" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +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.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5607fef843201070ed442fa257c7a944fae963fac7d4620612123192eb4d844" +dependencies = [ + "bitflags", + "log", + "ptr_meta", + "ucs2", + "uefi-macros", +] + +[[package]] +name = "uefi-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8099684193f2d99f7f130951f4054a1591ff7da370e2b33d7a71f0434920499" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "uefi-services" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75daa44f951cc1d9dc68d98cabc06ddbccc221d7bd21222271be5d7ac526a9d" +dependencies = [ + "cfg-if", + "log", + "uefi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cda065f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "barnacle" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "barnacle" +test = false +bench = false + +[lib] +test = false +bench = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.17" +uefi = "0.19.1" +uefi-services = "0.16.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fe2ad9 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Barnacle : The Rusty Stub Loader + +## Example objcopy command + +cmdline has to be converted to utf-16 + +```sh +objcopy target/x86_64-unknown-uefi/release/barnacle.efi /boot/efi/arch.efi \ + --add-section .linux=/boot/vmlinuz-linux --change-section-vma .linux=0x2000000 \ + --add-section .initrd=/boot/initramfs-linux.img --change-section-vma .initrd=0x3000000 \ + --add-section .cmdline=<(grep -a '^[^#]' "/etc/kernel/cmdline" | tr -s '\n' ' ' | iconv -t utf-16; printf '\n\0') --change-section-vma .cmdline=0x30000 +``` \ No newline at end of file diff --git a/src/initrd.rs b/src/initrd.rs new file mode 100644 index 0000000..ca645be --- /dev/null +++ b/src/initrd.rs @@ -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; 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; 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 +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c88aa2a --- /dev/null +++ b/src/lib.rs @@ -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) -> 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 +} diff --git a/src/linux.rs b/src/linux.rs new file mode 100644 index 0000000..07c1ea3 --- /dev/null +++ b/src/linux.rs @@ -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::( + 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::(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::::uninit(); 256]; + install_initrd(boot_services, &mut buf, loader); + + // Run kernel image + boot_services.start_image(handle).unwrap(); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a3dfd7d --- /dev/null +++ b/src/main.rs @@ -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) -> Status { + bootloader(image_handle, system_table) +} diff --git a/src/pe.rs b/src/pe.rs new file mode 100644 index 0000000..92fa168 --- /dev/null +++ b/src/pe.rs @@ -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, + pub cmdline: Option, + pub initrd: Option, +} + +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 { + // Obtain current image (UKI) memory location (and size) + let loaded_image = + boot_services.open_protocol_exclusive::(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(), + }) +} diff --git a/src/secureboot.rs b/src/secureboot.rs new file mode 100644 index 0000000..b3a38a5 --- /dev/null +++ b/src/secureboot.rs @@ -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::() + .and_then(|h| { + boot_services + .open_protocol_exclusive::(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::() + .and_then(|h| { + boot_services + .open_protocol_exclusive::(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 })) + }); + }; +} diff --git a/src/unicode.rs b/src/unicode.rs new file mode 100644 index 0000000..f04a608 --- /dev/null +++ b/src/unicode.rs @@ -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) +}