diff --git a/src/ptrace.rs b/src/ptrace.rs index 5a88d3a7902746ebb351d1a0047657e9dfd7999b..f82749680289e0e0aa38e82c3b3425a64848be17 100644 --- a/src/ptrace.rs +++ b/src/ptrace.rs @@ -10,17 +10,150 @@ use crate::{ }, common::unique::Unique, context::{self, Context, ContextId, Status}, + event, + scheme::proc, sync::WaitCondition }; use alloc::{ boxed::Box, - collections::BTreeMap, + collections::{ + BTreeMap, + VecDeque, + btree_map::Entry + }, sync::Arc, vec::Vec }; +use core::{ + cmp, + sync::atomic::Ordering +}; use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use syscall::error::*; +use syscall::{ + data::PtraceEvent, + error::*, + flag::{EVENT_READ, EVENT_WRITE} +}; + +// ____ _ +// / ___| ___ ___ ___(_) ___ _ __ ___ +// \___ \ / _ \/ __/ __| |/ _ \| '_ \/ __| +// ___) | __/\__ \__ \ | (_) | | | \__ \ +// |____/ \___||___/___/_|\___/|_| |_|___/ + +#[derive(Debug)] +struct Session { + file_id: usize, + events: VecDeque<PtraceEvent>, + breakpoint: Option<Breakpoint>, + tracer: Arc<WaitCondition> +} + +type SessionMap = BTreeMap<ContextId, Session>; + +static SESSIONS: Once<RwLock<SessionMap>> = Once::new(); + +fn init_sessions() -> RwLock<SessionMap> { + RwLock::new(BTreeMap::new()) +} +fn sessions() -> RwLockReadGuard<'static, SessionMap> { + SESSIONS.call_once(init_sessions).read() +} +fn sessions_mut() -> RwLockWriteGuard<'static, SessionMap> { + SESSIONS.call_once(init_sessions).write() +} + +/// Try to create a new session, but fail if one already exists for +/// this process +pub fn try_new_session(pid: ContextId, file_id: usize) -> bool { + let mut sessions = sessions_mut(); + + match sessions.entry(pid) { + Entry::Occupied(_) => false, + Entry::Vacant(vacant) => { + vacant.insert(Session { + file_id, + events: VecDeque::new(), + breakpoint: None, + tracer: Arc::new(WaitCondition::new()) + }); + true + } + } +} + +/// Returns true if a session is attached to this process +pub fn is_traced(pid: ContextId) -> bool { + sessions().contains_key(&pid) +} + +/// Used for getting the flags in fevent +pub fn session_fevent_flags(pid: ContextId) -> Option<usize> { + let sessions = sessions(); + let session = sessions.get(&pid)?; + let mut flags = 0; + if !session.events.is_empty() { + flags |= EVENT_READ; + } + if session.breakpoint.as_ref().map(|b| b.reached).unwrap_or(true) { + flags |= EVENT_WRITE; + } + Some(flags) +} + +/// Remove the session from the list of open sessions and notify any +/// waiting processes +pub fn close_session(pid: ContextId) { + if let Some(session) = sessions_mut().remove(&pid) { + session.tracer.notify(); + if let Some(breakpoint) = session.breakpoint { + breakpoint.tracee.notify(); + } + } +} + +/// Trigger a notification to the event: scheme +pub fn proc_trigger_event(file_id: usize, flags: usize) { + event::trigger(proc::PROC_SCHEME_ID.load(Ordering::SeqCst), file_id, flags); +} + +/// Dispatch an event to any tracer tracing `self`. This will cause +/// the tracer to wake up and poll for events. Returns Some(()) if an +/// event was sent. +pub fn send_event(event: PtraceEvent) -> Option<()> { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); + + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&context.id)?; + + session.events.push_back(event); + + // Notify nonblocking tracers + if session.events.len() == 1 { + // If the list of events was previously empty, alert now + proc_trigger_event(session.file_id, EVENT_READ); + } + + // Alert blocking tracers + session.tracer.notify(); + + Some(()) +} + +/// Poll events, return the amount read +pub fn recv_events(pid: ContextId, out: &mut [PtraceEvent]) -> Option<usize> { + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&pid)?; + + let len = cmp::min(out.len(), session.events.len()); + for (dst, src) in out.iter_mut().zip(session.events.drain(..len)) { + *dst = src; + } + Some(len) +} // ____ _ _ _ // | __ ) _ __ ___ __ _| | ___ __ ___ (_)_ __ | |_ ___ @@ -29,33 +162,25 @@ use syscall::error::*; // |____/|_| \___|\__,_|_|\_\ .__/ \___/|_|_| |_|\__|___/ // |_| -struct Handle { +#[derive(Debug)] +struct Breakpoint { tracee: Arc<WaitCondition>, - tracer: Arc<WaitCondition>, reached: bool, sysemu: bool, singlestep: bool } -static BREAKPOINTS: Once<RwLock<BTreeMap<ContextId, Handle>>> = Once::new(); - -fn init_breakpoints() -> RwLock<BTreeMap<ContextId, Handle>> { - RwLock::new(BTreeMap::new()) -} -fn breakpoints() -> RwLockReadGuard<'static, BTreeMap<ContextId, Handle>> { - BREAKPOINTS.call_once(init_breakpoints).read() -} -fn breakpoints_mut() -> RwLockWriteGuard<'static, BTreeMap<ContextId, Handle>> { - BREAKPOINTS.call_once(init_breakpoints).write() -} - -fn inner_cont(pid: ContextId) -> Option<Handle> { +fn inner_cont(pid: ContextId) -> Option<Breakpoint> { // Remove the breakpoint to both save space and also make sure any // yet unreached but obsolete breakpoints don't stop the program. - let handle = breakpoints_mut().remove(&pid)?; - handle.tracee.notify(); - Some(handle) + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&pid)?; + let breakpoint = session.breakpoint.take()?; + + breakpoint.tracee.notify(); + + Some(breakpoint) } /// Continue the process with the specified ID @@ -63,44 +188,61 @@ pub fn cont(pid: ContextId) { inner_cont(pid); } -/// Create a new breakpoint for the specified tracee, optionally with a sysemu flag +/// Create a new breakpoint for the specified tracee, optionally with +/// a sysemu flag. Panics if the session is invalid. pub fn set_breakpoint(pid: ContextId, sysemu: bool, singlestep: bool) { - let (tracee, tracer) = match inner_cont(pid) { - Some(breakpoint) => (breakpoint.tracee, breakpoint.tracer), - None => ( - Arc::new(WaitCondition::new()), - Arc::new(WaitCondition::new()) - ) - }; + let tracee = inner_cont(pid) + .map(|b| b.tracee) + .unwrap_or_else(|| Arc::new(WaitCondition::new())); - breakpoints_mut().insert(pid, Handle { + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&pid).expect("proc (set_breakpoint): invalid session"); + session.breakpoint = Some(Breakpoint { tracee, - tracer, reached: false, sysemu, singlestep }); } -/// Wait for the tracee to stop. -/// Note: Don't call while holding any locks, this will switch contexts -pub fn wait_breakpoint(pid: ContextId) -> Result<()> { - let tracer = { - let breakpoints = breakpoints(); - match breakpoints.get(&pid) { - Some(breakpoint) if !breakpoint.reached => Arc::clone(&breakpoint.tracer), - _ => return Ok(()) +/// Wait for the tracee to stop. If an event occurs, it returns a copy +/// of that. It will still be available for read using recv_event. +/// +/// Note: Don't call while holding any locks, this will switch +/// contexts +pub fn wait(pid: ContextId) -> Result<Option<PtraceEvent>> { + let tracer: Arc<WaitCondition> = { + let sessions = sessions(); + match sessions.get(&pid) { + Some(session) if session.breakpoint.as_ref().map(|b| !b.reached).unwrap_or(true) => { + if let Some(event) = session.events.front() { + return Ok(Some(event.clone())); + } + Arc::clone(&session.tracer) + }, + _ => return Ok(None) } }; + while !tracer.wait() {} + { + let sessions = sessions(); + if let Some(session) = sessions.get(&pid) { + if let Some(event) = session.events.front() { + return Ok(Some(event.clone())); + } + } + } + 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)); } - Ok(()) + + Ok(None) } /// Notify the tracer and await green flag to continue. @@ -112,8 +254,9 @@ pub fn breakpoint_callback(singlestep: bool) -> Option<bool> { let context = contexts.current()?; let context = context.read(); - let mut breakpoints = breakpoints_mut(); - let breakpoint = breakpoints.get_mut(&context.id)?; + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&context.id)?; + let breakpoint = session.breakpoint.as_mut()?; // TODO: How should singlesteps interact with syscalls? How // does Linux handle this? @@ -123,11 +266,13 @@ pub fn breakpoint_callback(singlestep: bool) -> Option<bool> { return None; } - breakpoint.tracer.notify(); // In case no tracer is waiting, make sure the next one gets // the memo breakpoint.reached = true; + session.tracer.notify(); + proc_trigger_event(session.file_id, EVENT_WRITE); + ( Arc::clone(&breakpoint.tracee), breakpoint.sysemu @@ -140,15 +285,13 @@ pub fn breakpoint_callback(singlestep: bool) -> Option<bool> { } /// Call when a context is closed to alert any tracers -pub fn close(pid: ContextId) { - { - let breakpoints = breakpoints(); - if let Some(breakpoint) = breakpoints.get(&pid) { - breakpoint.tracer.notify(); - } - } +pub fn close_tracee(pid: ContextId) -> Option<()> { + let mut sessions = sessions_mut(); + let session = sessions.get_mut(&pid)?; - breakpoints_mut().remove(&pid); + session.breakpoint = None; + session.tracer.notify(); + Some(()) } // ____ _ _ diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index 83becb8fe07c6f80272fb645773f990dcf6c08d5..8d2e345ec789baf461ee660f931615507a120377 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -96,7 +96,7 @@ impl<'a> Iterator for SchemeIter<'a> { /// Scheme list type pub struct SchemeList { - map: BTreeMap<SchemeId, Arc<Box<Scheme + Send + Sync>>>, + map: BTreeMap<SchemeId, Arc<Box<dyn Scheme + Send + Sync>>>, names: BTreeMap<SchemeNamespace, BTreeMap<Box<[u8]>, SchemeId>>, next_ns: usize, next_id: usize @@ -141,7 +141,7 @@ impl SchemeList { self.insert(ns, Box::new(*b"debug"), |scheme_id| Arc::new(Box::new(DebugScheme::new(scheme_id)))).unwrap(); self.insert(ns, Box::new(*b"initfs"), |_| Arc::new(Box::new(InitFsScheme::new()))).unwrap(); self.insert(ns, Box::new(*b"irq"), |scheme_id| Arc::new(Box::new(IrqScheme::new(scheme_id)))).unwrap(); - self.insert(ns, Box::new(*b"proc"), |_| Arc::new(Box::new(ProcScheme::new()))).unwrap(); + self.insert(ns, Box::new(*b"proc"), |scheme_id| Arc::new(Box::new(ProcScheme::new(scheme_id)))).unwrap(); #[cfg(feature = "live")] { self.insert(ns, Box::new(*b"disk/live"), |_| Arc::new(Box::new(self::live::DiskScheme::new()))).unwrap(); @@ -184,7 +184,7 @@ impl SchemeList { } /// Get the nth scheme. - pub fn get(&self, id: SchemeId) -> Option<&Arc<Box<Scheme + Send + Sync>>> { + pub fn get(&self, id: SchemeId) -> Option<&Arc<Box<dyn Scheme + Send + Sync>>> { self.map.get(&id) } diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index 93fd90d8ecc1fdf47a1ef2821c53e3e695b53bdc..76fc9d80b089c22c57fdfceb7938e9db7cc428eb 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -1,12 +1,13 @@ use crate::{ arch::paging::VirtualAddress, context::{self, ContextId, Status}, - syscall::validate, - ptrace + ptrace, + scheme::{ATOMIC_SCHEMEID_INIT, AtomicSchemeId, SchemeId}, + syscall::validate }; use alloc::{ - collections::{BTreeMap, BTreeSet}, + collections::BTreeMap, sync::Arc }; use core::{ @@ -17,7 +18,7 @@ use core::{ }; use spin::{Mutex, RwLock}; use syscall::{ - data::{IntRegisters, FloatRegisters}, + data::{FloatRegisters, IntRegisters, PtraceEvent}, error::*, flag::*, scheme::Scheme @@ -32,7 +33,9 @@ enum RegsKind { enum Operation { Memory(VirtualAddress), Regs(RegsKind), - Trace + Trace { + new_child: Option<ContextId> + } } #[derive(Clone, Copy)] @@ -41,19 +44,37 @@ struct Handle { 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<BTreeMap<usize, Arc<Mutex<Handle>>>>, - traced: Mutex<BTreeSet<ContextId>> + handles: RwLock<BTreeMap<usize, Arc<Mutex<Handle>>>> } impl ProcScheme { - pub fn new() -> Self { + 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()), - traced: Mutex::new(BTreeSet::new()) } } } @@ -70,50 +91,59 @@ impl Scheme for ProcScheme { 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, + 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))?; - // Unless root, check security - if uid != 0 && gid != 0 { - let current = contexts.current().ok_or(Error::new(ESRCH))?; - let current = current.read(); + { let target = target.read(); - // Do we own the process? - if uid != target.euid && gid != target.egid { - return Err(Error::new(EPERM)); + if let Status::Exited(_) = target.status { + return Err(Error::new(ESRCH)); } - // 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)) + // 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 { - let mut traced = self.traced.lock(); - - if traced.contains(&pid) { + 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)); } - traced.insert(pid); let mut target = target.write(); target.ptrace_stop = true; } - let id = self.next_id.fetch_add(1, Ordering::SeqCst); self.handles.write().insert(id, Arc::new(Mutex::new(Handle { flags, pid, @@ -244,7 +274,16 @@ impl Scheme for ProcScheme { Ok(len) }, - Operation::Trace => Err(Error::new(EBADF)) + 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::<PtraceEvent>() + ) + }).unwrap_or(0); + + Ok(read * mem::size_of::<PtraceEvent>()) + } } } @@ -255,7 +294,11 @@ impl Scheme for ProcScheme { 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; let mut first = true; match handle.operation { @@ -320,28 +363,23 @@ impl Scheme for ProcScheme { } }; }, - Operation::Trace => { + Operation::Trace { ref mut new_child } => { if buf.len() < 1 { return Ok(0); } let op = buf[0]; let sysemu = op & PTRACE_SYSEMU == PTRACE_SYSEMU; - let mut blocking = handle.flags & O_NONBLOCK != O_NONBLOCK; - let mut wait_breakpoint = false; + let mut blocking = flags & O_NONBLOCK != O_NONBLOCK; let mut singlestep = false; match op & PTRACE_OPERATIONMASK { - PTRACE_CONT => { ptrace::cont(handle.pid); }, + PTRACE_CONT => { ptrace::cont(pid); }, PTRACE_SYSCALL | PTRACE_SINGLESTEP => { // <- not a bitwise OR singlestep = op & PTRACE_OPERATIONMASK == PTRACE_SINGLESTEP; - ptrace::set_breakpoint(handle.pid, sysemu, singlestep); - wait_breakpoint = true; - }, - PTRACE_WAIT => { - wait_breakpoint = true; - blocking = true; + ptrace::set_breakpoint(pid, sysemu, singlestep); }, + PTRACE_WAIT => blocking = true, _ => return Err(Error::new(EINVAL)) } @@ -354,7 +392,7 @@ impl Scheme for ProcScheme { first = false; let contexts = context::contexts(); - let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; + 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)); @@ -371,8 +409,13 @@ impl Scheme for ProcScheme { break; } - if wait_breakpoint && blocking { - ptrace::wait_breakpoint(handle.pid)?; + if blocking { + if let Some(event) = ptrace::wait(pid)? { + if event.tag == PTRACE_EVENT_CLONE { + *new_child = Some(ContextId::from(unsafe { event.data.clone })); + } + return Ok(0); + } } Ok(1) @@ -392,6 +435,14 @@ impl Scheme for ProcScheme { } } + fn fevent(&self, id: usize, _flags: usize) -> Result<usize> { + 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<usize> { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; @@ -401,7 +452,7 @@ 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" }); let len = cmp::min(path.len(), buf.len()); @@ -412,11 +463,11 @@ impl Scheme for ProcScheme { fn close(&self, id: usize) -> Result<usize> { let handle = self.handles.write().remove(&id).ok_or(Error::new(EBADF))?; - let handle = handle.lock(); + let mut handle = handle.lock(); + handle.continue_ignored_child(); - if let Operation::Trace = handle.operation { - ptrace::cont(handle.pid); - self.traced.lock().remove(&handle.pid); + if let Operation::Trace { .. } = handle.operation { + ptrace::close_session(handle.pid); } let contexts = context::contexts(); diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 8165ef0e9c3491f4186c61fe7154d30db87dbfa6..9a74c0a23ac3d45c9f62b2975e79354d4e62af9f 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -21,12 +21,12 @@ use crate::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, PA use crate::ptrace; use crate::scheme::FileHandle; use crate::start::usermode; -use crate::syscall::data::{SigAction, Stat}; +use crate::syscall::data::{PtraceEvent, PtraceEventContent, SigAction, Stat}; use crate::syscall::error::*; use crate::syscall::flag::{CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_STACK, - PROT_EXEC, PROT_READ, PROT_WRITE, - SIG_DFL, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, SIGCONT, SIGTERM, - WCONTINUED, WNOHANG, WUNTRACED, wifcontinued, wifstopped}; + PROT_EXEC, PROT_READ, PROT_WRITE, PTRACE_EVENT_CLONE, + SIG_DFL, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, SIGCONT, SIGTERM, + WCONTINUED, WNOHANG, WUNTRACED, wifcontinued, wifstopped}; use crate::syscall::validate::{validate_slice, validate_slice_mut}; use crate::syscall; @@ -585,6 +585,22 @@ pub fn clone(flags: usize, stack_base: usize) -> Result<ContextId> { } } + let ptrace_event = PtraceEvent { + tag: PTRACE_EVENT_CLONE, + data: PtraceEventContent { + clone: pid.into() + } + }; + + if ptrace::send_event(ptrace_event).is_some() { + // Freeze the clone, allow ptrace to put breakpoints + // to it before it starts + let contexts = context::contexts(); + let context = contexts.get(pid).expect("Newly created context doesn't exist??"); + let mut context = context.write(); + context.ptrace_stop = true; + } + // Race to pick up the new process! ipi(IpiKind::Switch, IpiTarget::Other); @@ -1109,7 +1125,7 @@ pub fn exit(status: usize) -> ! { }; // Alert any tracers waiting for process (important: AFTER sending waitpid event) - ptrace::close(pid); + ptrace::close_tracee(pid); { let contexts = context::contexts(); diff --git a/syscall b/syscall index 49dd22260bd8bada8b835d12ee8e460a5a1c4af4..eddcb80eb7c2d43dedf0ba2ee514b54b0b8fafc7 160000 --- a/syscall +++ b/syscall @@ -1 +1 @@ -Subproject commit 49dd22260bd8bada8b835d12ee8e460a5a1c4af4 +Subproject commit eddcb80eb7c2d43dedf0ba2ee514b54b0b8fafc7