RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / user_mode.rs
3943 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
//! User mode: real ring-3 programs (user/*.rs) run as processes, make
//! syscalls, get killed when they misbehave, and get preempted when they hog
//! the CPU. Plus the ELF loader and user-pointer checks.
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(rust_kernel::test_runner)]
#![reexport_test_harness_main = "test_main"]

use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use core::sync::atomic::Ordering;
use rust_kernel::errno::Errno;
use rust_kernel::interrupts::TICKS;
use rust_kernel::memory::{self, BootInfoFrameAllocator, USER_START};
use rust_kernel::process::{self, ExitStatus};
use rust_kernel::{allocator, elf, fs, syscall, user_programs};
use x86_64::VirtAddr;

entry_point!(main);

fn main(boot_info: &'static BootInfo) -> ! {
    rust_kernel::init();
    let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset);
    let mut mapper = unsafe { memory::init(phys_mem_offset) };
    let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) };
    allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
    memory::init_frame_allocator(frame_allocator);
    fs::init();

    test_main();
    rust_kernel::hlt_loop();
}

/// hello does ~10 syscalls (open/read/write/close on the ramfs, error cases)
/// and exits with the number of failed checks.
#[test_case]
fn hello_program_passes_its_syscall_checks() {
    let p = process::spawn("hello", user_programs::HELLO).expect("spawn hello");
    assert_eq!(process::wait(&p), ExitStatus::Exited(0));
    // It created /tmp/note.txt through the syscalls; the kernel sees it too.
    let inode = fs::lookup("/tmp/note.txt").expect("hello's file is missing");
    assert_eq!(inode.size(), 19);
}

/// evil reads kernel memory -> page fault in ring 3 -> killed. We're still here.
#[test_case]
fn kernel_memory_access_kills_only_the_process() {
    let p = process::spawn("evil", user_programs::EVIL).expect("spawn evil");
    assert_eq!(process::wait(&p), ExitStatus::Killed("page fault"));
}

/// spin never makes a syscall while it loops. If preemption didn't work,
/// this kernel thread couldn't run again until spin finished.
#[test_case]
fn spinning_process_is_preempted() {
    let p = process::spawn("spin", user_programs::SPIN).expect("spawn spin");
    let start = TICKS.load(Ordering::Relaxed);
    let mut samples_while_spinning = 0;
    while p.status().is_none() {
        // Busy-wait (no hlt): we only get here again if the timer took the
        // CPU away from spin and gave it back to us.
        if TICKS.load(Ordering::Relaxed) > start + 3 {
            samples_while_spinning += 1;
        }
        assert!(TICKS.load(Ordering::Relaxed) < start + 18 * 30, "spin didn't finish");
        core::hint::spin_loop();
    }
    assert_eq!(p.status(), Some(ExitStatus::Exited(0)));
    assert!(samples_while_spinning > 0, "never ran while spin was running");
}

#[test_case]
fn elf_loader_rejects_garbage() {
    assert_eq!(elf::check(&[b'x'; 128]), Err(elf::ElfError::NotElf));
    assert_eq!(elf::check(b"\x7fELF"), Err(elf::ElfError::TooShort));
    let entry = elf::check(user_programs::HELLO).expect("hello should parse");
    assert!(entry >= USER_START);
    // A truncated program: the header is fine, the program headers are cut off.
    assert!(elf::check(&user_programs::HELLO[..70]).is_err());
}

#[test_case]
fn user_pointers_are_validated() {
    // Kernel addresses, unmapped user addresses and overflowing ranges all fail.
    assert_eq!(syscall::user_slice(0x20_0000, 16).err(), Some(Errno::EFAULT));
    assert_eq!(syscall::user_slice(USER_START, 16).err(), Some(Errno::EFAULT));
    assert_eq!(syscall::user_slice(u64::MAX - 4, 16).err(), Some(Errno::EFAULT));
    assert!(!memory::user_range_ok(USER_START - 8, 16, false));
}

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