| // Copyright 2016 The Fuchsia Authors | |
| // Copyright (c) 2008-2012 Travis Geiselbrecht | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| //! Debug console (`k`) commands (`dd`, `dw`, `dh`, `db`, `mw`, `mh`, `mb`, `fw`, `fh`, `fb`, `mc`, | |
| //! `mtest`, `crash`, `sleep`, `sleepm`, `build_instrumentation`, `pop`, `ub`). | |
| mod debugcommands_ffi; | |
| mod undefined_behavior; | |
| use core::ffi::{CStr, c_int, c_void}; | |
| use core::ptr::{with_exposed_provenance, with_exposed_provenance_mut}; | |
| use core::sync::atomic::{AtomicUsize, Ordering}; | |
| use kprint::{kprint, kprintln}; | |
| use zx_status::Status; | |
| use crate::console::{CMD_AVAIL_ALWAYS, CMD_AVAIL_NORMAL, CmdArgs, static_command}; | |
| use crate::debugcommands::debugcommands_ffi::{ | |
| arch_sync_cache_range, cpp_before_stack_overflow, cpp_crash_assert, | |
| cpp_crash_shadow_call_stack_stomp, cpp_crash_stack_guard, cpp_crash_stackstomp, | |
| }; | |
| use crate::kernel::thread::{sleep_relative, spawn}; | |
| use crate::kernel::types::PAddr; | |
| use crate::platform_rs::timer::{DurationMono, InstantMono}; | |
| use crate::top::debug::rust_debug_crash_panic; | |
| use crate::user_memory::UserMemory; | |
| use crate::vm::arch_vm_aspace::{ | |
| ARCH_MMU_FLAG_PERM_EXECUTE, ARCH_MMU_FLAG_PERM_READ, ARCH_MMU_FLAG_PERM_USER, | |
| ARCH_MMU_FLAG_PERM_WRITE, | |
| }; | |
| use crate::vm::page::VmPageDoublyLinkedList; | |
| use crate::vm::physmap::paddr_to_physmap; | |
| use crate::vm::pmm; | |
| use crate::vm::vm::vaddr_to_paddr; | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements, and the target memory range must be valid | |
| /// to read if mapped. | |
| unsafe extern "C" fn cmd_display_mem(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| // save the last address and len so we can continue where we left off | |
| static LAST_ADDRESS: AtomicUsize = AtomicUsize::new(0); | |
| static LAST_LEN: AtomicUsize = AtomicUsize::new(0); | |
| let mut address = LAST_ADDRESS.load(Ordering::Relaxed); | |
| let mut len = LAST_LEN.load(Ordering::Relaxed); | |
| if argc < 3 && len == 0 { | |
| kprintln!("not enough arguments"); | |
| kprintln!("{:s} [-l] [-b] [-p] [address] [length]", args[0].as_str()); | |
| return -1; | |
| } | |
| let size: usize = if args[0].as_str() == "dd" { | |
| 8 | |
| } else if args[0].as_str() == "dw" { | |
| 4 | |
| } else if args[0].as_str() == "dh" { | |
| 2 | |
| } else { | |
| 1 | |
| }; | |
| let mut little_endian = cfg!(target_endian = "little"); | |
| let mut argindex = 1usize; | |
| let mut read_address = false; | |
| let mut phys_addr = false; | |
| while (argc as usize) > argindex { | |
| if args[argindex].as_str() == "-l" { | |
| little_endian = true; | |
| } else if args[argindex].as_str() == "-b" { | |
| little_endian = false; | |
| } else if args[argindex].as_str() == "-p" { | |
| phys_addr = true; | |
| } else if !read_address { | |
| address = args[argindex].arg_uint as usize; | |
| LAST_ADDRESS.store(address, Ordering::Relaxed); | |
| read_address = true; | |
| } else { | |
| len = args[argindex].arg_uint as usize; | |
| LAST_LEN.store(len, Ordering::Relaxed); | |
| } | |
| argindex += 1; | |
| } | |
| if (address & (size - 1)) != 0 { | |
| kprintln!("unaligned address, cannot display"); | |
| return -1; | |
| } | |
| if phys_addr { | |
| // Translate to a physmap address if present. | |
| let vaddr = paddr_to_physmap(PAddr(address)).0; | |
| if vaddr == 0 { | |
| kprintln!("ERROR: failed to translate physical address"); | |
| return -1; | |
| } | |
| kprintln!( | |
| "physical address {:#x} translated to {:p}", | |
| address, | |
| with_exposed_provenance::<c_void>(vaddr) | |
| ); | |
| address = vaddr; | |
| LAST_ADDRESS.store(address, Ordering::Relaxed); | |
| } | |
| // preflight the start address to see if it's mapped | |
| if vaddr_to_paddr(with_exposed_provenance::<c_void>(address)).0 == 0 { | |
| kprintln!("ERROR: address 0x{:x} is unmapped", address); | |
| return -1; | |
| } | |
| let stop = address.wrapping_add(len); | |
| let mut count = 0usize; | |
| let swap = little_endian != cfg!(target_endian = "little"); | |
| while address < stop { | |
| if count == 0 { | |
| kprint!("0x{:08x}: ", address); | |
| } | |
| match size { | |
| 8 => { | |
| // SAFETY: `address` is 8-byte aligned and preflighted as mapped kernel memory. | |
| let raw = | |
| unsafe { core::ptr::read_volatile(with_exposed_provenance::<u64>(address)) }; | |
| let val = if swap { raw.swap_bytes() } else { raw }; | |
| kprint!("{:016x} ", val); | |
| } | |
| 4 => { | |
| // SAFETY: `address` is 4-byte aligned and preflighted as mapped kernel memory. | |
| let raw = | |
| unsafe { core::ptr::read_volatile(with_exposed_provenance::<u32>(address)) }; | |
| let val = if swap { raw.swap_bytes() } else { raw }; | |
| kprint!("{:08x} ", val); | |
| } | |
| 2 => { | |
| // SAFETY: `address` is 2-byte aligned and preflighted as mapped kernel memory. | |
| let raw = | |
| unsafe { core::ptr::read_volatile(with_exposed_provenance::<u16>(address)) }; | |
| let val = if swap { raw.swap_bytes() } else { raw }; | |
| kprint!("{:04x} ", val); | |
| } | |
| 1 => { | |
| // SAFETY: `address` is preflighted as mapped kernel memory. | |
| let val = | |
| unsafe { core::ptr::read_volatile(with_exposed_provenance::<u8>(address)) }; | |
| kprint!("{:02x} ", val); | |
| } | |
| _ => unreachable!(), | |
| } | |
| count += size; | |
| if count == 16 { | |
| kprintln!(""); | |
| count = 0; | |
| } | |
| address = address.wrapping_add(size); | |
| } | |
| LAST_ADDRESS.store(address, Ordering::Relaxed); | |
| if count != 0 { | |
| kprintln!(""); | |
| } | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements, and the target address must be valid for | |
| /// writing. | |
| unsafe extern "C" fn cmd_modify_mem(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| if argc < 3 { | |
| kprintln!("not enough arguments"); | |
| kprintln!("{:s} <address> <val>", args[0].as_str()); | |
| return -1; | |
| } | |
| let size: usize = if args[0].as_str() == "mw" { | |
| 4 | |
| } else if args[0].as_str() == "mh" { | |
| 2 | |
| } else { | |
| 1 | |
| }; | |
| let address = args[1].arg_uint as usize; | |
| let val = args[2].arg_uint as usize; | |
| if (address & (size - 1)) != 0 { | |
| kprintln!("unaligned address, cannot modify"); | |
| return -1; | |
| } | |
| match size { | |
| 4 => { | |
| // SAFETY: `address` is 4-byte aligned and specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile(with_exposed_provenance_mut::<u32>(address), val as u32); | |
| } | |
| } | |
| 2 => { | |
| // SAFETY: `address` is 2-byte aligned and specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile(with_exposed_provenance_mut::<u16>(address), val as u16); | |
| } | |
| } | |
| 1 => { | |
| // SAFETY: `address` is specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile(with_exposed_provenance_mut::<u8>(address), val as u8); | |
| } | |
| } | |
| _ => unreachable!(), | |
| } | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements, and the target memory range must be valid | |
| /// for writing. | |
| unsafe extern "C" fn cmd_fill_mem(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| if argc < 4 { | |
| kprintln!("not enough arguments"); | |
| kprintln!("{:s} <address> <len> <val>", args[0].as_str()); | |
| return -1; | |
| } | |
| let size: usize = if args[0].as_str() == "fw" { | |
| 4 | |
| } else if args[0].as_str() == "fh" { | |
| 2 | |
| } else { | |
| 1 | |
| }; | |
| let mut address = args[1].arg_uint as usize; | |
| let len = args[2].arg_uint as usize; | |
| let stop = address.wrapping_add(len); | |
| let val = args[3].arg_uint as usize; | |
| if (address & (size - 1)) != 0 { | |
| kprintln!("unaligned address, cannot modify"); | |
| return -1; | |
| } | |
| while address < stop { | |
| match size { | |
| 4 => { | |
| // SAFETY: `address` is 4-byte aligned and specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile( | |
| with_exposed_provenance_mut::<u32>(address), | |
| val as u32, | |
| ); | |
| } | |
| } | |
| 2 => { | |
| // SAFETY: `address` is 2-byte aligned and specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile( | |
| with_exposed_provenance_mut::<u16>(address), | |
| val as u16, | |
| ); | |
| } | |
| } | |
| 1 => { | |
| // SAFETY: `address` is specified by the console operator. | |
| unsafe { | |
| core::ptr::write_volatile( | |
| with_exposed_provenance_mut::<u8>(address), | |
| val as u8, | |
| ); | |
| } | |
| } | |
| _ => unreachable!(), | |
| } | |
| address = address.wrapping_add(size); | |
| } | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements, and the source and target memory ranges | |
| /// must be valid and non-overlapping. | |
| unsafe extern "C" fn cmd_copy_mem(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| if argc < 4 { | |
| kprintln!("not enough arguments"); | |
| kprintln!("{:s} <source address> <target address> <len>", args[0].as_str()); | |
| return -1; | |
| } | |
| let source = args[1].arg_uint as usize; | |
| let target = args[2].arg_uint as usize; | |
| let len = args[3].arg_uint as usize; | |
| // SAFETY: `source` and `target` are non-overlapping raw memory ranges of `len` bytes | |
| // specified by the console operator. | |
| unsafe { | |
| core::ptr::copy_nonoverlapping( | |
| with_exposed_provenance::<u8>(source), | |
| with_exposed_provenance_mut::<u8>(target), | |
| len, | |
| ); | |
| } | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements, and the target memory range must be | |
| /// 4-byte aligned and valid for reading and writing. | |
| unsafe extern "C" fn cmd_memtest(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| if argc < 3 { | |
| kprintln!("not enough arguments"); | |
| kprintln!("{:s} <base> <len>", args[0].as_str()); | |
| return -1; | |
| } | |
| let ptr = with_exposed_provenance_mut::<u32>(args[1].arg_uint as usize); | |
| let len = args[2].arg_uint as usize; | |
| // write out | |
| kprint!("writing first pass..."); | |
| for i in 0..(len / 4) { | |
| // SAFETY: `ptr.add(i)` is within the caller-specified `[base, base + len)` range. | |
| unsafe { | |
| core::ptr::write_volatile(ptr.add(i), i as u32); | |
| } | |
| } | |
| kprintln!("done"); | |
| // verify | |
| kprint!("verifying..."); | |
| for i in 0..(len / 4) { | |
| // SAFETY: `ptr.add(i)` is within the caller-specified `[base, base + len)` range. | |
| let val = unsafe { core::ptr::read_volatile(ptr.add(i)) }; | |
| if (val as usize) != i { | |
| // SAFETY: `ptr.add(i)` is within the caller-specified `[base, base + len)` range. | |
| kprintln!("error at {:p}", unsafe { ptr.add(i) }); | |
| } | |
| } | |
| kprintln!("done"); | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements. | |
| unsafe extern "C" fn cmd_sleep(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| let mut t = DurationMono::from_seconds(1); // default to 1 second | |
| if argc >= 2 { | |
| t = DurationMono::from_millis(args[1].arg_uint as i64); | |
| if args[0].as_str() == "sleep" { | |
| t = t * 1000; | |
| } | |
| } | |
| let _ = sleep_relative(t); | |
| 0 | |
| } | |
| extern "C" fn crash_thread(_arg: *mut c_void) -> i32 { | |
| // should crash | |
| // SAFETY: Intentionally dereferences an invalid pointer (`1`) to crash the kernel. | |
| unsafe { | |
| core::ptr::write_volatile(core::ptr::without_provenance_mut::<u8>(1), 1); | |
| } | |
| 0 | |
| } | |
| fn crash_deref(thread: bool) -> c_int { | |
| if thread { | |
| // SAFETY: `c"crasher"` is a valid NUL-terminated string, and `crash_thread` does not | |
| // dereference its argument pointer. | |
| let t = unsafe { spawn(c"crasher".as_ptr(), crash_thread, core::ptr::null_mut()) } | |
| .expect("Thread::Create failed"); | |
| // SAFETY: `t` was just created and has not been joined yet. | |
| let _ = unsafe { t.join(InstantMono::INFINITE) }; | |
| return 0; | |
| } | |
| crash_thread(core::ptr::null_mut()); | |
| // if it didn't, panic the system | |
| panic!("crash"); | |
| } | |
| /// Crash by intentionally recursing to itself until the kernel | |
| /// call stack is exceeded. | |
| #[allow(unconditional_recursion)] | |
| fn recurse() -> c_int { | |
| // The compiler doesn't know what function pointer this is anymore after | |
| // black_box, but it hasn't changed. | |
| let self_fn: fn() -> c_int = core::hint::black_box(recurse); | |
| self_fn().wrapping_add(1) | |
| } | |
| fn before_stack_overflow(what: &CStr) { | |
| // SAFETY: `what.as_ptr()` is a valid NUL-terminated C string. | |
| unsafe { | |
| cpp_before_stack_overflow(what.as_ptr()); | |
| } | |
| } | |
| fn crash_recurse() -> c_int { | |
| before_stack_overflow(c"Recursing"); | |
| recurse(); | |
| kprintln!("survived."); | |
| 0 | |
| } | |
| fn crash_stackstomp() -> c_int { | |
| // SAFETY: Intentionally overflows the kernel stack in test-only crash command. | |
| unsafe { cpp_crash_stackstomp() } | |
| } | |
| fn crash_shadow_call_stack_stomp() -> c_int { | |
| // SAFETY: Intentionally overflows the shadow call stack in test-only crash command. | |
| unsafe { cpp_crash_shadow_call_stack_stomp() } | |
| } | |
| fn has_user_code_execution_protection() -> bool { | |
| #[cfg(target_arch = "x86_64")] | |
| { | |
| use crate::arch_rs::feature::{X86_FEATURE_SMEP, x86_feature_has_smap, x86_feature_test}; | |
| x86_feature_test(X86_FEATURE_SMEP) || x86_feature_has_smap() | |
| } | |
| #[cfg(target_arch = "aarch64")] | |
| { | |
| // Privilege Execute Never (PXN) is available on all aarch64 machines. | |
| true | |
| } | |
| #[cfg(target_arch = "riscv64")] | |
| { | |
| // Supervisor User Memory access control is always available on RISC-V. | |
| true | |
| } | |
| } | |
| fn crash_user_execute() -> c_int { | |
| if !has_user_code_execution_protection() { | |
| kprintln!( | |
| "missing protection to avoid executing userspace code from a privileged context; will \ | |
| not crash." | |
| ); | |
| return -1; | |
| } | |
| const USER_MEMORY_SIZE: usize = page::SIZE; | |
| let Some(mem) = UserMemory::create(USER_MEMORY_SIZE) else { | |
| kprintln!("failed to allocate user memory; will not crash."); | |
| return -1; | |
| }; | |
| // Define a little fragment of code that we can copy. | |
| let begin_func: *const u8; | |
| let func_size: usize; | |
| // SAFETY: Places a `ret` instruction in `.rodata` and reads its address and byte length, then | |
| // forms a slice over those `func_size` static bytes. | |
| let code_fragment = unsafe { | |
| #[cfg(target_arch = "aarch64")] | |
| core::arch::asm!( | |
| ".pushsection .rodata.crash_user_exec, \"a?\", %progbits", | |
| ".balign 4", | |
| "2:", | |
| "ret", | |
| "3:", | |
| ".popsection", | |
| "adrp {ptr}, 2b", | |
| "add {ptr}, {ptr}, :lo12:2b", | |
| "mov {size}, #3b - 2b", | |
| ptr = out(reg) begin_func, | |
| size = out(reg) func_size, | |
| options(nomem, nostack), | |
| ); | |
| #[cfg(target_arch = "riscv64")] | |
| core::arch::asm!( | |
| ".pushsection .rodata.crash_user_exec, \"a?\", %progbits", | |
| ".balign 4", | |
| "2:", | |
| "ret", | |
| "3:", | |
| ".popsection", | |
| "lla {ptr}, 2b", | |
| "li {size}, 3b - 2b", | |
| ptr = out(reg) begin_func, | |
| size = out(reg) func_size, | |
| options(nomem, nostack), | |
| ); | |
| #[cfg(target_arch = "x86_64")] | |
| core::arch::asm!( | |
| ".pushsection .rodata.crash_user_exec, \"a?\", @progbits", | |
| "2:", | |
| "ret", | |
| "3:", | |
| ".popsection", | |
| "lea 2b(%rip), {ptr}", | |
| "mov $(3b - 2b), {size}", | |
| ptr = out(reg) begin_func, | |
| size = out(reg) func_size, | |
| options(att_syntax, nomem, nostack), | |
| ); | |
| core::slice::from_raw_parts(begin_func, func_size) | |
| }; | |
| assert!( | |
| code_fragment.len() <= USER_MEMORY_SIZE, | |
| "function does not fit in allocated user memory" | |
| ); | |
| if let Err(status) = mem.vmo_write(code_fragment, 0) { | |
| kprintln!("failed to copy payload ({}); will not crash.", status.into_raw()); | |
| return -1; | |
| } | |
| if let Err(status) = mem.commit_and_map(0..USER_MEMORY_SIZE) { | |
| kprintln!("failed to commit memory ({}); will not crash.", status.into_raw()); | |
| return -1; | |
| } | |
| // Set the memory as executable. We need to also make it read-only because | |
| // in arm64, writable user mappings imply Privileged Execute Never (PXN). | |
| if let Err(status) = mem.make_rx() { | |
| kprintln!("failed to make memory executable ({}); will not crash.", status.into_raw()); | |
| return -1; | |
| } | |
| let mmu_flags = match mem.aspace().arch_aspace().query(mem.base()) { | |
| Ok((_, flags)) => flags, | |
| Err(status) => { | |
| kprintln!("failed to query mmu flags ({}); will not crash.", status.into_raw()); | |
| return -1; | |
| } | |
| }; | |
| if (mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) != 0 | |
| || (mmu_flags | |
| & (ARCH_MMU_FLAG_PERM_USER | ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE)) | |
| == 0 | |
| { | |
| kprintln!("incorrect memory permissions; will not crash."); | |
| return -1; | |
| } | |
| let p = mem.user_in::<u8>().as_ptr(); | |
| if p.is_null() { | |
| kprintln!("failed to get pointer; will not crash."); | |
| return -1; | |
| } | |
| // SAFETY: `mem.base()..mem.base() + USER_MEMORY_SIZE` is a valid mapped user page. | |
| unsafe { | |
| arch_sync_cache_range(mem.base(), USER_MEMORY_SIZE); | |
| } | |
| kprintln!("about to crash.."); | |
| // SAFETY: `p` was checked to be non-null at line 583; intentionally jumps to a userspace | |
| // address from kernel context to trigger a SMEP/PXN/SUM execution fault. | |
| unsafe { | |
| let user_func = core::mem::transmute::<*const u8, unsafe extern "C" fn()>(p); | |
| user_func(); | |
| } | |
| kprintln!("executed userspace code from a kernel context; did not crash."); | |
| 0 | |
| } | |
| fn crash_user_read() -> c_int { | |
| #[cfg(target_arch = "aarch64")] | |
| { | |
| // TODO(https://fxbug.dev/42137335): Once we support PAN enable this for arm64. | |
| kprintln!("ARM64 currently does not support PAN; will not crash."); | |
| -1 | |
| } | |
| #[cfg(not(target_arch = "aarch64"))] | |
| { | |
| #[cfg(target_arch = "x86_64")] | |
| if !crate::arch_rs::feature::x86_feature_has_smap() { | |
| kprintln!("cpu does not support smap; will not crash."); | |
| return -1; | |
| } | |
| // RISCV implements the sstatus.sum bit, similar to x86 SMAP | |
| let Some(mem) = UserMemory::create(page::SIZE) else { | |
| kprintln!("failed to allocate user memory; will not crash."); | |
| return -1; | |
| }; | |
| let p = mem.user_in::<u8>().as_ptr(); | |
| if p.is_null() { | |
| kprintln!("failed to get pointer; will not crash."); | |
| return -1; | |
| } | |
| kprintln!("about to crash.."); | |
| // SAFETY: Intentionally reads a userspace address directly from kernel context to trigger a | |
| // SMAP/PAN/SUM read fault. | |
| let b = unsafe { crate::debugcommands::debugcommands_ffi::cpp_read_byte(p) }; | |
| kprintln!("read {:02x}; did not crash.", b); | |
| -1 | |
| } | |
| } | |
| fn crash_pmm_use_after_free() -> c_int { | |
| // We want to corrupt one of the pages on the pmm's free list. To do so, we'll allocate a bunch | |
| // of pages, keep track of the address of the last page, then free them all. The free list is | |
| // LIFO so by allocating and freeing a bunch of pages we'll have a pointer "to the middle" and | |
| // our corrupted page will be less like to be immediately allocated. | |
| // Allocate. | |
| const NUM_PAGES: usize = 10000; | |
| pin_init::stack_pin_init!(let pages = VmPageDoublyLinkedList::new()); | |
| if let Err(status) = pmm::alloc_pages(NUM_PAGES, 0, pages.as_mut()) { | |
| kprintln!("error: failed to allocate ({})", status.into_raw()); | |
| return -1; | |
| } | |
| // Make note of address. | |
| let last_paddr = pages.back().unwrap().paddr(); | |
| let va = paddr_to_physmap(last_paddr).0; | |
| // We're printing a little early because once we've returned the pages to the free list, we | |
| // want to avoid doing anything that might cause the target page to be allocated (by this | |
| // thread or some other thread). | |
| kprintln!("corrupting memory at address {:p}", with_exposed_provenance::<c_void>(va)); | |
| // Free. | |
| // SAFETY: `pages` contains valid allocated PMM pages. | |
| unsafe { | |
| pmm::free_list(pages.as_mut()); | |
| } | |
| // Corrupt! | |
| // SAFETY: Intentionally writes to a freed page in the physmap to test the PMM checker. | |
| unsafe { | |
| core::ptr::write_volatile(with_exposed_provenance_mut::<u8>(va), b'X'); | |
| } | |
| kprintln!("crash_pmm_use_after_free done"); | |
| -1 | |
| } | |
| fn crash_assert() -> c_int { | |
| // SAFETY: Intentionally triggers a C++ `ASSERT_MSG` failure. | |
| unsafe { cpp_crash_assert() } | |
| } | |
| fn crash_stack_guard() -> c_int { | |
| // SAFETY: Intentionally overwrites the stack guard to trigger a stack-protector crash. | |
| unsafe { cpp_crash_stack_guard() } | |
| } | |
| fn crash_illegal_instruction() -> c_int { | |
| kprintln!("attempting to crash with an illegal instruction"); | |
| // SAFETY: Intentionally executes an illegal instruction to crash the kernel. | |
| unsafe { | |
| #[cfg(target_arch = "riscv64")] | |
| core::arch::asm!("unimp"); | |
| #[cfg(target_arch = "x86_64")] | |
| core::arch::asm!("ud2"); | |
| #[cfg(target_arch = "aarch64")] | |
| core::arch::asm!("udf #0"); | |
| } | |
| #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64", target_arch = "aarch64")))] | |
| kprintln!("not implemented for this architecture"); | |
| 0 | |
| } | |
| fn crash_break_instruction() -> c_int { | |
| kprintln!("attempting to crash with a break instruction"); | |
| // SAFETY: Intentionally executes a break instruction to crash the kernel. | |
| unsafe { | |
| #[cfg(target_arch = "riscv64")] | |
| core::arch::asm!("ebreak"); | |
| #[cfg(target_arch = "x86_64")] | |
| core::arch::asm!("int3"); | |
| #[cfg(target_arch = "aarch64")] | |
| core::arch::asm!("brk #0"); | |
| } | |
| #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64", target_arch = "aarch64")))] | |
| kprintln!("not implemented for this architecture"); | |
| 0 | |
| } | |
| fn crash_syscall_instruction() -> c_int { | |
| kprintln!("attempting to crash with a syscall instruction"); | |
| #[cfg(target_arch = "riscv64")] | |
| { | |
| // Actually not a good idea, this will probably call into firmware with undefined args. | |
| kprintln!( | |
| "not implemented for RISC-V, ecall instruction would probably be trapped by firmware" | |
| ); | |
| } | |
| #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] | |
| // SAFETY: Intentionally executes a syscall/svc instruction in kernel mode to crash the kernel. | |
| unsafe { | |
| #[cfg(target_arch = "x86_64")] | |
| core::arch::asm!("syscall"); | |
| #[cfg(target_arch = "aarch64")] | |
| core::arch::asm!("svc #0"); | |
| } | |
| #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64", target_arch = "aarch64")))] | |
| kprintln!("not implemented for this architecture"); | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to `argc` valid `CmdArgs` elements. | |
| unsafe extern "C" fn cmd_crash(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| // SAFETY: The console dispatcher guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| let cmd_name = args[0].as_str(); | |
| let usage = || -> c_int { | |
| kprintln!("usage:"); | |
| kprintln!( | |
| "{:s} deref [thread] : intentionally dereference an invalid pointer", | |
| cmd_name | |
| ); | |
| kprintln!("{:s} stackstomp : intentionally overrun the stack", cmd_name); | |
| kprintln!( | |
| "{:s} recurse : intentionally overrun the stack by recursing", | |
| cmd_name | |
| ); | |
| kprintln!("{:s} user_read : intentionally read user memory", cmd_name); | |
| kprintln!("{:s} user_execute : intentionally execute user memory", cmd_name); | |
| kprintln!("{:s} pmm_use_after_free : intentionally corrupt the pmm free list", cmd_name); | |
| kprintln!("{:s} assert : intentionally crash by failing an assert", cmd_name); | |
| kprintln!("{:s} rust_panic : intentionally crash by panicking in Rust", cmd_name); | |
| kprintln!( | |
| "{:s} stack_guard : attempt to crash by overwriting the stack guard", | |
| cmd_name | |
| ); | |
| kprintln!( | |
| "{:s} illegal_instruction : attempt to crash by running an illegal instruction", | |
| cmd_name | |
| ); | |
| kprintln!( | |
| "{:s} break_instruction : attempt to crash by running a break instruction", | |
| cmd_name | |
| ); | |
| kprintln!( | |
| "{:s} syscall_instruction : attempt to crash by running a syscall instruction", | |
| cmd_name | |
| ); | |
| Status::INTERNAL.into_raw() | |
| }; | |
| if argc < 2 { | |
| kprintln!("not enough arguments"); | |
| return usage(); | |
| } | |
| match args[1].as_str() { | |
| "deref" => { | |
| if argc > 2 && args[2].as_str() == "thread" { | |
| crash_deref(true) | |
| } else { | |
| crash_deref(false) | |
| } | |
| } | |
| "stackstomp" => crash_stackstomp(), | |
| "shadow_call_stack_stomp" => crash_shadow_call_stack_stomp(), | |
| "recurse" => crash_recurse(), | |
| "user_read" => crash_user_read(), | |
| "user_execute" => crash_user_execute(), | |
| "pmm_use_after_free" => crash_pmm_use_after_free(), | |
| "assert" => crash_assert(), | |
| "rust_panic" => { | |
| rust_debug_crash_panic(); | |
| } | |
| "stack_guard" => crash_stack_guard(), | |
| "illegal_instruction" => crash_illegal_instruction(), | |
| "break_instruction" => crash_break_instruction(), | |
| "syscall_instruction" => crash_syscall_instruction(), | |
| _ => { | |
| kprintln!("unknown command"); | |
| usage() | |
| } | |
| } | |
| } | |
| /// # Safety | |
| /// | |
| /// `_argv` must be a valid pointer to `_argc` elements if dereferenced. | |
| unsafe extern "C" fn cmd_build_instrumentation( | |
| _argc: c_int, | |
| _argv: *const CmdArgs, | |
| _flags: u32, | |
| ) -> c_int { | |
| let print_feature = |what: &str| { | |
| kprintln!("build_instrumentation: {:s}", what); | |
| }; | |
| if cfg!(sanitize = "address") { | |
| print_feature("address_sanitizer"); | |
| } | |
| if cfg!(lk_debuglevel = "2") { | |
| print_feature("debug_assert"); | |
| } | |
| if cfg!(with_lock_dep) { | |
| print_feature("lockdep"); | |
| } | |
| if cfg!(sanitize = "safestack") { | |
| print_feature("safe_stack"); | |
| } | |
| if cfg!(sanitize = "shadow-call-stack") { | |
| print_feature("shadow_call_stack"); | |
| } | |
| if cfg!(ubsan) { | |
| print_feature("undefined_behavior_sanitizer"); | |
| } | |
| // missing: sancov, profile | |
| // This print statement is load-bearing! It is scraped in a panic test. | |
| // LINT.IfChange | |
| kprintln!("build_instrumentation: done"); | |
| // LINT.ThenChange(//src/tests/kernel_panic/kernel_panic_test.go) | |
| 0 | |
| } | |
| /// # Safety | |
| /// | |
| /// `_argv` must be a valid pointer to `argc` elements if dereferenced. | |
| unsafe extern "C" fn cmd_pop(argc: c_int, _argv: *const CmdArgs, _flags: u32) -> c_int { | |
| if argc > 1 { | |
| kprintln!("aren't there already enough arguments?"); | |
| return -1; | |
| } | |
| kprint!("drama!"); | |
| 0 | |
| } | |
| static_command!( | |
| DD_CMD, | |
| c"dd".as_ptr(), | |
| c"display memory in dwords".as_ptr(), | |
| cmd_display_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| DW_CMD, | |
| c"dw".as_ptr(), | |
| c"display memory in words".as_ptr(), | |
| cmd_display_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| DH_CMD, | |
| c"dh".as_ptr(), | |
| c"display memory in halfwords".as_ptr(), | |
| cmd_display_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| DB_CMD, | |
| c"db".as_ptr(), | |
| c"display memory in bytes".as_ptr(), | |
| cmd_display_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| MW_CMD, | |
| c"mw".as_ptr(), | |
| c"modify word of memory".as_ptr(), | |
| cmd_modify_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| MH_CMD, | |
| c"mh".as_ptr(), | |
| c"modify halfword of memory".as_ptr(), | |
| cmd_modify_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| MB_CMD, | |
| c"mb".as_ptr(), | |
| c"modify byte of memory".as_ptr(), | |
| cmd_modify_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| FW_CMD, | |
| c"fw".as_ptr(), | |
| c"fill range of memory by word".as_ptr(), | |
| cmd_fill_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| FH_CMD, | |
| c"fh".as_ptr(), | |
| c"fill range of memory by halfword".as_ptr(), | |
| cmd_fill_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| FB_CMD, | |
| c"fb".as_ptr(), | |
| c"fill range of memory by byte".as_ptr(), | |
| cmd_fill_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| MC_CMD, | |
| c"mc".as_ptr(), | |
| c"copy a range of memory".as_ptr(), | |
| cmd_copy_mem, | |
| CMD_AVAIL_ALWAYS | |
| ); | |
| static_command!( | |
| MTEST_CMD, | |
| c"mtest".as_ptr(), | |
| c"simple memory test".as_ptr(), | |
| cmd_memtest, | |
| CMD_AVAIL_NORMAL | |
| ); | |
| static_command!( | |
| CRASH_CMD, | |
| c"crash".as_ptr(), | |
| c"intentionally crash".as_ptr(), | |
| cmd_crash, | |
| CMD_AVAIL_NORMAL | |
| ); | |
| static_command!( | |
| SLEEP_CMD, | |
| c"sleep".as_ptr(), | |
| c"sleep number of seconds".as_ptr(), | |
| cmd_sleep, | |
| CMD_AVAIL_NORMAL | |
| ); | |
| static_command!( | |
| SLEEPM_CMD, | |
| c"sleepm".as_ptr(), | |
| c"sleep number of milliseconds".as_ptr(), | |
| cmd_sleep, | |
| CMD_AVAIL_NORMAL | |
| ); | |
| static_command!( | |
| BUILD_INSTRUMENTATION_CMD, | |
| c"build_instrumentation".as_ptr(), | |
| c"display a non-exhaustive list of build instrumentations used to build this kernel image" | |
| .as_ptr(), | |
| cmd_build_instrumentation, | |
| CMD_AVAIL_NORMAL | |
| ); | |
| static_command!(POP_CMD, c"pop".as_ptr(), c"start drama".as_ptr(), cmd_pop, CMD_AVAIL_NORMAL); |