feat: kernel W^X (per-section perms, firmware unmapped, stack guard, boot check), halt on kernel faults, region W^X across processes
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(_start)
|
||||
|
||||
/* Every boundary below is page aligned: the kernel maps each range with
|
||||
its own permissions (W^X), see mmu::init_kernel_page_table. */
|
||||
SECTIONS {
|
||||
. = 0x80200000;
|
||||
_text_start = .;
|
||||
|
||||
.text : {
|
||||
*(.text.entry)
|
||||
@@ -10,13 +13,22 @@ SECTIONS {
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
_text_end = .;
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
*(.srodata .srodata.*)
|
||||
}
|
||||
.eh_frame : {
|
||||
*(.eh_frame)
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
_rodata_end = .;
|
||||
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
*(.sdata .sdata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
@@ -27,7 +39,12 @@ SECTIONS {
|
||||
_bss_end = .;
|
||||
}
|
||||
|
||||
/* Left unmapped, so overflowing the kernel stack faults instead of
|
||||
silently overwriting .bss. */
|
||||
. = ALIGN(4096);
|
||||
_stack_guard = .;
|
||||
. += 4096;
|
||||
|
||||
_stack_bottom = .;
|
||||
. += 0x80000;
|
||||
_stack_top = .;
|
||||
|
||||
@@ -16,6 +16,26 @@ pub fn init(uart: &Uart) -> &'static mut mmu::PageTable {
|
||||
let kernel_pt = mmu::init_kernel_page_table();
|
||||
mmu::activate(kernel_pt);
|
||||
uart.write_str("k-os: MMU enabled (Sv39)\n");
|
||||
match mmu::check_wx(kernel_pt) {
|
||||
Ok(code_pages) => {
|
||||
uart.write_str("k-os: W^X verified, ");
|
||||
uart.write_dec(code_pages);
|
||||
uart.write_str(" code pages\n");
|
||||
}
|
||||
Err(v) => {
|
||||
let (what, va) = match v {
|
||||
mmu::page_table::WxViolation::WritableCode(va) => ("writable code", va),
|
||||
mmu::page_table::WxViolation::ExecutableData(va) => ("executable data", va),
|
||||
mmu::page_table::WxViolation::MappedHole(va) => ("mapped hole", va),
|
||||
};
|
||||
uart.write_str("k-os: W^X violation: ");
|
||||
uart.write_str(what);
|
||||
uart.write_str(" at ");
|
||||
uart.write_hex(va);
|
||||
uart.put_char(b'\n');
|
||||
crate::halt(uart, "kernel page table violates W^X");
|
||||
}
|
||||
}
|
||||
|
||||
trap::init();
|
||||
crate::hw::timer::init();
|
||||
|
||||
@@ -3,6 +3,6 @@ pub mod page_table;
|
||||
|
||||
pub use allocator::init_allocator;
|
||||
pub use page_table::{
|
||||
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root,
|
||||
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root, check_wx,
|
||||
create_user_root, init_kernel_page_table, map_user_page, unmap_user_page,
|
||||
};
|
||||
|
||||
@@ -22,9 +22,15 @@ pub struct Sv39Flags {
|
||||
}
|
||||
|
||||
impl Sv39Flags {
|
||||
pub fn kernel_rwx() -> Self {
|
||||
pub fn kernel_rx() -> Self {
|
||||
Self {
|
||||
bits: PTE_R | PTE_W | PTE_X | PTE_G,
|
||||
bits: PTE_R | PTE_X | PTE_G,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel_ro() -> Self {
|
||||
Self {
|
||||
bits: PTE_R | PTE_G,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,16 +146,33 @@ pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||
)
|
||||
};
|
||||
|
||||
let (va, pa) = identity(board::RAM_BASE);
|
||||
map(
|
||||
root,
|
||||
Mapping {
|
||||
va,
|
||||
pa,
|
||||
pages: board::RAM_SIZE / PAGE_SIZE,
|
||||
flags: Sv39Flags::kernel_rwx(),
|
||||
},
|
||||
);
|
||||
// The kernel image, then everything after it, each with only the
|
||||
// access it needs: code is never writable, data never executable.
|
||||
// RAM below the image belongs to the firmware and stays unmapped, as
|
||||
// does the guard page under the kernel stack.
|
||||
let layout = KernelLayout::from_linker();
|
||||
let ram_end = board::RAM_BASE + board::RAM_SIZE;
|
||||
for (start, end, flags) in [
|
||||
(layout.text_start, layout.text_end, Sv39Flags::kernel_rx()),
|
||||
(layout.text_end, layout.rodata_end, Sv39Flags::kernel_ro()),
|
||||
(
|
||||
layout.rodata_end,
|
||||
layout.stack_guard,
|
||||
Sv39Flags::kernel_rw(),
|
||||
),
|
||||
(layout.stack_bottom, ram_end, Sv39Flags::kernel_rw()),
|
||||
] {
|
||||
let (va, pa) = identity(start);
|
||||
map(
|
||||
root,
|
||||
Mapping {
|
||||
va,
|
||||
pa,
|
||||
pages: (end - start) / PAGE_SIZE,
|
||||
flags,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let (va, pa) = identity(board::UART_BASE);
|
||||
map(
|
||||
@@ -198,6 +221,102 @@ pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||
root
|
||||
}
|
||||
|
||||
/// Page-aligned boundaries of the kernel image, from linker.ld.
|
||||
pub struct KernelLayout {
|
||||
pub text_start: usize,
|
||||
pub text_end: usize,
|
||||
pub rodata_end: usize,
|
||||
pub stack_guard: usize,
|
||||
pub stack_bottom: usize,
|
||||
}
|
||||
|
||||
impl KernelLayout {
|
||||
pub fn from_linker() -> Self {
|
||||
unsafe extern "C" {
|
||||
static _text_start: u8;
|
||||
static _text_end: u8;
|
||||
static _rodata_end: u8;
|
||||
static _stack_guard: u8;
|
||||
static _stack_bottom: u8;
|
||||
}
|
||||
// SAFETY: only the symbols' addresses are taken, never their values.
|
||||
unsafe {
|
||||
Self {
|
||||
text_start: &_text_start as *const u8 as usize,
|
||||
text_end: &_text_end as *const u8 as usize,
|
||||
rodata_end: &_rodata_end as *const u8 as usize,
|
||||
stack_guard: &_stack_guard as *const u8 as usize,
|
||||
stack_bottom: &_stack_bottom as *const u8 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What `check_wx` found wrong with a kernel page table, if anything.
|
||||
pub enum WxViolation {
|
||||
/// A page that is both writable and executable.
|
||||
WritableCode(usize),
|
||||
/// An executable page outside the kernel's text.
|
||||
ExecutableData(usize),
|
||||
/// A mapped page under the firmware, or the stack guard.
|
||||
MappedHole(usize),
|
||||
}
|
||||
|
||||
/// Walks every leaf of `root` and checks the W^X layout: nothing is both
|
||||
/// writable and executable, only kernel text is executable, and the
|
||||
/// firmware and the stack guard are unmapped. Returns the number of
|
||||
/// executable pages.
|
||||
pub fn check_wx(root: &PageTable) -> Result<usize, WxViolation> {
|
||||
let layout = KernelLayout::from_linker();
|
||||
let text = layout.text_start..layout.text_end;
|
||||
let guard = layout.stack_guard..layout.stack_bottom;
|
||||
let firmware = board::RAM_BASE..layout.text_start;
|
||||
let mut executable = 0;
|
||||
for (va, bits) in leaves(root) {
|
||||
if bits & PTE_W != 0 && bits & PTE_X != 0 {
|
||||
return Err(WxViolation::WritableCode(va));
|
||||
}
|
||||
if bits & PTE_X != 0 {
|
||||
if !text.contains(&va) {
|
||||
return Err(WxViolation::ExecutableData(va));
|
||||
}
|
||||
executable += 1;
|
||||
}
|
||||
if guard.contains(&va) || firmware.contains(&va) {
|
||||
return Err(WxViolation::MappedHole(va));
|
||||
}
|
||||
}
|
||||
Ok(executable)
|
||||
}
|
||||
|
||||
/// Every 4 KiB leaf in `root` as (virtual address, PTE flag bits).
|
||||
/// The kernel maps nothing larger.
|
||||
fn leaves(root: &PageTable) -> impl Iterator<Item = (usize, u64)> + '_ {
|
||||
let next = |e: &PageTableEntry| table_at((e.ppn() << 12) as usize) as &PageTable;
|
||||
root.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.flat_map(move |(i2, e2)| {
|
||||
next(e2)
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.flat_map(move |(i1, e1)| {
|
||||
next(e1)
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.map(move |(i0, e0)| {
|
||||
let va = (i2 << 30) | (i1 << 21) | (i0 << 12);
|
||||
(va, e0.0 & 0x3ff)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn activate(root: &PageTable) {
|
||||
let ppn = (root as *const PageTable as u64) >> 12;
|
||||
let satp = (8u64 << 60) | ppn;
|
||||
|
||||
@@ -123,6 +123,8 @@ const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
|
||||
const CAUSE_S_TIMER: usize = 5;
|
||||
const CAUSE_S_EXTERNAL: usize = 9;
|
||||
const CAUSE_ECALL_U: usize = 8;
|
||||
/// Set in `sstatus` when the trap came from supervisor mode.
|
||||
const SSTATUS_SPP: usize = 1 << 8;
|
||||
|
||||
pub fn init() {
|
||||
unsafe extern "C" {
|
||||
@@ -347,6 +349,22 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ if frame.sstatus & SSTATUS_SPP != 0 => {
|
||||
// The kernel itself faulted (a W^X violation, the stack guard,
|
||||
// a bad pointer). Its state can no longer be trusted, and no
|
||||
// user thread is to blame.
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("KERNEL FAULT: scause=");
|
||||
uart.write_hex(scause);
|
||||
let stval: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
||||
uart.write_str(" stval=");
|
||||
uart.write_hex(stval);
|
||||
uart.write_str(" sepc=");
|
||||
uart.write_hex(frame.sepc);
|
||||
uart.put_char(b'\n');
|
||||
crate::halt(&uart, "fault in kernel mode");
|
||||
}
|
||||
_ => {
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("FAULT: scause=");
|
||||
|
||||
Reference in New Issue
Block a user