From efbae51f9d845e5ecc982d82eadee95672a4310d Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Mon, 7 Aug 2023 05:51:32 +0800 Subject: [PATCH] runtime: Validate ksupport ELF against hard-coded address ranges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This would have caught the reduction in header padding with LLD 14. In theory, we could just get rid of the hard-coded kernel CPU address ranges altogether and use ksupport.elf as the one source of truth; the code already exists in dyld. The actual base address of the file would still need to be forwarded to the kernel-side libunwind glue, though, as there doesn't seem to be a clean way to get the equivalent of KSUPPORT_HEADER_SIZE through the linker script. I have left this as-is with the hard-coded KERNELCPU_… constants for now. --- artiq/firmware/Cargo.lock | 1 + artiq/firmware/libdyld/lib.rs | 40 ++++++++++++++-------- artiq/firmware/runtime/Cargo.toml | 1 + artiq/firmware/runtime/kernel.rs | 57 +++++++++++++++++++++++++++---- artiq/firmware/runtime/main.rs | 1 + 5 files changed, 78 insertions(+), 22 deletions(-) diff --git a/artiq/firmware/Cargo.lock b/artiq/firmware/Cargo.lock index 23ded9aa2..f4bd9b88a 100644 --- a/artiq/firmware/Cargo.lock +++ b/artiq/firmware/Cargo.lock @@ -320,6 +320,7 @@ dependencies = [ "build_misoc", "byteorder", "cslice", + "dyld", "eh", "failure", "failure_derive", diff --git a/artiq/firmware/libdyld/lib.rs b/artiq/firmware/libdyld/lib.rs index 15bbe2548..64bfbdf15 100644 --- a/artiq/firmware/libdyld/lib.rs +++ b/artiq/firmware/libdyld/lib.rs @@ -5,7 +5,7 @@ use elf::*; pub mod elf; -fn read_unaligned(data: &[u8], offset: usize) -> Result { +pub fn read_unaligned(data: &[u8], offset: usize) -> Result { if data.len() < offset + mem::size_of::() { Err(()) } else { @@ -75,6 +75,25 @@ impl<'a> fmt::Display for Error<'a> { } } +pub fn is_elf_for_current_arch(ehdr: &Elf32_Ehdr, e_type: u16) -> bool { + const IDENT: [u8; EI_NIDENT] = [ + ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, + ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, + /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 + ]; + if ehdr.e_ident != IDENT { return false; } + + if ehdr.e_type != e_type { return false; } + + #[cfg(target_arch = "riscv32")] + const ARCH: u16 = EM_RISCV; + #[cfg(not(target_arch = "riscv32"))] + const ARCH: u16 = EM_NONE; + if ehdr.e_machine != ARCH { return false; } + + true +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Arch { RiscV, @@ -268,25 +287,16 @@ impl<'a> Library<'a> { let ehdr = read_unaligned::(data, 0) .map_err(|()| "cannot read ELF header")?; - const IDENT: [u8; EI_NIDENT] = [ - ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, - ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, - /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 - ]; - - #[cfg(target_arch = "riscv32")] - const ARCH: u16 = EM_RISCV; - #[cfg(not(target_arch = "riscv32"))] - const ARCH: u16 = EM_NONE; + if !is_elf_for_current_arch(&ehdr, ET_DYN) { + return Err("not a shared library for current architecture")? + } #[cfg(all(target_feature = "f", target_feature = "d"))] const FLAGS: u32 = EF_RISCV_FLOAT_ABI_DOUBLE; - #[cfg(not(all(target_feature = "f", target_feature = "d")))] const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT; - - if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS { - return Err("not a shared library for current architecture")? + if ehdr.e_flags != FLAGS { + return Err("unexpected flags for shared library (wrong floating point ABI?)")? } let mut dyn_off = None; diff --git a/artiq/firmware/runtime/Cargo.toml b/artiq/firmware/runtime/Cargo.toml index 85d05768c..86db5ed08 100644 --- a/artiq/firmware/runtime/Cargo.toml +++ b/artiq/firmware/runtime/Cargo.toml @@ -19,6 +19,7 @@ byteorder = { version = "1.0", default-features = false } cslice = { version = "0.3" } log = { version = "0.4", default-features = false } managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] } +dyld = { path = "../libdyld" } eh = { path = "../libeh" } unwind_backtrace = { path = "../libunwind_backtrace" } io = { path = "../libio", features = ["byteorder"] } diff --git a/artiq/firmware/runtime/kernel.rs b/artiq/firmware/runtime/kernel.rs index 1a67af556..42c1f2f05 100644 --- a/artiq/firmware/runtime/kernel.rs +++ b/artiq/firmware/runtime/kernel.rs @@ -1,5 +1,5 @@ -use core::ptr; use board_misoc::csr; +use core::{ptr, slice}; use mailbox; use rpc_queue; @@ -13,15 +13,20 @@ pub unsafe fn start() { stop(); - extern { + extern "C" { static _binary____ksupport_ksupport_elf_start: u8; static _binary____ksupport_ksupport_elf_end: u8; } - let ksupport_start = &_binary____ksupport_ksupport_elf_start as *const _; - let ksupport_end = &_binary____ksupport_ksupport_elf_end as *const _; - ptr::copy_nonoverlapping(ksupport_start, - (KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as *mut u8, - ksupport_end as usize - ksupport_start as usize); + let ksupport_elf_start = &_binary____ksupport_ksupport_elf_start as *const u8; + let ksupport_elf_end = &_binary____ksupport_ksupport_elf_end as *const u8; + let ksupport_elf = slice::from_raw_parts( + ksupport_elf_start, + ksupport_elf_end as usize - ksupport_elf_start as usize, + ); + + if let Err(msg) = load_image(&ksupport_elf) { + panic!("failed to load kernel CPU image (ksupport.elf): {}", msg); + } csr::kernel_cpu::reset_write(0); @@ -41,6 +46,44 @@ pub unsafe fn stop() { rpc_queue::init(); } +/// Loads the given image for execution on the kernel CPU. +/// +/// The entire image including the headers is copied into memory for later use by libunwind, but +/// placed such that the text section ends up at the right location in memory. Currently, we just +/// hard-code the address range, but at least verify that this matches the ELF program header given +/// in the image (avoids loading the – non-relocatable – code at the wrong address on toolchain/… +/// changes). +unsafe fn load_image(image: &[u8]) -> Result<(), &'static str> { + use dyld::elf::*; + use dyld::{is_elf_for_current_arch, read_unaligned}; + + let ehdr = read_unaligned::(image, 0).map_err(|()| "could not read ELF header")?; + + // The check assumes the two CPUs share the same architecture. This is just to avoid inscrutable + // errors; we do not functionally rely on this. + if !is_elf_for_current_arch(&ehdr, ET_EXEC) { + return Err("not an executable for kernel CPU architecture"); + } + + // First program header should be the main text/… LOAD (see ksupport.ld). + let phdr = read_unaligned::(image, ehdr.e_phoff as usize) + .map_err(|()| "could not read program header")?; + if phdr.p_type != PT_LOAD { + return Err("unexpected program header type"); + } + if phdr.p_vaddr + phdr.p_memsz > KERNELCPU_LAST_ADDRESS as u32 { + // This is a weak sanity check only; we also need to fit in the stack, etc. + return Err("too large for kernel CPU address range"); + } + const TARGET_ADDRESS: u32 = (KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as _; + if phdr.p_vaddr - phdr.p_offset != TARGET_ADDRESS { + return Err("unexpected load address/offset"); + } + + ptr::copy_nonoverlapping(image.as_ptr(), TARGET_ADDRESS as *mut u8, image.len()); + Ok(()) +} + pub fn validate(ptr: usize) -> bool { ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS } diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index 285768e8e..4809efa46 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -1,6 +1,7 @@ #![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by)] #![no_std] +extern crate dyld; extern crate eh; #[macro_use] extern crate alloc;