diff --git a/src/arch/x86_64/interrupt/exception.rs b/src/arch/x86_64/interrupt/exception.rs index bd3f4a442f140c29e46b39c64cadc9565741d7d4..b55a13e5d4aabaa233fee488be18c73f6a4e4551 100644 --- a/src/arch/x86_64/interrupt/exception.rs +++ b/src/arch/x86_64/interrupt/exception.rs @@ -48,6 +48,19 @@ interrupt_stack!(non_maskable, stack, { }); interrupt_stack!(breakpoint, stack, { + // The processor lets RIP point to the instruction *after* int3, so + // unhandled breakpoint interrupt don't go in an infinite loop. But we + // throw SIGTRAP anyway, so that's not a problem. + // + // We have the following code to prevent + // - RIP from going out of sync with instructions + // - The user having to do 2 syscalls to replace the instruction at RIP + // - Having more compatibility glue for GDB than necessary + // + // Let's just follow Linux convention and let RIP be RIP-1, point to the + // int3 instruction. After all, it's the sanest thing to do. + stack.iret.rip -= 1; + let guard = ptrace::set_process_regs(stack); if ptrace::breakpoint_callback(PTRACE_STOP_BREAKPOINT, None).is_none() { diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs index 9ddd3c79aef35b745b7113c531d4208467c676b8..9f5ed74b253032f23342867e5e5bb6126220c6f1 100644 --- a/src/arch/x86_64/interrupt/syscall.rs +++ b/src/arch/x86_64/interrupt/syscall.rs @@ -67,12 +67,7 @@ pub unsafe extern fn syscall_instruction() { : "intel", "volatile"); // Push scratch registers - scratch_push!(); - preserved_push!(); - asm!("push fs - mov r11, 0x18 - mov fs, r11" - : : : : "intel", "volatile"); + interrupt_push!(); // Get reference to stack variables let rsp: usize; @@ -87,10 +82,8 @@ pub unsafe extern fn syscall_instruction() { pti::unmap(); // Interrupt return - asm!("pop fs" : : : : "intel", "volatile"); - preserved_pop!(); - scratch_pop!(); - asm!("iretq" : : : : "intel", "volatile"); + interrupt_pop!(); + iret!() } #[naked] @@ -106,12 +99,7 @@ pub unsafe extern fn syscall() { } // Push scratch registers - scratch_push!(); - preserved_push!(); - asm!("push fs - mov r11, 0x18 - mov fs, r11" - : : : : "intel", "volatile"); + interrupt_push!(); // Get reference to stack variables let rsp: usize; @@ -126,10 +114,8 @@ pub unsafe extern fn syscall() { pti::unmap(); // Interrupt return - asm!("pop fs" : : : : "intel", "volatile"); - preserved_pop!(); - scratch_pop!(); - asm!("iretq" : : : : "intel", "volatile"); + interrupt_pop!(); + iret!(); } #[naked] diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs index 84021f6b6c755f403f476b23d2f91d8a13166584..22fe5a3cf649ade69d1cb04180acdb1d5738cd5d 100644 --- a/src/arch/x86_64/macros.rs +++ b/src/arch/x86_64/macros.rs @@ -1,5 +1,6 @@ use core::mem; use syscall::data::IntRegisters; +use super::gdt; /// Print to console #[macro_export] @@ -19,6 +20,7 @@ macro_rules! println { } #[allow(dead_code)] +#[derive(Default)] #[repr(packed)] pub struct ScratchRegisters { pub r11: usize, @@ -77,6 +79,7 @@ macro_rules! scratch_pop { } #[allow(dead_code)] +#[derive(Default)] #[repr(packed)] pub struct PreservedRegisters { pub r15: usize, @@ -124,9 +127,13 @@ macro_rules! preserved_pop { macro_rules! fs_push { () => (asm!( - "push fs + " + push fs + + // Load kernel tls mov rax, 0x18 - mov fs, ax" + mov fs, ax // can't load value directly into `fs` + " : : : : "intel", "volatile" )); } @@ -139,6 +146,7 @@ macro_rules! fs_pop { } #[allow(dead_code)] +#[derive(Default)] #[repr(packed)] pub struct IretRegisters { pub rip: usize, @@ -198,6 +206,7 @@ macro_rules! interrupt { } #[allow(dead_code)] +#[derive(Default)] #[repr(packed)] pub struct InterruptStack { pub fs: usize, @@ -207,6 +216,24 @@ pub struct InterruptStack { } impl InterruptStack { + pub fn new_usermode(ip: usize, sp: usize, arg: usize) -> Self { + // See which registers are set in start.rs, function `usermode` + Self { + fs: gdt::GDT_USER_TLS << 3 | 3, + preserved: PreservedRegisters::default(), + scratch: ScratchRegisters { + rdi: arg, + ..ScratchRegisters::default() + }, + iret: IretRegisters { + rip: ip, + cs: gdt::GDT_USER_CODE << 3 | 3, + rflags: 1 << 9, + rsp: sp, + ss: gdt::GDT_USER_DATA << 3 | 3, + }, + } + } pub fn dump(&self) { self.iret.dump(); self.scratch.dump(); @@ -298,9 +325,24 @@ impl InterruptStack { } } +macro_rules! interrupt_push { + () => { + scratch_push!(); + preserved_push!(); + fs_push!(); + }; +} +macro_rules! interrupt_pop { + () => { + fs_pop!(); + preserved_pop!(); + scratch_pop!(); + }; +} + #[macro_export] macro_rules! interrupt_stack { - ($name:ident, $stack: ident, $func:block) => { + ($name:ident, $stack:ident, $func:block) => { #[naked] pub unsafe extern fn $name () { #[inline(never)] @@ -309,9 +351,7 @@ macro_rules! interrupt_stack { } // Push scratch registers - scratch_push!(); - preserved_push!(); - fs_push!(); + interrupt_push!(); // Get reference to stack variables let rsp: usize; @@ -327,15 +367,14 @@ macro_rules! interrupt_stack { $crate::arch::x86_64::pti::unmap(); // Pop scratch registers and return - fs_pop!(); - preserved_pop!(); - scratch_pop!(); + interrupt_pop!(); iret!(); } }; } #[allow(dead_code)] +#[derive(Default)] #[repr(packed)] pub struct InterruptErrorStack { pub fs: usize, @@ -366,9 +405,7 @@ macro_rules! interrupt_error { } // Push scratch registers - scratch_push!(); - preserved_push!(); - fs_push!(); + interrupt_push!(); // Get reference to stack variables let rsp: usize; @@ -384,10 +421,8 @@ macro_rules! interrupt_error { $crate::arch::x86_64::pti::unmap(); // Pop scratch registers, error code, and return - fs_pop!(); - preserved_pop!(); - scratch_pop!(); - asm!("add rsp, 8" : : : : "intel", "volatile"); + interrupt_pop!(); + asm!("add rsp, 8" : : : : "intel", "volatile"); // pop error code iret!(); } }; diff --git a/src/arch/x86_64/start.rs b/src/arch/x86_64/start.rs index ab7fc39629f4a7b2f69a4a2d92adcb227a4104b7..b2bc4e5fc1efc093626ed521fe285b5239b7117a 100644 --- a/src/arch/x86_64/start.rs +++ b/src/arch/x86_64/start.rs @@ -3,7 +3,7 @@ /// It must create the IDT with the correct entries, those entries are /// defined in other files inside of the `arch` module -use core::slice; +use core::{slice, mem, ptr}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use crate::allocator; @@ -17,6 +17,7 @@ use crate::gdt; use crate::idt; use crate::interrupt; use crate::log; +use crate::macros::InterruptStack; use crate::memory; use crate::paging; @@ -283,3 +284,31 @@ pub unsafe fn usermode(ip: usize, sp: usize, arg: usize) -> ! { : "intel", "volatile"); unreachable!(); } + +#[naked] +pub unsafe fn usermode_interrupt_stack(stack: InterruptStack) -> ! { + // Push fake stack to the actual stack + let rsp: usize; + asm!("sub rsp, $1" : "={rsp}"(rsp) : "r"(mem::size_of::<InterruptStack>()) : : "intel", "volatile"); + ptr::write(rsp as *mut InterruptStack, stack); + + // Unmap kernel + pti::unmap(); + + // Set up floating point and TLS + asm!("mov ds, r14d + mov es, r14d + mov fs, r15d + mov gs, r14d + fninit" + : // No output because it never returns + : "{r14}"(gdt::GDT_USER_DATA << 3 | 3), // Data segment + "{r15}"(gdt::GDT_USER_TLS << 3 | 3) // TLS segment + : "ds", "es", "fs", "gs" + : "intel", "volatile"); + + // Go to usermode + interrupt_pop!(); + iret!(); + unreachable!(); +} diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index da275532bf8b0bf3f5e89e656ef6f2f5c4fafa2a..c45c345f5bae553a9d657bb679183e0d2b83d4b7 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -4,7 +4,7 @@ use crate::{ ptrace, scheme::{AtomicSchemeId, SchemeId}, syscall::{ - data::{FloatRegisters, IntRegisters, PtraceEvent}, + data::{FloatRegisters, IntRegisters, PtraceEvent, Stat}, error::*, flag::*, scheme::{calc_seek_offset_usize, Scheme}, @@ -14,8 +14,9 @@ use crate::{ }; use alloc::{ + boxed::Box, collections::BTreeMap, - vec::Vec + vec::Vec, }; use core::{ cmp, @@ -91,6 +92,17 @@ enum Operation { Memory, Regs(RegsKind), Trace, + Static(&'static str), +} +impl Operation { + fn needs_child_process(self) -> bool { + match self { + Self::Memory => true, + Self::Regs(_) => true, + Self::Trace => true, + Self::Static(_) => false, + } + } } struct MemData { offset: VirtualAddress, @@ -104,19 +116,25 @@ impl Default for MemData { struct TraceData { clones: Vec<ContextId>, } +struct StaticData { + buf: Box<[u8]>, + offset: usize, +} +impl StaticData { + fn new(buf: Box<[u8]>) -> Self { + Self { + buf, + offset: 0, + } + } +} enum OperationData { Memory(MemData), Trace(TraceData), + Static(StaticData), Other, } impl OperationData { - fn default_for(op: Operation) -> OperationData { - match op { - Operation::Memory => OperationData::Memory(MemData::default()), - Operation::Trace => OperationData::Trace(TraceData::default()), - _ => OperationData::Other, - } - } fn trace_data(&mut self) -> Option<&mut TraceData> { match self { OperationData::Trace(data) => Some(data), @@ -129,6 +147,12 @@ impl OperationData { _ => None, } } + fn static_data(&mut self) -> Option<&mut StaticData> { + match self { + OperationData::Static(data) => Some(data), + _ => None, + } + } } #[derive(Clone, Copy)] @@ -195,21 +219,31 @@ impl Scheme for ProcScheme { Some("regs/float") => Operation::Regs(RegsKind::Float), Some("regs/int") => Operation::Regs(RegsKind::Int), Some("trace") => Operation::Trace, + Some("exe") => Operation::Static("exe"), _ => return Err(Error::new(EINVAL)) }; let contexts = context::contexts(); let target = contexts.get(pid).ok_or(Error::new(ESRCH))?; + let data; + { let target = target.read(); + data = match operation { + Operation::Memory => OperationData::Memory(MemData::default()), + Operation::Trace => OperationData::Trace(TraceData::default()), + Operation::Static(_) => OperationData::Static(StaticData::new(target.name.lock().clone())), + _ => OperationData::Other, + }; + if let Status::Exited(_) = target.status { return Err(Error::new(ESRCH)); } // Unless root, check security - if uid != 0 && gid != 0 { + if operation.needs_child_process() && uid != 0 && gid != 0 { let current = contexts.current().ok_or(Error::new(ESRCH))?; let current = current.read(); @@ -227,10 +261,10 @@ impl Scheme for ProcScheme { assert_eq!(id, current.id); assert_eq!(id, context.read().id); }, - None => return Err(Error::new(EPERM)) + None => return Err(Error::new(EPERM)), } } - } + }; let id = self.next_id.fetch_add(1, Ordering::SeqCst); @@ -253,7 +287,7 @@ impl Scheme for ProcScheme { pid, operation, }, - data: OperationData::default_for(operation), + data, }); Ok(id) } @@ -304,6 +338,16 @@ impl Scheme for ProcScheme { }; match info.operation { + Operation::Static(_) => { + let mut handles = self.handles.write(); + let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + let data = handle.data.static_data().expect("operations can't change"); + + let len = cmp::min(data.buf.len() - data.offset, buf.len()); + buf[..len].copy_from_slice(&data.buf[data.offset .. data.offset + len]); + data.offset += len; + Ok(len) + }, Operation::Memory => { // Won't context switch, don't worry about the locks let mut handles = self.handles.write(); @@ -415,6 +459,7 @@ impl Scheme for ProcScheme { }; match info.operation { + Operation::Static(_) => Err(Error::new(EBADF)), Operation::Memory => { // Won't context switch, don't worry about the locks let mut handles = self.handles.write(); @@ -521,7 +566,7 @@ impl Scheme for ProcScheme { })?; Ok(mem::size_of::<u64>()) - } + }, } } @@ -540,9 +585,12 @@ impl Scheme for ProcScheme { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; - ptrace::Session::with_session(handle.info.pid, |session| { - Ok(session.data.lock().session_fevent_flags()) - }) + match handle.info.operation { + Operation::Trace => ptrace::Session::with_session(handle.info.pid, |session| { + Ok(session.data.lock().session_fevent_flags()) + }), + _ => Ok(EventFlags::empty()), + } } fn fpath(&self, id: usize, buf: &mut [u8]) -> Result<usize> { @@ -553,7 +601,8 @@ impl Scheme for ProcScheme { Operation::Memory => "mem", Operation::Regs(RegsKind::Float) => "regs/float", Operation::Regs(RegsKind::Int) => "regs/int", - Operation::Trace => "trace" + Operation::Trace => "trace", + Operation::Static(path) => path, }); let len = cmp::min(path.len(), buf.len()); @@ -562,6 +611,27 @@ impl Scheme for ProcScheme { Ok(len) } + fn fstat(&self, id: usize, stat: &mut Stat) -> Result<usize> { + let handles = self.handles.read(); + let handle = handles.get(&id).ok_or(Error::new(EBADF))?; + + stat.st_size = match handle.data { + OperationData::Static(ref data) => (data.buf.len() - data.offset) as u64, + _ => 0, + }; + *stat = Stat { + st_mode: MODE_FILE | 0o666, + st_size: match handle.data { + OperationData::Static(ref data) => (data.buf.len() - data.offset) as u64, + _ => 0, + }, + + ..Stat::default() + }; + + Ok(0) + } + fn close(&self, id: usize) -> Result<usize> { let mut handle = self.handles.write().remove(&id).ok_or(Error::new(EBADF))?; handle.continue_ignored_children(); diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 18de7f27ada9cb30b08e8151e55ce65f0fc38022..e27576fd3e248ec542b0b40df67034f97ec23686 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -6,6 +6,7 @@ use core::{intrinsics, mem}; use core::ops::DerefMut; use spin::Mutex; +use crate::arch::macros::InterruptStack; use crate::context::file::FileDescriptor; use crate::context::{ContextId, WaitpidKey}; use crate::context; @@ -20,14 +21,14 @@ use crate::paging::temporary_page::TemporaryPage; use crate::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, PAGE_SIZE}; use crate::{ptrace, syscall}; use crate::scheme::FileHandle; -use crate::start::usermode; +use crate::start::{usermode, usermode_interrupt_stack}; use crate::syscall::data::{SigAction, Stat}; use crate::syscall::error::*; -use crate::syscall::flag::{CloneFlags, CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, - CLONE_STACK, MapFlags, PROT_EXEC, PROT_READ, PROT_WRITE, PTRACE_EVENT_CLONE, - PTRACE_STOP_EXIT, SigActionFlags, SIG_DFL, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, - SIGCONT, SIGTERM, WaitFlags, WCONTINUED, WNOHANG, WUNTRACED, wifcontinued, - wifstopped}; +use crate::syscall::flag::{CloneFlags, CLONE_FILES, CLONE_FS, CLONE_SIGHAND, + CLONE_STACK, CLONE_VFORK, CLONE_VM, MapFlags, PtraceFlags, PROT_EXEC, + PROT_READ, PROT_WRITE, PTRACE_EVENT_CLONE, PTRACE_STOP_EXIT, SigActionFlags, + SIG_BLOCK, SIG_DFL, SIG_SETMASK, SIG_UNBLOCK, SIGCONT, SIGTERM, WaitFlags, + WCONTINUED, WNOHANG,WUNTRACED, wifcontinued, wifstopped}; use crate::syscall::ptrace_event; use crate::syscall::validate::{validate_slice, validate_slice_mut}; @@ -899,8 +900,22 @@ fn fexec_noreturn( } } - // Go to usermode - unsafe { usermode(entry, sp, 0); } + // Create dummy stack for ptrace to read from + let mut regs = InterruptStack::new_usermode(entry, sp, 0); + + // ptrace breakpoint + let was_traced = { + let _guard = ptrace::set_process_regs(&mut regs); + ptrace::breakpoint_callback(PtraceFlags::PTRACE_STOP_EXEC, None).is_some() + }; + + if !was_traced { + // Go to usermode, fast route + unsafe { usermode(entry, sp, 0) } + } else { + // Go to usermode, take ptrace-modified stack into account + unsafe { usermode_interrupt_stack(regs) } + } } pub fn fexec_kernel(fd: FileHandle, args: Box<[Box<[u8]>]>, vars: Box<[Box<[u8]>]>, name_override_opt: Option<Box<[u8]>>) -> Result<usize> {