runtime: Validate ksupport ELF against hard-coded address ranges

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.
This commit is contained in:
David Nadlinger 2023-08-07 05:51:32 +08:00 committed by Sébastien Bourdeauducq
parent 8acfa82586
commit efbae51f9d
5 changed files with 78 additions and 22 deletions

View File

@ -320,6 +320,7 @@ dependencies = [
"build_misoc", "build_misoc",
"byteorder", "byteorder",
"cslice", "cslice",
"dyld",
"eh", "eh",
"failure", "failure",
"failure_derive", "failure_derive",

View File

@ -5,7 +5,7 @@ use elf::*;
pub mod elf; pub mod elf;
fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> { pub fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> {
if data.len() < offset + mem::size_of::<T>() { if data.len() < offset + mem::size_of::<T>() {
Err(()) Err(())
} else { } 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arch { pub enum Arch {
RiscV, RiscV,
@ -268,25 +287,16 @@ impl<'a> Library<'a> {
let ehdr = read_unaligned::<Elf32_Ehdr>(data, 0) let ehdr = read_unaligned::<Elf32_Ehdr>(data, 0)
.map_err(|()| "cannot read ELF header")?; .map_err(|()| "cannot read ELF header")?;
const IDENT: [u8; EI_NIDENT] = [ if !is_elf_for_current_arch(&ehdr, ET_DYN) {
ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, return Err("not a shared library for current architecture")?
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;
#[cfg(all(target_feature = "f", target_feature = "d"))] #[cfg(all(target_feature = "f", target_feature = "d"))]
const FLAGS: u32 = EF_RISCV_FLOAT_ABI_DOUBLE; const FLAGS: u32 = EF_RISCV_FLOAT_ABI_DOUBLE;
#[cfg(not(all(target_feature = "f", target_feature = "d")))] #[cfg(not(all(target_feature = "f", target_feature = "d")))]
const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT; const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT;
if ehdr.e_flags != FLAGS {
if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS { return Err("unexpected flags for shared library (wrong floating point ABI?)")?
return Err("not a shared library for current architecture")?
} }
let mut dyn_off = None; let mut dyn_off = None;

View File

@ -19,6 +19,7 @@ byteorder = { version = "1.0", default-features = false }
cslice = { version = "0.3" } cslice = { version = "0.3" }
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] } managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] }
dyld = { path = "../libdyld" }
eh = { path = "../libeh" } eh = { path = "../libeh" }
unwind_backtrace = { path = "../libunwind_backtrace" } unwind_backtrace = { path = "../libunwind_backtrace" }
io = { path = "../libio", features = ["byteorder"] } io = { path = "../libio", features = ["byteorder"] }

View File

@ -1,5 +1,5 @@
use core::ptr;
use board_misoc::csr; use board_misoc::csr;
use core::{ptr, slice};
use mailbox; use mailbox;
use rpc_queue; use rpc_queue;
@ -13,15 +13,20 @@ pub unsafe fn start() {
stop(); stop();
extern { extern "C" {
static _binary____ksupport_ksupport_elf_start: u8; static _binary____ksupport_ksupport_elf_start: u8;
static _binary____ksupport_ksupport_elf_end: u8; static _binary____ksupport_ksupport_elf_end: u8;
} }
let ksupport_start = &_binary____ksupport_ksupport_elf_start as *const _; let ksupport_elf_start = &_binary____ksupport_ksupport_elf_start as *const u8;
let ksupport_end = &_binary____ksupport_ksupport_elf_end as *const _; let ksupport_elf_end = &_binary____ksupport_ksupport_elf_end as *const u8;
ptr::copy_nonoverlapping(ksupport_start, let ksupport_elf = slice::from_raw_parts(
(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as *mut u8, ksupport_elf_start,
ksupport_end as usize - ksupport_start as usize); 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); csr::kernel_cpu::reset_write(0);
@ -41,6 +46,44 @@ pub unsafe fn stop() {
rpc_queue::init(); 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::<Elf32_Ehdr>(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::<Elf32_Phdr>(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 { pub fn validate(ptr: usize) -> bool {
ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS
} }

View File

@ -1,6 +1,7 @@
#![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by)] #![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by)]
#![no_std] #![no_std]
extern crate dyld;
extern crate eh; extern crate eh;
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;