blob: 426855782707f73d1e94c8f8aff70ab52c5accbc [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{
arch::execution::new_syscall_from_state,
mm::{DumpPolicy, MemoryAccessor, MemoryAccessorExt},
security,
signals::{
send_signal_first, send_standard_signal, syscalls::WaitingOptions, SignalDetail,
SignalInfo, SignalInfoHeader, SI_HEADER_SIZE,
},
task::{
waiter::WaitQueue, CurrentTask, Kernel, PidTable, ProcessSelector, StopState, Task,
TaskMutableState, ThreadGroup, ThreadState, ZombieProcess,
},
vfs::parse_unsigned_file,
};
use bitflags::bitflags;
use starnix_logging::track_stub;
use starnix_sync::{LockBefore, Locked, MmDumpable};
use starnix_syscalls::{decls::SyscallDecl, SyscallResult};
use starnix_uapi::{
auth::{CAP_SYS_PTRACE, PTRACE_MODE_ATTACH_REALCREDS},
clone_args,
elf::ElfNoteType,
errno, error,
errors::Errno,
iovec,
ownership::{OwnedRef, Releasable, WeakRef},
pid_t, ptrace_syscall_info,
signals::{SigSet, Signal, UncheckedSignal, SIGCHLD, SIGKILL, SIGSTOP, SIGTRAP},
user_address::{UserAddress, UserRef},
user_regs_struct, PTRACE_CONT, PTRACE_DETACH, PTRACE_EVENT_CLONE, PTRACE_EVENT_EXEC,
PTRACE_EVENT_EXIT, PTRACE_EVENT_FORK, PTRACE_EVENT_SECCOMP, PTRACE_EVENT_STOP,
PTRACE_EVENT_VFORK, PTRACE_EVENT_VFORK_DONE, PTRACE_GETEVENTMSG, PTRACE_GETREGSET,
PTRACE_GETSIGINFO, PTRACE_GETSIGMASK, PTRACE_GET_SYSCALL_INFO, PTRACE_INTERRUPT, PTRACE_KILL,
PTRACE_LISTEN, PTRACE_O_TRACECLONE, PTRACE_O_TRACEEXEC, PTRACE_O_TRACEEXIT, PTRACE_O_TRACEFORK,
PTRACE_O_TRACESYSGOOD, PTRACE_O_TRACEVFORK, PTRACE_O_TRACEVFORKDONE, PTRACE_PEEKDATA,
PTRACE_PEEKTEXT, PTRACE_PEEKUSR, PTRACE_POKEDATA, PTRACE_POKETEXT, PTRACE_POKEUSR,
PTRACE_SETOPTIONS, PTRACE_SETSIGINFO, PTRACE_SETSIGMASK, PTRACE_SYSCALL,
PTRACE_SYSCALL_INFO_ENTRY, PTRACE_SYSCALL_INFO_EXIT, PTRACE_SYSCALL_INFO_NONE, SI_MAX_SIZE,
};
use std::collections::BTreeMap;
use std::sync::{atomic::Ordering, Arc};
use zerocopy::FromBytes;
#[cfg(any(target_arch = "x86_64"))]
use starnix_uapi::{user, PTRACE_GETREGS};
/// For most of the time, for the purposes of ptrace, a tracee is either "going"
/// or "stopped". However, after certain ptrace calls, there are special rules
/// to be followed.
#[derive(Clone, Default, PartialEq)]
pub enum PtraceStatus {
/// Proceed as otherwise indicated by the task's stop status.
#[default]
Default,
/// Resuming after a ptrace_cont with a signal, so do not stop for signal-delivery-stop
Continuing,
/// "The state of the tracee after PTRACE_LISTEN is somewhat of a
/// gray area: it is not in any ptrace-stop (ptrace commands won't work on it,
/// and it will deliver waitpid(2) notifications), but it also may be considered
/// "stopped" because it is not executing instructions (is not scheduled), and
/// if it was in group-stop before PTRACE_LISTEN, it will not respond to signals
/// until SIGCONT is received."
Listening,
}
impl PtraceStatus {
pub fn is_continuing(&self) -> bool {
*self == PtraceStatus::Continuing
}
}
/// Indicates the way that ptrace attached to the task.
#[derive(Copy, Clone, PartialEq)]
pub enum PtraceAttachType {
/// Attached with PTRACE_ATTACH
Attach,
/// Attached with PTRACE_SEIZE
Seize,
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct PtraceOptions: u32 {
const EXITKILL = starnix_uapi::PTRACE_O_EXITKILL;
const TRACECLONE = starnix_uapi::PTRACE_O_TRACECLONE;
const TRACEEXEC = starnix_uapi::PTRACE_O_TRACEEXEC;
const TRACEEXIT = starnix_uapi::PTRACE_O_TRACEEXIT;
const TRACEFORK = starnix_uapi::PTRACE_O_TRACEFORK;
const TRACESYSGOOD = starnix_uapi::PTRACE_O_TRACESYSGOOD;
const TRACEVFORK = starnix_uapi::PTRACE_O_TRACEVFORK;
const TRACEVFORKDONE = starnix_uapi::PTRACE_O_TRACEVFORKDONE;
const TRACESECCOMP = starnix_uapi::PTRACE_O_TRACESECCOMP;
const SUSPEND_SECCOMP = starnix_uapi::PTRACE_O_SUSPEND_SECCOMP;
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum PtraceEvent {
#[default]
None = 0,
Stop = PTRACE_EVENT_STOP,
Clone = PTRACE_EVENT_CLONE,
Fork = PTRACE_EVENT_FORK,
Vfork = PTRACE_EVENT_VFORK,
VforkDone = PTRACE_EVENT_VFORK_DONE,
Exec = PTRACE_EVENT_EXEC,
Exit = PTRACE_EVENT_EXIT,
Seccomp = PTRACE_EVENT_SECCOMP,
}
impl PtraceEvent {
pub fn from_option(option: &PtraceOptions) -> Self {
match *option {
PtraceOptions::TRACECLONE => PtraceEvent::Clone,
PtraceOptions::TRACEFORK => PtraceEvent::Fork,
PtraceOptions::TRACEVFORK => PtraceEvent::Vfork,
PtraceOptions::TRACEVFORKDONE => PtraceEvent::VforkDone,
PtraceOptions::TRACEEXEC => PtraceEvent::Exec,
PtraceOptions::TRACEEXIT => PtraceEvent::Exit,
PtraceOptions::TRACESECCOMP => PtraceEvent::Seccomp,
_ => unreachable!("Bad ptrace event specified"),
}
}
}
/// Information about what caused a ptrace-event-stop.
pub struct PtraceEventData {
/// The event that caused the task to stop (e.g., PTRACE_EVENT_TRACEFORK or PTRACE_EVENT_EXIT).
pub event: PtraceEvent,
/// The message associated with the event (e.g., tid, exit status)..
pub msg: u64,
}
impl PtraceEventData {
pub fn new(option: PtraceOptions, msg: u64) -> Self {
Self { event: PtraceEvent::from_option(&option), msg }
}
pub fn new_from_event(event: PtraceEvent, msg: u64) -> Self {
Self { event, msg }
}
}
/// The ptrace state that a new task needs to connect to the same tracer as the
/// task that clones it.
#[derive(Clone)]
pub struct PtraceCoreState {
/// The pid of the tracer
pub pid: pid_t,
/// Whether the attach was a seize or an attach. There are a few subtle
/// differences in behavior of the different attach types - see ptrace(2).
pub attach_type: PtraceAttachType,
/// The options set by PTRACE_SETOPTIONS
pub options: PtraceOptions,
/// The tracer waits on this WaitQueue to find out if the tracee has done
/// something worth being notified about.
pub tracer_waiters: Arc<WaitQueue>,
}
impl PtraceCoreState {
pub fn has_option(&self, option: PtraceOptions) -> bool {
self.options.contains(option)
}
}
/// Per-task ptrace-related state
pub struct PtraceState {
/// The core state of the tracer, which can be shared between processes
pub core_state: PtraceCoreState,
/// The tracee waits on this WaitQueue to find out when it should stop or wake
/// for ptrace-related shenanigans.
pub tracee_waiters: WaitQueue,
/// The signal that caused the task to enter the given state (for
/// signal-delivery-stop)
pub last_signal: Option<SignalInfo>,
/// Whether waitpid() will return the last signal. The presence of last_signal
/// can't be used for that, because that needs to be saved for GETSIGINFO.
pub last_signal_waitable: bool,
/// Data about the PTRACE_EVENT that caused the most recent stop (if any).
pub event_data: Option<PtraceEventData>,
/// Indicates whether the last ptrace call put this thread into a state with
/// special semantics for stopping behavior.
pub stop_status: PtraceStatus,
/// For SYSCALL_INFO_EXIT
pub last_syscall_was_error: bool,
}
impl PtraceState {
pub fn new(pid: pid_t, attach_type: PtraceAttachType, options: PtraceOptions) -> Self {
PtraceState {
core_state: PtraceCoreState {
pid,
attach_type,
options,
tracer_waiters: Arc::new(WaitQueue::default()),
},
tracee_waiters: WaitQueue::default(),
last_signal: None,
last_signal_waitable: false,
event_data: None,
stop_status: PtraceStatus::default(),
last_syscall_was_error: false,
}
}
pub fn get_pid(&self) -> pid_t {
self.core_state.pid
}
pub fn set_pid(&mut self, pid: pid_t) {
self.core_state.pid = pid;
}
pub fn is_seized(&self) -> bool {
self.core_state.attach_type == PtraceAttachType::Seize
}
pub fn get_attach_type(&self) -> PtraceAttachType {
self.core_state.attach_type
}
pub fn is_waitable(&self, stop: StopState, options: &WaitingOptions) -> bool {
if self.stop_status == PtraceStatus::Listening {
// Waiting for any change of state
return self.last_signal_waitable;
}
if !options.wait_for_continued && !stop.is_stopping_or_stopped() {
// Only waiting for stops, but is not stopped.
return false;
}
self.last_signal_waitable && !stop.is_in_progress()
}
pub fn set_last_signal(&mut self, mut signal: Option<SignalInfo>) {
if let Some(ref mut siginfo) = signal {
// We don't want waiters to think the process was unstopped because
// of a sigkill. They will get woken when the process dies.
if siginfo.signal == SIGKILL {
return;
}
self.last_signal_waitable = true;
self.last_signal = signal;
}
}
pub fn set_last_event(&mut self, event: Option<PtraceEventData>) {
if event.is_some() {
self.event_data = event;
}
}
// Gets the last signal, and optionally clears the wait state of the ptrace.
pub fn get_last_signal(&mut self, keep_signal_waitable: bool) -> Option<SignalInfo> {
self.last_signal_waitable = keep_signal_waitable;
self.last_signal.clone()
}
pub fn has_option(&self, option: PtraceOptions) -> bool {
self.core_state.has_option(option)
}
pub fn set_options_from_bits(&mut self, option: u32) -> Result<(), Errno> {
if let Some(options) = PtraceOptions::from_bits(option) {
self.core_state.options = options;
Ok(())
} else {
error!(EINVAL)
}
}
pub fn get_options(&self) -> PtraceOptions {
self.core_state.options
}
/// Returns enough of the ptrace state to propagate it to a fork / clone / vforked task.
pub fn get_core_state(&self) -> PtraceCoreState {
self.core_state.clone()
}
pub fn tracer_waiters(&self) -> &Arc<WaitQueue> {
&self.core_state.tracer_waiters
}
/// Returns an (i32, ptrace_syscall_info) pair. The ptrace_syscall_info is
/// the info associated with the syscall that the target task is currently
/// blocked on, The i32 is (per ptrace(2)) "the number of bytes available to
/// be written by the kernel. If the size of the data to be written by the
/// kernel exceeds the size specified by the addr argument, the output data
/// is truncated."; ptrace(PTRACE_GET_SYSCALL_INFO) returns that value"
pub fn get_target_syscall(
&self,
target: &Task,
state: &TaskMutableState,
) -> Result<(i32, ptrace_syscall_info), Errno> {
#[cfg(target_arch = "x86_64")]
let arch = starnix_uapi::AUDIT_ARCH_X86_64;
#[cfg(target_arch = "aarch64")]
let arch = starnix_uapi::AUDIT_ARCH_AARCH64;
#[cfg(target_arch = "riscv64")]
let arch = starnix_uapi::AUDIT_ARCH_RISCV64;
let mut info = ptrace_syscall_info { arch, ..Default::default() };
let mut info_len = memoffset::offset_of!(ptrace_syscall_info, __bindgen_anon_1);
match &state.captured_thread_state {
Some(captured) => {
let registers = captured.thread_state.registers;
info.instruction_pointer = registers.instruction_pointer_register();
info.stack_pointer = registers.stack_pointer_register();
match target.load_stopped() {
StopState::SyscallEnterStopped => {
let syscall_decl = SyscallDecl::from_number(registers.syscall_register());
let syscall = new_syscall_from_state(syscall_decl, &captured.thread_state);
info.op = PTRACE_SYSCALL_INFO_ENTRY as u8;
let entry = linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_1 {
nr: syscall.decl.number,
args: [
syscall.arg0.raw(),
syscall.arg1.raw(),
syscall.arg2.raw(),
syscall.arg3.raw(),
syscall.arg4.raw(),
syscall.arg5.raw(),
],
};
info_len += memoffset::offset_of!(
linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_1,
args
) + std::mem::size_of_val(&entry.args);
info.__bindgen_anon_1.entry = entry;
}
StopState::SyscallExitStopped => {
info.op = PTRACE_SYSCALL_INFO_EXIT as u8;
let exit = linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_2 {
rval: registers.return_register() as i64,
is_error: state
.ptrace
.as_ref()
.map_or(0, |ptrace| ptrace.last_syscall_was_error as u8),
..Default::default()
};
info_len += memoffset::offset_of!(
linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_2,
is_error
) + std::mem::size_of_val(&exit.is_error);
info.__bindgen_anon_1.exit = exit;
}
_ => {
info.op = PTRACE_SYSCALL_INFO_NONE as u8;
}
};
}
_ => (),
}
Ok((info_len as i32, info))
}
/// Gets the core state for this ptrace if the options set on this ptrace
/// match |trace_kind|. Returns a pair: the trace option you *should* use
/// (sometimes this is different from the one that the caller thinks it
/// should use), and the core state.
pub fn get_core_state_for_clone(
&self,
clone_args: &clone_args,
) -> (PtraceOptions, Option<PtraceCoreState>) {
// ptrace(2): If the tracee calls clone(2) with the CLONE_VFORK flag,
// PTRACE_EVENT_VFORK will be delivered instead if PTRACE_O_TRACEVFORK
// is set, otherwise if the tracee calls clone(2) with the exit signal
// set to SIGCHLD, PTRACE_EVENT_FORK will be delivered if
// PTRACE_O_TRACEFORK is set.
let trace_type = if clone_args.flags & (starnix_uapi::CLONE_UNTRACED as u64) != 0 {
PtraceOptions::empty()
} else {
if clone_args.flags & (starnix_uapi::CLONE_VFORK as u64) != 0 {
PtraceOptions::TRACEVFORK
} else if clone_args.exit_signal != (starnix_uapi::SIGCHLD as u64) {
PtraceOptions::TRACECLONE
} else {
PtraceOptions::TRACEFORK
}
};
if !self.has_option(trace_type)
&& (clone_args.flags & (starnix_uapi::CLONE_PTRACE as u64) == 0)
{
return (PtraceOptions::empty(), None);
}
(trace_type, Some(self.get_core_state()))
}
}
/// A list of zombie processes that were traced by a given tracer, but which
/// have not yet notified that tracer of their exit. Once the tracer is
/// notified, the original parent will be notified.
pub struct ZombiePtraces {
/// A list of zombies that have to be delivered to the ptracer. The key is
/// the zombie. The value is a (threadgroup, zombie) pair, where the zombie
/// has to be delivered to the threadgroup when we have delivered the zombie
/// to the ptracer.
zombies: BTreeMap<ZombieProcess, Option<(Arc<ThreadGroup>, OwnedRef<ZombieProcess>)>>,
}
impl ZombiePtraces {
pub fn new() -> Self {
Self { zombies: BTreeMap::new() }
}
/// Adds a zombie tracee to the list, but does not provide a parent task to
/// notify when the tracer is done.
pub fn add(&mut self, zombie: ZombieProcess) {
if self.zombies.contains_key(&zombie) {
return;
}
self.zombies.insert(zombie, None);
}
/// Delete any zombie ptracees that do not match the predicate given.
pub fn retain(&mut self, f: &mut dyn FnMut(&ZombieProcess) -> bool) {
self.zombies.retain(|z, _| f(&z))
}
/// Provide a parent task and a zombie to notify when the tracer has been
/// notified.
pub fn set_parent_of(
&mut self,
tracee: pid_t,
new_zombie: Option<OwnedRef<ZombieProcess>>,
new_parent: Arc<ThreadGroup>,
) {
let mut zombie = None;
for (z, _) in &self.zombies {
if z.pid == tracee {
zombie = Some(z.copy_for_key());
break;
}
}
let Some(zombie) = zombie else {
if let Some(new_zombie) = new_zombie {
self.zombies.insert(new_zombie.copy_for_key(), Some((new_parent, new_zombie)));
}
return;
};
let Some((zombie, deferred)) = self.zombies.remove_entry(&zombie) else {
return;
};
if let Some(new_zombie) = new_zombie {
self.zombies.insert(zombie, Some((new_parent, new_zombie)));
return;
}
if let Some((_, real_zombie)) = deferred {
self.zombies.insert(zombie, Some((new_parent, real_zombie)));
}
return;
}
/// When a parent dies without having been notified, replace it with a given
/// new parent.
pub fn reparent(pids: &PidTable, old_parent: &Arc<ThreadGroup>, new_parent: &Arc<ThreadGroup>) {
let mut lockless_list = vec![];
old_parent.read().deferred_zombie_ptracers.iter().for_each(|z| lockless_list.push(*z));
for (ptracer, tracee) in &lockless_list {
let task_ref = pids.get_task(*ptracer);
let Some(task) = task_ref.upgrade() else {
continue;
};
task.thread_group.write().zombie_ptracees.set_parent_of(
*tracee,
None,
new_parent.clone(),
);
}
let mut new_state = new_parent.write();
new_state.deferred_zombie_ptracers.append(&mut lockless_list);
}
/// Empty the table and notify all of the remaining parents. Used if the
/// tracer terminates or detaches without acknowledging all pending tracees.
pub fn release(&mut self, pids: &mut PidTable) {
let mut entry = self.zombies.pop_first();
while let Some((zombie, deferred)) = entry {
if let Some((tg, z)) = deferred {
tg.do_zombie_notifications(z);
}
zombie.release(pids);
entry = self.zombies.pop_first();
}
}
/// Returns true iff there is a zombie matching the given selector.
pub fn has_match(&self, selector: &ProcessSelector, pids: &PidTable) -> bool {
self.zombies.keys().any(|p| selector.do_match(p.pid, pids))
}
/// Returns a zombie matching the given selector and options, and
/// (optionally) a thread group to notify after the caller has consumed that
/// zombie.
pub fn get_waitable_entry(
&mut self,
selector: ProcessSelector,
options: &WaitingOptions,
) -> Option<(ZombieProcess, Option<(Arc<ThreadGroup>, OwnedRef<ZombieProcess>)>)> {
// The zombies whose pid matches the pid selector queried.
let zombie_matches_pid_selector = |zombie: &ZombieProcess| match selector {
ProcessSelector::Any => true,
ProcessSelector::Pid(pid) => zombie.pid == pid,
ProcessSelector::Pgid(pgid) => zombie.pgid == pgid,
};
// The zombies whose exit signal matches the waiting options queried.
let zombie_matches_wait_options: Box<dyn Fn(&ZombieProcess) -> bool> =
if options.wait_for_all {
Box::new(|_zombie: &ZombieProcess| true)
} else {
// A "clone" zombie is one which has delivered no signal, or a
// signal other than SIGCHLD to its parent upon termination.
Box::new(|zombie: &ZombieProcess| {
options.wait_for_clone == (zombie.exit_info.exit_signal != Some(SIGCHLD))
})
};
// We look for the last zombie in the vector that matches pid
// selector and waiting options
let Some(found_zombie) = self.zombies.keys().rfind(|zombie: &&ZombieProcess| {
zombie_matches_wait_options(*zombie) && zombie_matches_pid_selector(*zombie)
}) else {
return None;
};
let zombie = found_zombie.copy_for_key();
let result;
if !options.keep_waitable_state {
// Maybe notify child waiters.
result = self.zombies.remove_entry(&zombie.copy_for_key());
} else {
result = Some((zombie, None));
}
result
}
}
/// Scope definitions for Yama. For full details, see ptrace(2).
/// 1 means tracer needs to have CAP_SYS_PTRACE or be a parent / child
/// process. This is the default.
const RESTRICTED_SCOPE: u8 = 1;
/// 2 means tracer needs to have CAP_SYS_PTRACE
const ADMIN_ONLY_SCOPE: u8 = 2;
/// 3 means no process can attach.
const NO_ATTACH_SCOPE: u8 = 3;
// PR_SET_PTRACER_ANY is defined as ((unsigned long) -1),
// which is not understood by bindgen.
pub const PR_SET_PTRACER_ANY: i32 = -1;
/// Indicates processes specifically allowed to trace a given process if using
/// RESTRICTED_SCOPE. Used by prctl(PR_SET_PTRACER).
#[derive(Copy, Clone, Default, PartialEq)]
pub enum PtraceAllowedPtracers {
#[default]
None,
Some(pid_t),
Any,
}
/// Continues the target thread, optionally detaching from it.
/// |data| is treated as it is in PTRACE_CONT.
/// |new_status| is the PtraceStatus to set for this trace.
/// |detach| will cause the tracer to detach from the tracee.
fn ptrace_cont(tracee: &Task, data: &UserAddress, detach: bool) -> Result<(), Errno> {
let data = data.ptr() as u64;
let new_state;
let mut siginfo = if data != 0 {
let signal = Signal::try_from(UncheckedSignal::new(data))?;
Some(SignalInfo::default(signal))
} else {
None
};
let mut state = tracee.write();
let is_listen = state.is_ptrace_listening();
if tracee.load_stopped().is_waking_or_awake() && !is_listen {
if detach {
state.set_ptrace(None)?;
}
return error!(EIO);
}
if !state.can_accept_ptrace_commands() && !detach {
return error!(ESRCH);
}
if let Some(ref mut ptrace) = &mut state.ptrace {
if data != 0 {
new_state = PtraceStatus::Continuing;
if let Some(ref mut last_signal) = &mut ptrace.last_signal {
if let Some(si) = siginfo {
let new_signal = si.signal;
last_signal.signal = new_signal;
}
siginfo = Some(last_signal.clone());
}
} else {
new_state = PtraceStatus::Default;
ptrace.last_signal = None;
ptrace.event_data = None;
}
ptrace.stop_status = new_state;
if is_listen {
state.notify_ptracees();
}
}
if let Some(siginfo) = siginfo {
// This will wake up the task for us, and also release state
send_signal_first(&tracee, state, siginfo);
} else {
state.set_stopped(StopState::Waking, None, None, None);
drop(state);
tracee.thread_group.set_stopped(StopState::Waking, None, false);
}
if detach {
tracee.write().set_ptrace(None)?;
}
Ok(())
}
fn ptrace_interrupt(tracee: &Task) -> Result<(), Errno> {
let mut state = tracee.write();
if let Some(ref mut ptrace) = &mut state.ptrace {
if !ptrace.is_seized() {
return error!(EIO);
}
let status = ptrace.stop_status.clone();
ptrace.stop_status = PtraceStatus::Default;
let event_data = Some(PtraceEventData::new_from_event(PtraceEvent::Stop, 0));
if status == PtraceStatus::Listening {
let signal = ptrace.last_signal.clone();
// "If the tracee was already stopped by a signal and PTRACE_LISTEN
// was sent to it, the tracee stops with PTRACE_EVENT_STOP and
// WSTOPSIG(status) returns the stop signal"
state.set_stopped(StopState::PtraceEventStopped, signal, None, event_data);
} else {
state.set_stopped(
StopState::PtraceEventStopping,
Some(SignalInfo::default(SIGTRAP)),
None,
event_data,
);
drop(state);
tracee.interrupt();
}
}
Ok(())
}
fn ptrace_listen(tracee: &Task) -> Result<(), Errno> {
let mut state = tracee.write();
if let Some(ref mut ptrace) = &mut state.ptrace {
if !ptrace.is_seized()
|| (ptrace.last_signal_waitable
&& ptrace
.event_data
.as_ref()
.is_some_and(|event_data| event_data.event != PtraceEvent::Stop))
{
return error!(EIO);
}
ptrace.stop_status = PtraceStatus::Listening;
}
Ok(())
}
pub fn ptrace_detach(
thread_group: Arc<ThreadGroup>,
tracee: &Task,
data: &UserAddress,
) -> Result<(), Errno> {
if let Err(x) = ptrace_cont(&tracee, &data, true) {
return Err(x);
}
security::clear_ptracer_sid(tracee);
let tid = tracee.get_tid();
thread_group.ptracees.lock().remove(&tid);
thread_group.write().zombie_ptracees.retain(&mut |zombie: &ZombieProcess| zombie.pid != tid);
Ok(())
}
/// For all ptrace requests that require an attached tracee
pub fn ptrace_dispatch(
current_task: &mut CurrentTask,
request: u32,
pid: pid_t,
addr: UserAddress,
data: UserAddress,
) -> Result<SyscallResult, Errno> {
let weak_init = current_task.kernel().pids.read().get_task(pid);
let tracee = weak_init.upgrade().ok_or_else(|| errno!(ESRCH))?;
if let Some(ptrace) = &tracee.read().ptrace {
if ptrace.get_pid() != current_task.get_tid() {
return error!(ESRCH);
}
}
// These requests may be run without the thread in a stop state, or
// check the stop state themselves.
match request {
PTRACE_KILL => {
let mut siginfo = SignalInfo::default(SIGKILL);
siginfo.code = (linux_uapi::SIGTRAP | PTRACE_KILL << 8) as i32;
send_standard_signal(&tracee, siginfo);
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_INTERRUPT => {
ptrace_interrupt(tracee.as_ref())?;
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_LISTEN => {
ptrace_listen(&tracee)?;
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_CONT => {
ptrace_cont(&tracee, &data, false)?;
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_SYSCALL => {
tracee.trace_syscalls.store(true, std::sync::atomic::Ordering::Relaxed);
ptrace_cont(&tracee, &data, false)?;
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_DETACH => {
ptrace_detach(current_task.thread_group.clone(), tracee.as_ref(), &data)?;
return Ok(starnix_syscalls::SUCCESS);
}
_ => {}
}
// The remaining requests (to be added) require the thread to be stopped.
let mut state = tracee.write();
if !state.can_accept_ptrace_commands() {
return error!(ESRCH);
}
match request {
PTRACE_PEEKDATA | PTRACE_PEEKTEXT => {
// NB: The behavior of the syscall is different from the behavior in ptrace(2),
// which is provided by libc.
let src: UserRef<usize> = UserRef::from(addr);
let val = tracee.read_object(src)?;
let dst: UserRef<usize> = UserRef::from(data);
current_task.write_object(dst, &val)?;
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_POKEDATA | PTRACE_POKETEXT => {
let ptr: UserRef<usize> = UserRef::from(addr);
let val = data.ptr() as usize;
tracee.write_object(ptr, &val)?;
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_PEEKUSR => {
if let Some(ref mut captured) = &mut state.captured_thread_state {
let val = ptrace_peekuser(&mut captured.thread_state, addr.ptr() as usize)?;
let dst: UserRef<usize> = UserRef::from(data);
current_task.write_object(dst, &val)?;
return Ok(starnix_syscalls::SUCCESS);
}
error!(ESRCH)
}
PTRACE_POKEUSR => {
ptrace_pokeuser(&mut *state, data.ptr() as usize, addr.ptr() as usize)?;
return Ok(starnix_syscalls::SUCCESS);
}
PTRACE_GETREGSET => {
if let Some(ref mut captured) = state.captured_thread_state {
let uiv: UserRef<iovec> = UserRef::from(data);
let iv = current_task.read_object(uiv)?;
let base = iv.iov_base.addr;
let mut len = iv.iov_len as usize;
ptrace_getregset(
current_task,
&mut captured.thread_state,
ElfNoteType::try_from(addr.ptr() as usize)?,
base,
&mut len,
)?;
current_task.write_object(
UserRef::<usize>::new(UserAddress::from_ptr(
uiv.addr().ptr() + memoffset::offset_of!(iovec, iov_len),
)),
&(len as usize),
)?;
return Ok(starnix_syscalls::SUCCESS);
}
error!(ESRCH)
}
#[cfg(target_arch = "x86_64")]
PTRACE_GETREGS => {
if let Some(ref mut captured) = &mut state.captured_thread_state {
let mut len = usize::MAX;
ptrace_getregset(
current_task,
&mut captured.thread_state,
ElfNoteType::PrStatus,
data.ptr() as u64,
&mut len,
)?;
return Ok(starnix_syscalls::SUCCESS);
}
error!(ESRCH)
}
PTRACE_SETSIGMASK => {
// addr is the size of the buffer pointed to
// by data, but has to be sizeof(sigset_t).
if addr.ptr() != std::mem::size_of::<SigSet>() {
return error!(EINVAL);
}
// sigset comes from *data.
let src: UserRef<SigSet> = UserRef::from(data);
let val = current_task.read_object(src)?;
state.signals.set_mask(val);
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_GETSIGMASK => {
// addr is the size of the buffer pointed to
// by data, but has to be sizeof(sigset_t).
if addr.ptr() != std::mem::size_of::<SigSet>() {
return error!(EINVAL);
}
// sigset goes in *data.
let dst: UserRef<SigSet> = UserRef::from(data);
let val = state.signals.mask();
current_task.write_object(dst, &val)?;
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_GETSIGINFO => {
let dst: UserRef<u8> = UserRef::from(data);
if let Some(ptrace) = &state.ptrace {
if let Some(signal) = ptrace.last_signal.as_ref() {
current_task.write_objects(dst, &signal.as_siginfo_bytes())?;
} else {
return error!(EINVAL);
}
}
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_SETSIGINFO => {
// Rust will let us do this cast in a const assignment but not in a
// const generic constraint.
const SI_MAX_SIZE_AS_USIZE: usize = SI_MAX_SIZE as usize;
let siginfo_mem = current_task.read_memory_to_array::<SI_MAX_SIZE_AS_USIZE>(data)?;
let header = SignalInfoHeader::read_from(&siginfo_mem[..SI_HEADER_SIZE]).unwrap();
let mut bytes = [0u8; SI_MAX_SIZE as usize - SI_HEADER_SIZE];
bytes.copy_from_slice(&siginfo_mem[SI_HEADER_SIZE..SI_MAX_SIZE as usize]);
let details = SignalDetail::Raw { data: bytes };
let unchecked_signal = UncheckedSignal::new(header.signo as u64);
let signal = Signal::try_from(unchecked_signal)?;
let siginfo = SignalInfo {
signal,
errno: header.errno,
code: header.code,
detail: details,
force: false,
};
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.last_signal = Some(siginfo);
}
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_GET_SYSCALL_INFO => {
if let Some(ptrace) = &state.ptrace {
let (size, info) = ptrace.get_target_syscall(&tracee, &state)?;
let dst: UserRef<ptrace_syscall_info> = UserRef::from(data);
let len = std::cmp::min(std::mem::size_of::<ptrace_syscall_info>(), addr.ptr());
// SAFETY: ptrace_syscall_info does not implement FromBytes/AsBytes,
// so this has to happen manually.
let src = unsafe {
std::slice::from_raw_parts(
&info as *const ptrace_syscall_info as *const u8,
len as usize,
)
};
current_task.write_memory(dst.addr(), src)?;
Ok(size.into())
} else {
error!(ESRCH)
}
}
PTRACE_SETOPTIONS => {
let mask = data.ptr() as u32;
// This is what we currently support.
if mask != 0
&& (mask
& !(PTRACE_O_TRACESYSGOOD
| PTRACE_O_TRACECLONE
| PTRACE_O_TRACEFORK
| PTRACE_O_TRACEVFORK
| PTRACE_O_TRACEVFORKDONE
| PTRACE_O_TRACEEXEC
| PTRACE_O_TRACEEXIT)
!= 0)
{
return error!(ENOSYS);
}
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.set_options_from_bits(data.ptr() as u32)?;
}
Ok(starnix_syscalls::SUCCESS)
}
PTRACE_GETEVENTMSG => {
if let Some(ptrace) = &state.ptrace {
if let Some(event_data) = &ptrace.event_data {
let dst: UserRef<u64> = UserRef::from(data);
current_task.write_object(dst, &event_data.msg)?;
return Ok(starnix_syscalls::SUCCESS);
}
}
error!(EIO)
}
_ => {
track_stub!(TODO("https://fxbug.dev/322874463"), "ptrace", request);
error!(ENOSYS)
}
}
}
/// Makes the given thread group trace the given task.
fn do_attach(
thread_group: &ThreadGroup,
task: WeakRef<Task>,
attach_type: PtraceAttachType,
options: PtraceOptions,
) -> Result<(), Errno> {
if let Some(task_ref) = task.upgrade() {
thread_group.ptracees.lock().insert(task_ref.get_tid(), (&task_ref).into());
{
let process_state = &mut task_ref.thread_group.write();
let mut state = task_ref.write();
state.set_ptrace(Some(PtraceState::new(thread_group.leader, attach_type, options)))?;
// If the tracee is already stopped, make sure that the tracer can
// identify that right away.
if process_state.is_waitable()
&& process_state.base.load_stopped() == StopState::GroupStopped
&& task_ref.load_stopped() == StopState::GroupStopped
{
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.last_signal_waitable = true;
}
}
}
return Ok(());
}
// The tracee is either the current thread, or there is a live ref to it outside
// this function.
unreachable!("Tracee thread not found");
}
fn check_caps_for_attach(ptrace_scope: u8, current_task: &CurrentTask) -> Result<(), Errno> {
if ptrace_scope == ADMIN_ONLY_SCOPE && !current_task.creds().has_capability(CAP_SYS_PTRACE) {
// Admin only use of ptrace
return error!(EPERM);
}
if ptrace_scope == NO_ATTACH_SCOPE {
// No use of ptrace
return error!(EPERM);
}
Ok(())
}
/// Uses the given core ptrace state (including tracer, attach type, etc) to
/// attach to another task, given by `tracee_task`. Also sends a signal to stop
/// tracee_task. Typical for when inheriting ptrace state from another task.
pub fn ptrace_attach_from_state(
tracee_task: &OwnedRef<Task>,
ptrace_state: PtraceCoreState,
) -> Result<(), Errno> {
{
let weak_init = tracee_task.thread_group.kernel.pids.read().get_task(ptrace_state.pid);
let tracer_task = weak_init.upgrade().ok_or_else(|| errno!(ESRCH))?;
do_attach(
&tracer_task.thread_group,
WeakRef::from(tracee_task),
ptrace_state.attach_type,
ptrace_state.options,
)?;
}
let mut state = tracee_task.write();
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.core_state.tracer_waiters = Arc::clone(&ptrace_state.tracer_waiters);
}
// The newly started tracee starts with a signal that depends on the attach type.
let signal = if ptrace_state.attach_type == PtraceAttachType::Seize {
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.set_last_event(Some(PtraceEventData::new_from_event(PtraceEvent::Stop, 0)));
}
SignalInfo::default(SIGTRAP)
} else {
SignalInfo::default(SIGSTOP)
};
send_signal_first(tracee_task, state, signal);
Ok(())
}
pub fn ptrace_traceme(current_task: &mut CurrentTask) -> Result<SyscallResult, Errno> {
let ptrace_scope = current_task.kernel().ptrace_scope.load(Ordering::Relaxed);
check_caps_for_attach(ptrace_scope, current_task)?;
let parent = current_task.thread_group.read().parent.clone();
if let Some(parent) = parent {
security::check_ptrace_traceme_access_and_update_state(&parent, current_task)?;
let task_ref = OwnedRef::temp(&current_task.task);
do_attach(&parent, (&task_ref).into(), PtraceAttachType::Attach, PtraceOptions::empty())?;
Ok(starnix_syscalls::SUCCESS)
} else {
error!(EPERM)
}
}
pub fn ptrace_attach<L>(
locked: &mut Locked<'_, L>,
current_task: &mut CurrentTask,
pid: pid_t,
attach_type: PtraceAttachType,
data: UserAddress,
) -> Result<SyscallResult, Errno>
where
L: LockBefore<MmDumpable>,
{
let ptrace_scope = current_task.kernel().ptrace_scope.load(Ordering::Relaxed);
check_caps_for_attach(ptrace_scope, current_task)?;
let weak_task = current_task.kernel().pids.read().get_task(pid);
let tracee = weak_task.upgrade().ok_or_else(|| errno!(ESRCH))?;
security::check_ptrace_attach_access_and_update_state(&current_task, &tracee)?;
if tracee.thread_group == current_task.thread_group {
return error!(EPERM);
}
if *tracee.mm().dumpable.lock(locked) == DumpPolicy::Disable {
return error!(EPERM);
}
if ptrace_scope == RESTRICTED_SCOPE {
// This only allows us to attach to descendants and tasks that have
// explicitly allowlisted us with PR_SET_PTRACER.
let mut ttg = tracee.thread_group.read().parent.clone();
let mut is_parent = false;
let my_pid = current_task.thread_group.leader;
while let Some(target) = ttg {
if target.as_ref().leader == my_pid {
is_parent = true;
break;
}
ttg = target.read().parent.clone();
}
if !is_parent {
match tracee.thread_group.read().allowed_ptracers {
PtraceAllowedPtracers::None => return error!(EPERM),
PtraceAllowedPtracers::Some(pid) => {
if my_pid != pid {
return error!(EPERM);
}
}
PtraceAllowedPtracers::Any => {}
}
}
}
current_task.check_ptrace_access_mode(locked, PTRACE_MODE_ATTACH_REALCREDS, &tracee)?;
do_attach(&current_task.thread_group, weak_task.clone(), attach_type, PtraceOptions::empty())?;
if attach_type == PtraceAttachType::Attach {
send_standard_signal(&tracee, SignalInfo::default(SIGSTOP));
} else if attach_type == PtraceAttachType::Seize {
// When seizing, |data| should be used as the options bitmask.
if let Some(task_ref) = weak_task.upgrade() {
let mut state = task_ref.write();
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.set_options_from_bits(data.ptr() as u32)?;
}
}
}
Ok(starnix_syscalls::SUCCESS)
}
/// Implementation of ptrace(PTRACE_PEEKUSER). The user struct holds the
/// registers and other information about the process. See ptrace(2) and
/// sys/user.h for full details.
pub fn ptrace_peekuser(thread_state: &mut ThreadState, offset: usize) -> Result<usize, Errno> {
#[cfg(any(target_arch = "x86_64"))]
if offset >= std::mem::size_of::<user>() {
return error!(EIO);
}
if offset < std::mem::size_of::<user_regs_struct>() {
let result = thread_state.get_user_register(offset)?;
return Ok(result);
}
error!(EIO)
}
pub fn ptrace_pokeuser(
state: &mut TaskMutableState,
value: usize,
offset: usize,
) -> Result<(), Errno> {
if let Some(ref mut thread_state) = state.captured_thread_state {
thread_state.dirty = true;
#[cfg(any(target_arch = "x86_64"))]
if offset >= std::mem::size_of::<user>() {
return error!(EIO);
}
if offset < std::mem::size_of::<user_regs_struct>() {
return thread_state.thread_state.set_user_register(offset, value);
}
}
error!(EIO)
}
pub fn ptrace_getregset(
current_task: &CurrentTask,
thread_state: &mut ThreadState,
regset_type: ElfNoteType,
base: u64,
len: &mut usize,
) -> Result<(), Errno> {
match regset_type {
ElfNoteType::PrStatus => {
if *len < std::mem::size_of::<user_regs_struct>() {
return error!(EINVAL);
}
*len = std::cmp::min(*len, std::mem::size_of::<user_regs_struct>());
let mut i: usize = 0;
while i < *len {
let mut val = None;
thread_state
.registers
.apply_user_register(i, &mut |register| val = Some(*register as usize))?;
if let Some(val) = val {
current_task.write_object(
UserRef::<usize>::new(UserAddress::from_ptr((base as usize) + i)),
&val,
)?;
}
i += std::mem::size_of::<usize>();
}
Ok(())
}
_ => {
error!(EINVAL)
}
}
}
pub fn ptrace_set_scope(kernel: &Arc<Kernel>, data: &[u8]) -> Result<(), Errno> {
loop {
let ptrace_scope = kernel.ptrace_scope.load(Ordering::Relaxed);
if let Ok(val) = parse_unsigned_file::<u8>(data) {
// Legal values are 0<=val<=3, unless scope is NO_ATTACH - see Yama
// documentation.
if ptrace_scope == NO_ATTACH_SCOPE && val != NO_ATTACH_SCOPE {
return error!(EINVAL);
}
if val <= NO_ATTACH_SCOPE {
match kernel.ptrace_scope.compare_exchange(
ptrace_scope,
val,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => return Ok(()),
Err(_) => continue,
}
}
}
return error!(EINVAL);
}
}
pub fn ptrace_get_scope(kernel: &Arc<Kernel>) -> Vec<u8> {
let mut scope = kernel.ptrace_scope.load(Ordering::Relaxed).to_string();
scope.push('\n');
scope.into_bytes()
}
#[inline(never)]
pub fn ptrace_syscall_enter(current_task: &mut CurrentTask) {
let block = {
let mut state = current_task.write();
if state.ptrace.is_some() {
current_task.trace_syscalls.store(false, Ordering::Relaxed);
let mut sig = SignalInfo::default(SIGTRAP);
sig.code = (linux_uapi::SIGTRAP | 0x80) as i32;
if state
.ptrace
.as_ref()
.is_some_and(|ptrace| ptrace.has_option(PtraceOptions::TRACESYSGOOD))
{
sig.signal.set_ptrace_syscall_bit();
}
state.set_stopped(StopState::SyscallEnterStopping, Some(sig), None, None);
true
} else {
false
}
};
if block {
current_task.block_while_stopped();
}
}
#[inline(never)]
pub fn ptrace_syscall_exit(current_task: &mut CurrentTask, is_error: bool) {
let block = {
let mut state = current_task.write();
current_task.trace_syscalls.store(false, Ordering::Relaxed);
if state.ptrace.is_some() {
let mut sig = SignalInfo::default(SIGTRAP);
sig.code = (linux_uapi::SIGTRAP | 0x80) as i32;
if state
.ptrace
.as_ref()
.is_some_and(|ptrace| ptrace.has_option(PtraceOptions::TRACESYSGOOD))
{
sig.signal.set_ptrace_syscall_bit();
}
state.set_stopped(StopState::SyscallExitStopping, Some(sig), None, None);
if let Some(ref mut ptrace) = &mut state.ptrace {
ptrace.last_syscall_was_error = is_error;
}
true
} else {
false
}
};
if block {
current_task.block_while_stopped();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
task::syscalls::sys_prctl,
testing::{create_kernel_task_and_unlocked, create_task},
};
use starnix_uapi::PR_SET_PTRACER;
#[::fuchsia::test]
async fn test_set_ptracer() {
let (kernel, mut tracee, mut locked) = create_kernel_task_and_unlocked();
let mut tracer = create_task(&mut locked, &kernel, "tracer");
kernel.ptrace_scope.store(RESTRICTED_SCOPE, Ordering::Relaxed);
assert_eq!(
sys_prctl(&mut locked, &mut tracee, PR_SET_PTRACER, 0xFFF, 0, 0, 0),
error!(EINVAL)
);
assert_eq!(
ptrace_attach(
&mut locked,
&mut tracer,
tracee.as_ref().task.id,
PtraceAttachType::Attach,
UserAddress::NULL,
),
error!(EPERM)
);
assert!(sys_prctl(
&mut locked,
&mut tracee,
PR_SET_PTRACER,
tracer.thread_group.leader as u64,
0,
0,
0
)
.is_ok());
let mut not_tracer = create_task(&mut locked, &kernel, "not-tracer");
assert_eq!(
ptrace_attach(
&mut locked,
&mut not_tracer,
tracee.as_ref().task.id,
PtraceAttachType::Attach,
UserAddress::NULL,
),
error!(EPERM)
);
assert!(ptrace_attach(
&mut locked,
&mut tracer,
tracee.as_ref().task.id,
PtraceAttachType::Attach,
UserAddress::NULL,
)
.is_ok());
}
#[::fuchsia::test]
async fn test_set_ptracer_any() {
let (kernel, mut tracee, mut locked) = create_kernel_task_and_unlocked();
let mut tracer = create_task(&mut locked, &kernel, "tracer");
kernel.ptrace_scope.store(RESTRICTED_SCOPE, Ordering::Relaxed);
assert_eq!(
sys_prctl(&mut locked, &mut tracee, PR_SET_PTRACER, 0xFFF, 0, 0, 0),
error!(EINVAL)
);
assert_eq!(
ptrace_attach(
&mut locked,
&mut tracer,
tracee.as_ref().task.id,
PtraceAttachType::Attach,
UserAddress::NULL,
),
error!(EPERM)
);
assert!(sys_prctl(
&mut locked,
&mut tracee,
PR_SET_PTRACER,
PR_SET_PTRACER_ANY as u64,
0,
0,
0
)
.is_ok());
assert!(ptrace_attach(
&mut locked,
&mut tracer,
tracee.as_ref().task.id,
PtraceAttachType::Attach,
UserAddress::NULL,
)
.is_ok());
}
}