WIP(ptrace): Add ptrace event system for catching child forks

This commit is contained in:
jD91mZM2
2019-07-19 17:21:21 +02:00
parent 3d44242407
commit be867ae5f1
5 changed files with 321 additions and 111 deletions

View File

@@ -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(())
}
// ____ _ _

View File

@@ -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)
}

View File

@@ -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();

View File

@@ -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();

Submodule syscall updated: 49dd22260b...eddcb80eb7