forked from M-Labs/artiq
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:
parent
8acfa82586
commit
efbae51f9d
1
artiq/firmware/Cargo.lock
generated
1
artiq/firmware/Cargo.lock
generated
@ -320,6 +320,7 @@ dependencies = [
|
||||
"build_misoc",
|
||||
"byteorder",
|
||||
"cslice",
|
||||
"dyld",
|
||||
"eh",
|
||||
"failure",
|
||||
"failure_derive",
|
||||
|
@ -5,7 +5,7 @@ use 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>() {
|
||||
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::<Elf32_Ehdr>(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;
|
||||
|
@ -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"] }
|
||||
|
@ -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::<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 {
|
||||
ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user