//! 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) }