RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / guard_pages.rs
3466 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Stage 11: guard pages. Below every kernel stack is an unmapped 4 KiB
//! page, and below every user stack a 64 KiB unmapped zone. Running off the
//! end of a stack hits them, and the kernel kills just the culprit with a
//! clear "stack overflow" message, instead of corrupting other memory.
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(rustbelt::test_runner)]
#![reexport_test_harness_main = "test_main"]

extern crate alloc;

use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use core::sync::atomic::{AtomicBool, Ordering};
use rustbelt::process::{self, ExitStatus, SpawnArgs};
use rustbelt::stack::{self, KernelStack, STACK_SIZE};
use rustbelt::{fs, memory, thread};

entry_point!(main);

fn main(boot_info: &'static BootInfo) -> ! {
    rustbelt::init();
    rustbelt::init_memory(boot_info);
    rustbelt::init_smp();
    fs::init();
    rustbelt::init_storage();
    test_main();
    rustbelt::hlt_loop();
}

#[test_case]
fn guard_page_sits_right_below_each_kernel_stack() {
    let s = KernelStack::new().expect("no stack");
    assert_eq!(s.top() - s.bottom(), STACK_SIZE);
    assert_eq!(s.guard_page(), s.bottom() - 4096);
    assert!(stack::is_guard_page(s.guard_page()));
    assert!(stack::is_guard_page(s.bottom() - 1));
    assert!(!stack::is_guard_page(s.bottom()));
    assert!(!stack::is_guard_page(s.top() - 8));
}

#[test_case]
fn user_stack_guard_zone_bounds() {
    let stack_bottom = process::USER_STACK_TOP - process::USER_STACK_SIZE;
    assert!(process::in_user_stack_guard(stack_bottom - 1));
    assert!(process::in_user_stack_guard(process::USER_STACK_ZONE_START));
    assert!(!process::in_user_stack_guard(stack_bottom));
    assert!(!process::in_user_stack_guard(process::USER_STACK_ZONE_START - 1));
}

static REACHED_END: AtomicBool = AtomicBool::new(false);

#[allow(unconditional_recursion)]
fn recurse(depth: u64) -> u64 {
    let buf = core::hint::black_box([depth as u8; 256]);
    recurse(depth + 1) + buf[0] as u64
}

fn overflowing_thread() {
    recurse(0);
    REACHED_END.store(true, Ordering::Relaxed);
}

#[test_case]
fn kernel_stack_overflow_kills_only_that_thread() {
    let frames = memory::frames_in_use();
    let stacks = stack::live_stacks();
    let caught = thread::stack_overflows();
    for _ in 0..3 {
        // Three times: the handler must keep working (on any CPU).
        let id = thread::spawn("recurser", overflowing_thread);
        thread::join(id);
    }
    assert!(!REACHED_END.load(Ordering::Relaxed));
    assert_eq!(thread::stack_overflows(), caught + 3);
    // Its stack was freed like any other thread's, and we're still running.
    assert_eq!(stack::live_stacks(), stacks);
    assert_eq!(memory::frames_in_use(), frames);
}

#[test_case]
fn user_stack_overflow_kills_the_process() {
    let run = || {
        let spawn = SpawnArgs::kernel("overflow");
        let p = process::spawn_path("/mnt/disk0/bin/overflow", spawn).expect("spawn failed");
        process::wait(&p)
    };
    assert_eq!(run(), ExitStatus::Killed(rustbelt::signal::SIGSEGV, "stack overflow")); // warm up
    let frames = memory::frames_in_use();
    assert_eq!(run(), ExitStatus::Killed(rustbelt::signal::SIGSEGV, "stack overflow"));
    // Everything it had (incl. the 16 KiB of stack it filled) is free again.
    assert_eq!(memory::frames_in_use(), frames);
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rustbelt::test_panic_handler(info)
}