use crate::{ arch::paging::VirtualAddress, context::{self, Context, ContextId, Status}, ptrace, scheme::{ATOMIC_SCHEMEID_INIT, AtomicSchemeId, SchemeId}, syscall::{ data::{FloatRegisters, IntRegisters, PtraceEvent}, error::*, flag::*, scheme::Scheme, self, validate, }, }; use alloc::{ collections::BTreeMap, sync::Arc }; use core::{ cmp, mem, slice, sync::atomic::{AtomicUsize, Ordering} }; use spin::{Mutex, RwLock}; #[derive(Clone, Copy)] enum RegsKind { Float, Int } #[derive(Clone, Copy)] enum Operation { Memory(VirtualAddress), Regs(RegsKind), Trace { new_child: Option } } fn with_context(pid: ContextId, callback: F) -> Result where F: FnOnce(&Context) -> Result { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; let context = context.read(); if let Status::Exited(_) = context.status { return Err(Error::new(ESRCH)); } callback(&context) } fn with_context_mut(pid: ContextId, callback: F) -> Result where F: FnOnce(&mut Context) -> Result { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; let mut context = context.write(); if let Status::Exited(_) = context.status { return Err(Error::new(ESRCH)); } callback(&mut context) } fn try_stop_context(pid: ContextId, restart_after: bool, mut callback: F) -> Result where F: FnMut(&mut Context) -> Result { let mut first = true; let mut was_stopped = false; // will never be read loop { if !first { // We've tried this before, so lets wait before retrying unsafe { context::switch(); } } first = false; let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; let mut context = context.write(); if let Status::Exited(_) = context.status { return Err(Error::new(ESRCH)); } // Stop the process until we've done our thing if first { was_stopped = context.ptrace_stop; } context.ptrace_stop = true; if context.running { // Process still running, wait until it has stopped continue; } let ret = callback(&mut context); context.ptrace_stop = restart_after && was_stopped; break ret; } } #[derive(Clone, Copy)] struct Handle { flags: usize, pid: ContextId, operation: Operation } impl Handle { fn continue_ignored_child(&mut self) -> Option<()> { let pid = match self.operation { Operation::Trace { ref mut new_child } => new_child.take()?, _ => return None }; if ptrace::is_traced(pid) { return None; } let contexts = context::contexts(); let context = contexts.get(pid)?; let mut context = context.write(); context.ptrace_stop = false; Some(()) } } pub static PROC_SCHEME_ID: AtomicSchemeId = ATOMIC_SCHEMEID_INIT; pub struct ProcScheme { next_id: AtomicUsize, handles: RwLock>>> } impl ProcScheme { pub fn new(scheme_id: SchemeId) -> Self { PROC_SCHEME_ID.store(scheme_id, Ordering::SeqCst); Self { next_id: AtomicUsize::new(0), handles: RwLock::new(BTreeMap::new()), } } } impl Scheme for ProcScheme { fn open(&self, path: &[u8], flags: usize, uid: u32, gid: u32) -> Result { let path = core::str::from_utf8(path).map_err(|_| Error::new(EINVAL))?; let mut parts = path.splitn(2, '/'); let pid = parts.next() .and_then(|s| s.parse().ok()) .map(ContextId::from) .ok_or(Error::new(EINVAL))?; let operation = match parts.next() { Some("mem") => Operation::Memory(VirtualAddress::new(0)), Some("regs/float") => Operation::Regs(RegsKind::Float), Some("regs/int") => Operation::Regs(RegsKind::Int), Some("trace") => Operation::Trace { new_child: None }, _ => return Err(Error::new(EINVAL)) }; let contexts = context::contexts(); let target = contexts.get(pid).ok_or(Error::new(ESRCH))?; { let target = target.read(); if let Status::Exited(_) = target.status { return Err(Error::new(ESRCH)); } // Unless root, check security if uid != 0 && gid != 0 { let current = contexts.current().ok_or(Error::new(ESRCH))?; let current = current.read(); // Do we own the process? if uid != target.euid && gid != target.egid { return Err(Error::new(EPERM)); } // Is it a subprocess of us? In the future, a capability // could bypass this check. match contexts.anchestors(target.ppid).find(|&(id, _context)| id == current.id) { Some((id, context)) => { // Paranoid sanity check, as ptrace security holes // wouldn't be fun assert_eq!(id, current.id); assert_eq!(id, context.read().id); }, None => return Err(Error::new(EPERM)) } } } let id = self.next_id.fetch_add(1, Ordering::SeqCst); if let Operation::Trace { .. } = operation { if !ptrace::try_new_session(pid, id) { // There is no good way to handle id being occupied // for nothing here, is there? return Err(Error::new(EBUSY)); } if flags & O_TRUNC == O_TRUNC { let mut target = target.write(); target.ptrace_stop = true; } } self.handles.write().insert(id, Arc::new(Mutex::new(Handle { flags, pid, operation }))); Ok(id) } /// Using dup for `proc:` simply opens another operation on the same PID /// ```rust,ignore /// let trace = syscall::open("proc:1234/trace")?; /// /// // let regs = syscall::open("proc:1234/regs/int")?; /// let regs = syscall::dup(trace, "regs/int")?; /// ``` fn dup(&self, old_id: usize, buf: &[u8]) -> Result { let handle = { let handles = self.handles.read(); let handle = handles.get(&old_id).ok_or(Error::new(EBADF))?; let handle = handle.lock(); *handle }; let mut path = format!("{}/", handle.pid.into()).into_bytes(); path.extend_from_slice(buf); let (uid, gid) = { let contexts = context::contexts(); let context = contexts.current().ok_or(Error::new(ESRCH))?; let context = context.read(); (context.euid, context.egid) }; self.open(&path, handle.flags, uid, gid) } fn seek(&self, id: usize, pos: usize, whence: usize) -> Result { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; let mut handle = handle.lock(); match handle.operation { Operation::Memory(ref mut offset) => Ok({ *offset = VirtualAddress::new(match whence { SEEK_SET => pos, SEEK_CUR => cmp::max(0, offset.get() as isize + pos as isize) as usize, SEEK_END => cmp::max(0, isize::max_value() + pos as isize) as usize, _ => return Err(Error::new(EBADF)) }); offset.get() }), _ => Err(Error::new(EBADF)) } } fn read(&self, id: usize, buf: &mut [u8]) -> Result { // Don't hold a global lock during the context switch later on let handle = { let handles = self.handles.read(); Arc::clone(handles.get(&id).ok_or(Error::new(EBADF))?) }; // TODO: Make sure handle can't deadlock let mut handle = handle.lock(); let pid = handle.pid; match handle.operation { Operation::Memory(ref mut offset) => { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; let context = context.read(); ptrace::with_context_memory(&context, *offset, buf.len(), |ptr| { buf.copy_from_slice(validate::validate_slice(ptr, buf.len())?); Ok(()) })?; *offset = VirtualAddress::new(offset.get() + buf.len()); Ok(buf.len()) }, Operation::Regs(kind) => { union Output { float: FloatRegisters, int: IntRegisters } let (output, size) = match kind { RegsKind::Float => with_context(handle.pid, |context| { // NOTE: The kernel will never touch floats // In the rare case of not having floating // point registers uninitiated, return // empty everything. let fx = context.arch.get_fx_regs().unwrap_or_default(); Ok((Output { float: fx }, mem::size_of::())) })?, RegsKind::Int => try_stop_context(handle.pid, true, |context| match unsafe { ptrace::regs_for(&context) } { None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) }, Some(stack) => { let mut regs = IntRegisters::default(); stack.save(&mut regs); Ok((Output { int: regs }, mem::size_of::())) } })? }; let bytes = unsafe { slice::from_raw_parts(&output as *const _ as *const u8, mem::size_of::()) }; let len = cmp::min(buf.len(), size); buf[..len].copy_from_slice(&bytes[..len]); Ok(len) }, Operation::Trace { .. } => { let read = ptrace::recv_events(handle.pid, unsafe { slice::from_raw_parts_mut( buf.as_mut_ptr() as *mut PtraceEvent, buf.len() / mem::size_of::() ) }).unwrap_or(0); Ok(read * mem::size_of::()) } } } fn write(&self, id: usize, buf: &[u8]) -> Result { // Don't hold a global lock during the context switch later on let handle = { let handles = self.handles.read(); Arc::clone(handles.get(&id).ok_or(Error::new(EBADF))?) }; let mut handle = handle.lock(); handle.continue_ignored_child(); // Some operations borrow Operation:: mutably let pid = handle.pid; let flags = handle.flags; match handle.operation { Operation::Memory(ref mut offset) => { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; let context = context.read(); ptrace::with_context_memory(&context, *offset, buf.len(), |ptr| { validate::validate_slice_mut(ptr, buf.len())?.copy_from_slice(buf); Ok(()) })?; *offset = VirtualAddress::new(offset.get() + buf.len()); Ok(buf.len()) }, Operation::Regs(kind) => match kind { RegsKind::Float => { if buf.len() < mem::size_of::() { return Ok(0); } let regs = unsafe { *(buf as *const _ as *const FloatRegisters) }; with_context_mut(pid, |context| { // NOTE: The kernel will never touch floats // Ignore the rare case of floating point // registers being uninitiated let _ = context.arch.set_fx_regs(regs); Ok(mem::size_of::()) }) }, RegsKind::Int => { if buf.len() < mem::size_of::() { return Ok(0); } let regs = unsafe { *(buf as *const _ as *const IntRegisters) }; try_stop_context(handle.pid, true, |context| match unsafe { ptrace::regs_for_mut(context) } { None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) }, Some(stack) => { stack.load(®s); Ok(mem::size_of::()) } }) } }, Operation::Trace { ref mut new_child } => { if buf.len() < mem::size_of::() { return Ok(0); } let mut bytes = [0; mem::size_of::()]; let len = bytes.len(); bytes.copy_from_slice(&buf[0..len]); let op = u64::from_ne_bytes(bytes); if op & PTRACE_FLAG_WAIT != PTRACE_FLAG_WAIT || op & PTRACE_STOP_MASK != 0 { ptrace::cont(pid); } if op & PTRACE_STOP_MASK != 0 { ptrace::set_breakpoint(pid, op); } if op & PTRACE_STOP_SINGLESTEP == PTRACE_STOP_SINGLESTEP { try_stop_context(pid, false, |context| { match unsafe { ptrace::regs_for_mut(context) } { // If another CPU is running this process, // await for it to be stopped and in such // a way the registers can be read! None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) }, Some(stack) => { stack.set_singlestep(true); Ok(()) } } })?; } if op & PTRACE_FLAG_WAIT == PTRACE_FLAG_WAIT || flags & O_NONBLOCK != O_NONBLOCK { if let Some(event) = ptrace::wait(pid)? { if event.cause == PTRACE_EVENT_CLONE { *new_child = Some(ContextId::from(event.a)); } } } Ok(mem::size_of::()) } } } fn fcntl(&self, id: usize, cmd: usize, arg: usize) -> Result { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; let mut handle = handle.lock(); match cmd { F_SETFL => { handle.flags = arg; Ok(0) }, F_GETFL => return Ok(handle.flags), _ => return Err(Error::new(EINVAL)) } } fn fevent(&self, id: usize, _flags: usize) -> Result { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; let handle = handle.lock(); Ok(ptrace::session_fevent_flags(handle.pid).expect("proc (fevent): invalid session")) } fn fpath(&self, id: usize, buf: &mut [u8]) -> Result { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; let handle = handle.lock(); let path = format!("proc:{}/{}", handle.pid.into(), match handle.operation { Operation::Memory(_) => "mem", Operation::Regs(RegsKind::Float) => "regs/float", Operation::Regs(RegsKind::Int) => "regs/int", Operation::Trace { .. } => "trace" }); let len = cmp::min(path.len(), buf.len()); buf[..len].copy_from_slice(&path.as_bytes()[..len]); Ok(len) } fn close(&self, id: usize) -> Result { let handle = self.handles.write().remove(&id).ok_or(Error::new(EBADF))?; let mut handle = handle.lock(); handle.continue_ignored_child(); if let Operation::Trace { .. } = handle.operation { ptrace::close_session(handle.pid); if handle.flags & O_EXCL == O_EXCL { syscall::kill(handle.pid, SIGKILL)?; } else { let contexts = context::contexts(); if let Some(context) = contexts.get(handle.pid) { let mut context = context.write(); context.ptrace_stop = false; } } } Ok(0) } }