Skip to main content

starnix_core/ptrace/
ptrace.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::arch::execution::new_syscall_from_state;
6use crate::mm::{IOVecPtr, MemoryAccessor, MemoryAccessorExt};
7use crate::ptrace::StopState;
8use crate::security;
9use crate::signals::syscalls::WaitingOptions;
10use crate::signals::{
11    SignalDetail, SignalInfo, UncheckedSignalInfo, send_signal_first, send_standard_signal,
12};
13use crate::task::{
14    CurrentTask, Pid, PidTableGuard, ProcessSelector, Task, TaskMutableState, ThreadGroup,
15    ThreadState, WaitQueue, ZombieNotification, ZombieProcess,
16};
17use bitflags::bitflags;
18use starnix_logging::track_stub;
19use starnix_registers::HeapRegs;
20use starnix_syscalls::SyscallResult;
21use starnix_syscalls::decls::SyscallDecl;
22use starnix_types::ownership::{OwnedRef, Releasable};
23use starnix_uapi::auth::PTRACE_MODE_ATTACH_REALCREDS;
24use starnix_uapi::elf::ElfNoteType;
25use starnix_uapi::errors::Errno;
26use starnix_uapi::signals::{SIGKILL, SIGSTOP, SIGTRAP, SigSet, Signal, UncheckedSignal};
27#[allow(unused_imports)]
28use starnix_uapi::user_address::ArchSpecific;
29use starnix_uapi::user_address::{LongPtr, MultiArchUserRef, UserAddress, UserRef};
30use starnix_uapi::{
31    PTRACE_CONT, PTRACE_DETACH, PTRACE_EVENT_CLONE, PTRACE_EVENT_EXEC, PTRACE_EVENT_EXIT,
32    PTRACE_EVENT_FORK, PTRACE_EVENT_SECCOMP, PTRACE_EVENT_STOP, PTRACE_EVENT_VFORK,
33    PTRACE_EVENT_VFORK_DONE, PTRACE_GET_SYSCALL_INFO, PTRACE_GETEVENTMSG, PTRACE_GETREGSET,
34    PTRACE_GETSIGINFO, PTRACE_GETSIGMASK, PTRACE_INTERRUPT, PTRACE_KILL, PTRACE_LISTEN,
35    PTRACE_O_EXITKILL, PTRACE_O_TRACECLONE, PTRACE_O_TRACEEXEC, PTRACE_O_TRACEEXIT,
36    PTRACE_O_TRACEFORK, PTRACE_O_TRACESYSGOOD, PTRACE_O_TRACEVFORK, PTRACE_O_TRACEVFORKDONE,
37    PTRACE_PEEKDATA, PTRACE_PEEKTEXT, PTRACE_PEEKUSR, PTRACE_POKEDATA, PTRACE_POKETEXT,
38    PTRACE_POKEUSR, PTRACE_SETOPTIONS, PTRACE_SETREGSET, PTRACE_SETSIGINFO, PTRACE_SETSIGMASK,
39    PTRACE_SYSCALL, PTRACE_SYSCALL_INFO_ENTRY, PTRACE_SYSCALL_INFO_EXIT, PTRACE_SYSCALL_INFO_NONE,
40    clone_args, errno, error, ptrace_syscall_info, uapi,
41};
42use zerocopy::IntoBytes;
43
44use std::collections::BTreeMap;
45use std::sync::atomic::Ordering;
46use std::sync::{Arc, Weak};
47
48#[cfg(target_arch = "x86_64")]
49use starnix_uapi::{PTRACE_GETREGS, user};
50
51#[cfg(all(target_arch = "aarch64"))]
52use starnix_uapi::arch32::PTRACE_GETREGS;
53
54type UserRegsStructPtr =
55    MultiArchUserRef<starnix_uapi::user_regs_struct, starnix_uapi::arch32::user_regs_struct>;
56
57uapi::check_arch_independent_layout! {
58    ptrace_syscall_info {
59        op,
60        arch,
61        instruction_pointer,
62        stack_pointer,
63        __bindgen_anon_1,
64    }
65
66    ptrace_syscall_info__bindgen_ty_1 {
67        entry,
68        exit,
69        seccomp,
70    }
71
72    ptrace_syscall_info__bindgen_ty_1__bindgen_ty_1 {
73        nr,
74        args,
75    }
76
77    ptrace_syscall_info__bindgen_ty_1__bindgen_ty_2 {
78        rval,
79        is_error,
80    }
81
82    ptrace_syscall_info__bindgen_ty_1__bindgen_ty_3 {
83        nr,
84        args,
85        ret_data,
86    }
87}
88
89/// For most of the time, for the purposes of ptrace, a tracee is either "going"
90/// or "stopped".  However, after certain ptrace calls, there are special rules
91/// to be followed.
92#[derive(Clone, Default, PartialEq)]
93pub enum PtraceStatus {
94    /// Proceed as otherwise indicated by the task's stop status.
95    #[default]
96    Default,
97    /// Resuming after a ptrace_cont with a signal, so do not stop for signal-delivery-stop
98    Continuing,
99    /// "The state of the tracee after PTRACE_LISTEN is somewhat of a
100    /// gray area: it is not in any ptrace-stop (ptrace commands won't work on it,
101    /// and it will deliver waitpid(2) notifications), but it also may be considered
102    /// "stopped" because it is not executing instructions (is not scheduled), and
103    /// if it was in group-stop before PTRACE_LISTEN, it will not respond to signals
104    /// until SIGCONT is received."
105    Listening,
106}
107
108impl PtraceStatus {
109    pub fn is_continuing(&self) -> bool {
110        *self == PtraceStatus::Continuing
111    }
112}
113
114/// Indicates the way that ptrace attached to the task.
115#[derive(Copy, Clone, PartialEq)]
116pub enum PtraceAttachType {
117    /// Attached with PTRACE_ATTACH
118    Attach,
119    /// Attached with PTRACE_SEIZE
120    Seize,
121}
122
123bitflags! {
124    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
125    #[repr(transparent)]
126    pub struct PtraceOptions: u32 {
127        const EXITKILL = starnix_uapi::PTRACE_O_EXITKILL;
128        const TRACECLONE = starnix_uapi::PTRACE_O_TRACECLONE;
129        const TRACEEXEC = starnix_uapi::PTRACE_O_TRACEEXEC;
130        const TRACEEXIT = starnix_uapi::PTRACE_O_TRACEEXIT;
131        const TRACEFORK = starnix_uapi::PTRACE_O_TRACEFORK;
132        const TRACESYSGOOD = starnix_uapi::PTRACE_O_TRACESYSGOOD;
133        const TRACEVFORK = starnix_uapi::PTRACE_O_TRACEVFORK;
134        const TRACEVFORKDONE = starnix_uapi::PTRACE_O_TRACEVFORKDONE;
135        const TRACESECCOMP = starnix_uapi::PTRACE_O_TRACESECCOMP;
136        const SUSPEND_SECCOMP = starnix_uapi::PTRACE_O_SUSPEND_SECCOMP;
137    }
138}
139
140#[repr(u32)]
141#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
142pub enum PtraceEvent {
143    #[default]
144    None = 0,
145    Stop = PTRACE_EVENT_STOP,
146    Clone = PTRACE_EVENT_CLONE,
147    Fork = PTRACE_EVENT_FORK,
148    Vfork = PTRACE_EVENT_VFORK,
149    VforkDone = PTRACE_EVENT_VFORK_DONE,
150    Exec = PTRACE_EVENT_EXEC,
151    Exit = PTRACE_EVENT_EXIT,
152    Seccomp = PTRACE_EVENT_SECCOMP,
153}
154
155impl PtraceEvent {
156    pub fn from_option(option: &PtraceOptions) -> Self {
157        match *option {
158            PtraceOptions::TRACECLONE => PtraceEvent::Clone,
159            PtraceOptions::TRACEFORK => PtraceEvent::Fork,
160            PtraceOptions::TRACEVFORK => PtraceEvent::Vfork,
161            PtraceOptions::TRACEVFORKDONE => PtraceEvent::VforkDone,
162            PtraceOptions::TRACEEXEC => PtraceEvent::Exec,
163            PtraceOptions::TRACEEXIT => PtraceEvent::Exit,
164            PtraceOptions::TRACESECCOMP => PtraceEvent::Seccomp,
165            _ => unreachable!("Bad ptrace event specified"),
166        }
167    }
168}
169
170/// Information about what caused a ptrace-event-stop.
171pub struct PtraceEventData {
172    /// The event that caused the task to stop (e.g., PTRACE_EVENT_TRACEFORK or PTRACE_EVENT_EXIT).
173    pub event: PtraceEvent,
174
175    /// The message associated with the event (e.g., tid, exit status)..
176    pub msg: u64,
177}
178
179impl PtraceEventData {
180    pub fn new(option: PtraceOptions, msg: u64) -> Self {
181        Self { event: PtraceEvent::from_option(&option), msg }
182    }
183    pub fn new_from_event(event: PtraceEvent, msg: u64) -> Self {
184        Self { event, msg }
185    }
186}
187
188/// The ptrace state that a new task needs to connect to the same tracer as the
189/// task that clones it.
190#[derive(Clone)]
191pub struct PtraceCoreState {
192    /// The task of the tracer
193    pub task: Weak<Task>,
194
195    /// The thread group of the tracer
196    pub thread_group: Weak<ThreadGroup>,
197
198    /// Whether the attach was a seize or an attach.  There are a few subtle
199    /// differences in behavior of the different attach types - see ptrace(2).
200    pub attach_type: PtraceAttachType,
201
202    /// The options set by PTRACE_SETOPTIONS
203    pub options: PtraceOptions,
204
205    /// The tracer waits on this WaitQueue to find out if the tracee has done
206    /// something worth being notified about.
207    pub tracer_waiters: Arc<WaitQueue>,
208}
209
210impl PtraceCoreState {
211    pub fn has_option(&self, option: PtraceOptions) -> bool {
212        self.options.contains(option)
213    }
214}
215
216/// Per-task ptrace-related state
217pub struct PtraceState {
218    /// The core state of the tracer, which can be shared between processes
219    pub core_state: PtraceCoreState,
220
221    /// The tracee waits on this WaitQueue to find out when it should stop or wake
222    /// for ptrace-related shenanigans.
223    pub tracee_waiters: WaitQueue,
224
225    /// The signal that caused the task to enter the given state (for
226    /// signal-delivery-stop)
227    pub last_signal: Option<SignalInfo>,
228
229    /// Whether waitpid() will return the last signal.  The presence of last_signal
230    /// can't be used for that, because that needs to be saved for GETSIGINFO.
231    pub last_signal_waitable: bool,
232
233    /// Data about the PTRACE_EVENT that caused the most recent stop (if any).
234    pub event_data: Option<PtraceEventData>,
235
236    /// Indicates whether the last ptrace call put this thread into a state with
237    /// special semantics for stopping behavior.
238    pub stop_status: PtraceStatus,
239
240    /// For SYSCALL_INFO_EXIT
241    pub last_syscall_was_error: bool,
242}
243
244impl PtraceState {
245    pub fn new(
246        task: Weak<Task>,
247        thread_group: Weak<ThreadGroup>,
248        attach_type: PtraceAttachType,
249        options: PtraceOptions,
250    ) -> Box<Self> {
251        Box::new(PtraceState {
252            core_state: PtraceCoreState {
253                task,
254                thread_group,
255                attach_type,
256                options,
257                tracer_waiters: Arc::new(WaitQueue::default()),
258            },
259            tracee_waiters: WaitQueue::default(),
260            last_signal: None,
261            last_signal_waitable: false,
262            event_data: None,
263            stop_status: PtraceStatus::default(),
264            last_syscall_was_error: false,
265        })
266    }
267
268    pub fn is_seized(&self) -> bool {
269        self.core_state.attach_type == PtraceAttachType::Seize
270    }
271
272    pub fn get_attach_type(&self) -> PtraceAttachType {
273        self.core_state.attach_type
274    }
275
276    pub fn is_waitable(&self, stop: StopState, options: &WaitingOptions) -> bool {
277        if self.stop_status == PtraceStatus::Listening {
278            // Waiting for any change of state
279            return self.last_signal_waitable;
280        }
281        if !options.wait_for_continued && !stop.is_stopping_or_stopped() {
282            // Only waiting for stops, but is not stopped.
283            return false;
284        }
285        self.last_signal_waitable && !stop.is_in_progress()
286    }
287
288    pub fn set_last_signal(&mut self, mut signal: Option<SignalInfo>) {
289        if let Some(ref mut siginfo) = signal {
290            // We don't want waiters to think the process was unstopped because
291            // of a sigkill. They will get woken when the process dies.
292            if siginfo.signal == SIGKILL {
293                return;
294            }
295            self.last_signal_waitable = true;
296            self.last_signal = signal;
297        }
298    }
299
300    pub fn set_last_event(&mut self, event: Option<PtraceEventData>) {
301        if event.is_some() {
302            self.event_data = event;
303        }
304    }
305
306    pub fn get_last_signal_ref(&self) -> Option<&SignalInfo> {
307        self.last_signal.as_ref()
308    }
309
310    // Gets the last signal, and optionally clears the wait state of the ptrace.
311    pub fn get_last_signal(&mut self, keep_signal_waitable: bool) -> Option<SignalInfo> {
312        self.last_signal_waitable = keep_signal_waitable;
313        self.last_signal.clone()
314    }
315
316    pub fn has_option(&self, option: PtraceOptions) -> bool {
317        self.core_state.has_option(option)
318    }
319
320    pub fn set_options_from_bits(&mut self, option: u32) -> Result<(), Errno> {
321        if let Some(options) = PtraceOptions::from_bits(option) {
322            self.core_state.options = options;
323            Ok(())
324        } else {
325            error!(EINVAL)
326        }
327    }
328
329    pub fn get_options(&self) -> PtraceOptions {
330        self.core_state.options
331    }
332
333    /// Returns enough of the ptrace state to propagate it to a fork / clone / vforked task.
334    pub fn get_core_state(&self) -> PtraceCoreState {
335        self.core_state.clone()
336    }
337
338    pub fn tracer_waiters(&self) -> &Arc<WaitQueue> {
339        &self.core_state.tracer_waiters
340    }
341
342    /// Returns an (i32, ptrace_syscall_info) pair.  The ptrace_syscall_info is
343    /// the info associated with the syscall that the target task is currently
344    /// blocked on, The i32 is (per ptrace(2)) "the number of bytes available to
345    /// be written by the kernel.  If the size of the data to be written by the
346    /// kernel exceeds the size specified by the addr argument, the output data
347    /// is truncated."; ptrace(PTRACE_GET_SYSCALL_INFO) returns that value"
348    pub fn get_target_syscall(
349        &self,
350        target: &Task,
351        state: &TaskMutableState,
352    ) -> Result<(i32, ptrace_syscall_info), Errno> {
353        #[cfg(target_arch = "x86_64")]
354        let arch = starnix_uapi::AUDIT_ARCH_X86_64;
355        #[cfg(target_arch = "aarch64")]
356        let arch = starnix_uapi::AUDIT_ARCH_AARCH64;
357        #[cfg(target_arch = "riscv64")]
358        let arch = starnix_uapi::AUDIT_ARCH_RISCV64;
359
360        let mut info = ptrace_syscall_info { arch, ..Default::default() };
361        let mut info_len = memoffset::offset_of!(ptrace_syscall_info, __bindgen_anon_1);
362
363        match &state.captured_thread_state {
364            Some(captured) => {
365                let registers = captured.thread_state.registers.clone();
366                info.instruction_pointer = registers.instruction_pointer_register();
367                info.stack_pointer = registers.stack_pointer_register();
368                #[cfg(target_arch = "aarch64")]
369                if captured.thread_state.is_arch32() {
370                    // If any additional arch32 archs are added, just use a cfg
371                    // macro here.
372                    info.arch = starnix_uapi::AUDIT_ARCH_ARM;
373                }
374                match target.load_stopped() {
375                    StopState::SyscallEnterStopped => {
376                        let syscall_decl = SyscallDecl::from_number(
377                            registers.syscall_register(),
378                            captured.thread_state.arch_width(),
379                        );
380                        let syscall = new_syscall_from_state(syscall_decl, &captured.thread_state);
381                        info.op = PTRACE_SYSCALL_INFO_ENTRY as u8;
382                        let entry = linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_1 {
383                            nr: syscall.decl.number,
384                            args: [
385                                syscall.arg0.raw(),
386                                syscall.arg1.raw(),
387                                syscall.arg2.raw(),
388                                syscall.arg3.raw(),
389                                syscall.arg4.raw(),
390                                syscall.arg5.raw(),
391                            ],
392                        };
393                        info_len += memoffset::offset_of!(
394                            linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_1,
395                            args
396                        ) + std::mem::size_of_val(&entry.args);
397                        info.__bindgen_anon_1.entry = entry;
398                    }
399                    StopState::SyscallExitStopped => {
400                        info.op = PTRACE_SYSCALL_INFO_EXIT as u8;
401                        let exit = linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_2 {
402                            rval: registers.return_register() as i64,
403                            is_error: state
404                                .ptrace
405                                .as_ref()
406                                .map_or(0, |ptrace| ptrace.last_syscall_was_error as u8),
407                            ..Default::default()
408                        };
409                        info_len += memoffset::offset_of!(
410                            linux_uapi::ptrace_syscall_info__bindgen_ty_1__bindgen_ty_2,
411                            is_error
412                        ) + std::mem::size_of_val(&exit.is_error);
413                        info.__bindgen_anon_1.exit = exit;
414                    }
415                    _ => {
416                        info.op = PTRACE_SYSCALL_INFO_NONE as u8;
417                    }
418                };
419            }
420            _ => (),
421        }
422        Ok((info_len as i32, info))
423    }
424
425    /// Gets the core state for this ptrace if the options set on this ptrace
426    /// match |trace_kind|.  Returns a pair: the trace option you *should* use
427    /// (sometimes this is different from the one that the caller thinks it
428    /// should use), and the core state.
429    pub fn get_core_state_for_clone(
430        &self,
431        clone_args: &clone_args,
432    ) -> (PtraceOptions, Option<PtraceCoreState>) {
433        // ptrace(2): If the tracee calls clone(2) with the CLONE_VFORK flag,
434        // PTRACE_EVENT_VFORK will be delivered instead if PTRACE_O_TRACEVFORK
435        // is set, otherwise if the tracee calls clone(2) with the exit signal
436        // set to SIGCHLD, PTRACE_EVENT_FORK will be delivered if
437        // PTRACE_O_TRACEFORK is set.
438        let trace_type = if clone_args.flags & (starnix_uapi::CLONE_UNTRACED as u64) != 0 {
439            PtraceOptions::empty()
440        } else {
441            if clone_args.flags & (starnix_uapi::CLONE_VFORK as u64) != 0 {
442                PtraceOptions::TRACEVFORK
443            } else if clone_args.exit_signal != (starnix_uapi::SIGCHLD as u64) {
444                PtraceOptions::TRACECLONE
445            } else {
446                PtraceOptions::TRACEFORK
447            }
448        };
449
450        if !self.has_option(trace_type)
451            && (clone_args.flags & (starnix_uapi::CLONE_PTRACE as u64) == 0)
452        {
453            return (PtraceOptions::empty(), None);
454        }
455
456        (trace_type, Some(self.get_core_state()))
457    }
458}
459
460/// A zombie that must delivered to a tracer process for a traced process.
461struct TracedZombie {
462    /// An artificial zombie that must be delivered to the tracer program.
463    artificial_zombie: ZombieProcess,
464
465    /// The real zombie notification to be sent after the artificial zombie has been delivered to
466    /// the tracer.
467    notification: Option<ZombieNotification>,
468}
469
470impl TracedZombie {
471    fn new(artificial_zombie: ZombieProcess) -> Self {
472        Self { artificial_zombie, notification: None }
473    }
474
475    fn new_with_notification(
476        artificial_zombie: ZombieProcess,
477        notification: ZombieNotification,
478    ) -> Self {
479        Self { artificial_zombie, notification: Some(notification) }
480    }
481
482    fn set_parent(
483        &mut self,
484        new_zombie: Option<OwnedRef<ZombieProcess>>,
485        new_parent: &ThreadGroup,
486    ) {
487        if let Some(new_zombie) = new_zombie {
488            self.notification = Some(ZombieNotification {
489                recipient: new_parent.weak_self.clone(),
490                zombie: new_zombie,
491            });
492        } else if let Some(ref mut notification) = self.notification {
493            notification.recipient = new_parent.weak_self.clone();
494        }
495    }
496
497    fn detach(self, pids: &mut PidTableGuard<'_>) -> Option<ZombieNotification> {
498        self.artificial_zombie.release(pids);
499        self.notification
500    }
501}
502
503/// A list of zombie processes that were traced by a given tracer, but which
504/// have not yet notified that tracer of their exit.  Once the tracer is
505/// notified, the original parent will be notified.
506#[derive(Default)]
507pub struct ZombiePtracees {
508    /// A list of zombies that have to be delivered to the ptracer.  The key is
509    /// the tid of the traced process.
510    zombies: BTreeMap<Pid, TracedZombie>,
511}
512
513impl Drop for ZombiePtracees {
514    fn drop(&mut self) {
515        assert_eq!(self.zombies.len(), 0);
516    }
517}
518
519impl ZombiePtracees {
520    pub fn new() -> Self {
521        Self::default()
522    }
523
524    /// Adds a zombie tracee to the list, but does not provide a parent task to
525    /// notify when the tracer is done.
526    pub fn add(&mut self, pids: &mut PidTableGuard<'_>, tid: Pid, zombie: ZombieProcess) {
527        if let std::collections::btree_map::Entry::Vacant(entry) = self.zombies.entry(tid) {
528            entry.insert(TracedZombie::new(zombie));
529        } else {
530            zombie.release(pids);
531        }
532    }
533
534    /// Detaches from the zombie tracee with the given TID.
535    ///
536    /// Returns the notification to deliver to the tracee's real parent.
537    pub fn detach(
538        &mut self,
539        pids: &mut PidTableGuard<'_>,
540        tid: &Pid,
541    ) -> Option<ZombieNotification> {
542        self.zombies.remove(tid).and_then(|traced_zombie| traced_zombie.detach(pids))
543    }
544
545    /// Detaches from every zombie tracee.
546    ///
547    /// Returns the notifications to deliver to the tracees' real parents.
548    pub fn detach_all(&mut self, pids: &mut PidTableGuard<'_>) -> Vec<ZombieNotification> {
549        let traced_zombies = std::mem::replace(&mut self.zombies, Default::default());
550        traced_zombies
551            .into_iter()
552            .filter_map(|(_, traced_zombie)| traced_zombie.detach(pids))
553            .collect()
554    }
555
556    pub fn is_empty(&self) -> bool {
557        self.zombies.is_empty()
558    }
559
560    /// Provide a parent task and a zombie to notify when the tracer has been
561    /// notified.
562    pub fn set_parent_of(
563        &mut self,
564        tracee: &Pid,
565        new_zombie: Option<OwnedRef<ZombieProcess>>,
566        new_parent: &ThreadGroup,
567    ) {
568        match self.zombies.entry(tracee.clone()) {
569            std::collections::btree_map::Entry::Vacant(entry) => {
570                if let Some(new_zombie) = new_zombie {
571                    entry.insert(TracedZombie::new_with_notification(
572                        new_zombie.as_artificial(),
573                        ZombieNotification::new(new_parent.weak_self.clone(), new_zombie),
574                    ));
575                }
576            }
577            std::collections::btree_map::Entry::Occupied(mut entry) => {
578                entry.get_mut().set_parent(new_zombie, new_parent);
579            }
580        }
581    }
582
583    /// When a parent dies without having been notified, replace it with a given
584    /// new parent.
585    pub fn reparent(old_parent: &ThreadGroup, new_parent: &ThreadGroup) {
586        let mut lockless_list = old_parent.read().deferred_zombie_ptracers.clone();
587
588        for deferred_zombie_ptracer in &lockless_list {
589            if let Ok(tg) = deferred_zombie_ptracer.tracer_pid.get_thread_group() {
590                tg.write().zombie_ptracees.set_parent_of(
591                    &deferred_zombie_ptracer.tracee_tid,
592                    None,
593                    new_parent,
594                );
595            }
596        }
597        let mut new_state = new_parent.write();
598        new_state.deferred_zombie_ptracers.append(&mut lockless_list);
599    }
600
601    /// Returns true iff there is a zombie waiting to be delivered to the tracers matching the
602    /// given selector.
603    pub fn has_zombie_matching(&self, selector: &ProcessSelector) -> bool {
604        self.zombies.values().any(|z| z.artificial_zombie.matches_selector(selector))
605    }
606
607    /// Returns true iff the given `tid` is a traced thread that needs to deliver a zombie to the
608    /// tracer.
609    pub fn has_tracee(&self, tid: &Pid) -> bool {
610        self.zombies.contains_key(tid)
611    }
612
613    /// Returns a zombie matching the given selector and options, and
614    /// (optionally) a thread group to notify after the caller has consumed that
615    /// zombie.
616    pub fn get_waitable_entry(
617        &mut self,
618        selector: &ProcessSelector,
619        options: &WaitingOptions,
620    ) -> Option<(ZombieProcess, Option<(Weak<ThreadGroup>, OwnedRef<ZombieProcess>)>)> {
621        // Look for the last zombie in the map that matches process
622        // selector and waiting options.
623        let Some((t, found_zombie)) = self
624            .zombies
625            .iter()
626            .map(|(t, z)| (t.clone(), &z.artificial_zombie))
627            .rfind(|(_, zombie)| zombie.matches_selector_and_waiting_option(selector, options))
628        else {
629            return None;
630        };
631
632        let result;
633        if !options.keep_waitable_state {
634            // Maybe notify child waiters.
635            result = self.zombies.remove(&t).map(|traced_zombie| {
636                (
637                    traced_zombie.artificial_zombie,
638                    traced_zombie.notification.map(|n| (n.recipient, n.zombie)),
639                )
640            });
641        } else {
642            result = Some((found_zombie.as_artificial(), None));
643        }
644
645        result
646    }
647}
648
649// PR_SET_PTRACER_ANY is defined as ((unsigned long) -1) in Linux UAPI.
650pub const PR_SET_PTRACER_ANY: u64 = u64::MAX;
651pub const PR_SET_PTRACER_ANY_ARCH32: u64 = u32::MAX as u64;
652
653/// Indicates processes specifically allowed to trace a given process if using
654/// SCOPE_RESTRICTED.  Used by prctl(PR_SET_PTRACER).
655#[derive(Clone, Debug, Default, PartialEq, Eq)]
656pub enum PtraceAllowedPtracers {
657    #[default]
658    None,
659    Some(Pid),
660    Any,
661}
662
663#[derive(Copy, Clone)]
664pub enum PtraceTracer<'a> {
665    /// Tracer thread group is exiting, which detaches all its tracees.
666    Exiting(&'a ThreadGroup),
667    /// Tracer invoked a ptrace syscall.
668    Syscall(&'a Arc<Task>),
669}
670
671impl<'a> PtraceTracer<'a> {
672    fn thread_group(self) -> &'a ThreadGroup {
673        match self {
674            PtraceTracer::Exiting(tg) => tg,
675            PtraceTracer::Syscall(task) => &task.thread_group,
676        }
677    }
678
679    fn matches_task(self, task: &Arc<Task>) -> bool {
680        match self {
681            PtraceTracer::Syscall(expected_task) => task == expected_task,
682            PtraceTracer::Exiting(expected_tg) => &*task.thread_group == expected_tg,
683        }
684    }
685}
686
687/// Continues the target thread, optionally detaching from it.
688///
689/// # Arguments
690/// * `tracer` - The context of the tracer performing the operation.
691/// * `tracee` - The target thread to continue.
692/// * `data` - Is treated as it is in PTRACE_CONT.
693/// * `detach` - If true, the tracer will detach from the tracee.
694fn ptrace_cont(
695    tracer: PtraceTracer<'_>,
696    tracee: &Task,
697    data: &UserAddress,
698    detach: bool,
699) -> Result<(), Errno> {
700    let data = data.ptr() as u64;
701    let new_state;
702    let mut siginfo = if data != 0 {
703        let signal = Signal::try_from(UncheckedSignal::new(data))?;
704        Some(SignalInfo::kernel(signal))
705    } else {
706        None
707    };
708
709    let mut state = tracee.write();
710
711    // Verify under lock that we are still the tracer.
712    // This check is performed under the tracee task lock to prevent races where the tracee
713    // is concurrently detached or re-attached to another tracer in the window between our
714    // initial check (e.g. in ptrace_dispatch or wait_on_pid) and when we actually execute
715    // the continuation. This ensures we don't accidentally control a task we no longer trace,
716    // or interfere with another tracer's control.
717    {
718        let ptrace = state.ptrace.as_ref().ok_or_else(|| errno!(ESRCH))?;
719        let tracer_tg = ptrace.core_state.thread_group.upgrade().ok_or_else(|| errno!(ESRCH))?;
720        let tracer_task = ptrace.core_state.task.upgrade();
721        let is_match = match tracer_task {
722            Some(tracer_task) if !tracer_task.is_exitted() => tracer.matches_task(&tracer_task),
723            _ => tracer.thread_group() == &*tracer_tg,
724        };
725        if !is_match {
726            return error!(ESRCH);
727        }
728    }
729    let is_listen = state.is_ptrace_listening();
730
731    if tracee.load_stopped().is_waking_or_awake() && !is_listen {
732        if detach && matches!(tracer, PtraceTracer::Exiting(_)) {
733            // If the tracer thread group is exiting, we must force-detach even if the
734            // tracee is currently running (waking/awake), otherwise the tracee would
735            // be left permanently attached to a dead tracer.
736            state.set_ptrace(None)?;
737            return Ok(());
738        }
739        // Manual PTRACE_DETACH via syscall (tracer is CurrentTask) is not allowed
740        // on running tracees and returns EIO.
741        return error!(EIO);
742    }
743
744    if !state.can_accept_ptrace_commands() && !detach {
745        return error!(ESRCH);
746    }
747
748    let signal_mask = state.signal_mask();
749    if let Some(ptrace) = &mut state.ptrace {
750        if data != 0 {
751            if let Some(last_signal) = &mut ptrace.last_signal {
752                // If the tracer resumes the tracee with the same signal number (or one already
753                // updated via `PTRACE_SETSIGINFO`), preserve `last_signal`'s `siginfo_t` details
754                // and `force` state; otherwise replace `last_signal` with the newly injected
755                // signal so details and `force` from the stopping signal do not leak.
756                if let Some(si) = &siginfo {
757                    if last_signal.signal == si.signal {
758                        siginfo = Some(last_signal.clone());
759                    } else {
760                        *last_signal = si.clone();
761                    }
762                }
763            }
764            new_state = if siginfo
765                .as_ref()
766                .is_some_and(|si| !signal_mask.has_signal(si.signal) || si.force)
767            {
768                PtraceStatus::Continuing
769            } else {
770                PtraceStatus::Default
771            };
772        } else {
773            new_state = PtraceStatus::Default;
774            ptrace.last_signal = None;
775            ptrace.event_data = None;
776        }
777        ptrace.stop_status = new_state;
778
779        if is_listen {
780            state.notify_ptracees();
781        }
782    }
783
784    if let Some(siginfo) = siginfo {
785        // This will wake up the task for us, and also release state
786        send_signal_first(&tracee, state, siginfo);
787    } else {
788        state.set_stopped(StopState::Waking, None, None, None);
789        drop(state);
790        tracee.thread_group().set_stopped(StopState::Waking, None, false);
791    }
792    if detach {
793        tracee.write().set_ptrace(None)?;
794    }
795    Ok(())
796}
797
798fn ptrace_interrupt(tracee: &Task) -> Result<(), Errno> {
799    let mut state = tracee.write();
800    if let Some(ptrace) = &mut state.ptrace {
801        if !ptrace.is_seized() {
802            return error!(EIO);
803        }
804        let status = ptrace.stop_status.clone();
805        ptrace.stop_status = PtraceStatus::Default;
806        let event_data = Some(PtraceEventData::new_from_event(PtraceEvent::Stop, 0));
807        if status == PtraceStatus::Listening {
808            let signal = ptrace.last_signal.clone();
809            // "If the tracee was already stopped by a signal and PTRACE_LISTEN
810            // was sent to it, the tracee stops with PTRACE_EVENT_STOP and
811            // WSTOPSIG(status) returns the stop signal"
812            state.set_stopped(StopState::PtraceEventStopped, signal, None, event_data);
813        } else {
814            state.set_stopped(
815                StopState::PtraceEventStopping,
816                Some(SignalInfo::kernel(SIGTRAP)),
817                None,
818                event_data,
819            );
820            drop(state);
821            tracee.interrupt();
822        }
823    }
824    Ok(())
825}
826
827fn ptrace_listen(tracee: &Task) -> Result<(), Errno> {
828    let mut state = tracee.write();
829    if let Some(ptrace) = &mut state.ptrace {
830        if !ptrace.is_seized()
831            || (ptrace.last_signal_waitable
832                && ptrace
833                    .event_data
834                    .as_ref()
835                    .is_some_and(|event_data| event_data.event != PtraceEvent::Stop))
836        {
837            return error!(EIO);
838        }
839        ptrace.stop_status = PtraceStatus::Listening;
840    }
841    Ok(())
842}
843
844pub fn ptrace_detach(
845    pids: &mut PidTableGuard<'_>,
846    tracer: PtraceTracer<'_>,
847    tracee: &Task,
848    data: &UserAddress,
849) -> Result<(), Errno> {
850    let thread_group = tracer.thread_group();
851    {
852        let mut ptracees = thread_group.ptracees.lock();
853        ptrace_cont(tracer, tracee, &data, true)?;
854        ptracees.remove(&tracee.persistent_info);
855    }
856    let zombie_notification = thread_group.write().zombie_ptracees.detach(pids, &tracee.tid);
857    if let Some(zombie_notification) = zombie_notification {
858        zombie_notification.deliver(pids);
859    }
860    Ok(())
861}
862
863/// For all ptrace requests that require an attached tracee
864pub fn ptrace_dispatch(
865    current_task: &mut CurrentTask,
866    request: u32,
867    pid: &Pid,
868    addr: UserAddress,
869    data: UserAddress,
870) -> Result<SyscallResult, Errno> {
871    let tracee = pid.get_task()?;
872
873    if let Some(ptrace) = &tracee.read().ptrace {
874        let is_tracer = ptrace
875            .core_state
876            .thread_group
877            .upgrade()
878            .is_some_and(|tg| Arc::ptr_eq(&tg, current_task.thread_group()));
879        if !is_tracer {
880            return error!(ESRCH);
881        }
882    }
883
884    // These requests may be run without the thread in a stop state, or
885    // check the stop state themselves.
886    match request {
887        PTRACE_KILL => {
888            let siginfo = SignalInfo::with_detail(
889                SIGKILL,
890                (SIGTRAP.number() | PTRACE_KILL << 8) as i32,
891                SignalDetail::None,
892            );
893            send_standard_signal(&tracee, siginfo);
894            return Ok(starnix_syscalls::SUCCESS);
895        }
896        PTRACE_INTERRUPT => {
897            ptrace_interrupt(tracee.as_ref())?;
898            return Ok(starnix_syscalls::SUCCESS);
899        }
900        PTRACE_LISTEN => {
901            ptrace_listen(&tracee)?;
902            return Ok(starnix_syscalls::SUCCESS);
903        }
904        PTRACE_CONT => {
905            ptrace_cont(PtraceTracer::Syscall(&current_task.task), tracee.as_ref(), &data, false)?;
906            return Ok(starnix_syscalls::SUCCESS);
907        }
908        PTRACE_SYSCALL => {
909            tracee.trace_syscalls.store(true, std::sync::atomic::Ordering::Relaxed);
910            ptrace_cont(PtraceTracer::Syscall(&current_task.task), tracee.as_ref(), &data, false)?;
911            return Ok(starnix_syscalls::SUCCESS);
912        }
913        PTRACE_DETACH => {
914            let mut pids = current_task.kernel().pids.lock();
915            ptrace_detach(
916                &mut pids,
917                PtraceTracer::Syscall(&current_task.task),
918                tracee.as_ref(),
919                &data,
920            )?;
921            return Ok(starnix_syscalls::SUCCESS);
922        }
923        _ => {}
924    }
925
926    // The remaining requests (to be added) require the thread to be stopped.
927    let mut state = tracee.write();
928    if !state.can_accept_ptrace_commands() {
929        return error!(ESRCH);
930    }
931
932    match request {
933        PTRACE_PEEKDATA | PTRACE_PEEKTEXT => {
934            let Some(captured) = &mut state.captured_thread_state else {
935                return error!(ESRCH);
936            };
937
938            // NB: The behavior of the syscall is different from the behavior in ptrace(2),
939            // which is provided by libc.
940            let src = LongPtr::new(captured.as_ref(), addr);
941            let val = tracee.read_multi_arch_object(src)?;
942
943            let dst = LongPtr::new(&src, data);
944            current_task.write_multi_arch_object(dst, val)?;
945            Ok(starnix_syscalls::SUCCESS)
946        }
947        PTRACE_POKEDATA | PTRACE_POKETEXT => {
948            let Some(captured) = &mut state.captured_thread_state else {
949                return error!(ESRCH);
950            };
951
952            let bytes = if captured.is_arch32() {
953                u32::try_from(data.ptr()).map_err(|_| errno!(EINVAL))?.to_ne_bytes().to_vec()
954            } else {
955                data.ptr().to_ne_bytes().to_vec()
956            };
957
958            tracee.mm()?.force_write_memory(addr, &bytes)?;
959
960            Ok(starnix_syscalls::SUCCESS)
961        }
962        PTRACE_PEEKUSR => {
963            let Some(captured) = &mut state.captured_thread_state else {
964                return error!(ESRCH);
965            };
966
967            let dst = LongPtr::new(captured.as_ref(), data);
968            let val = ptrace_peekuser(&mut captured.thread_state, addr.ptr() as usize)?;
969            current_task.write_multi_arch_object(dst, val as u64)?;
970            return Ok(starnix_syscalls::SUCCESS);
971        }
972        PTRACE_POKEUSR => {
973            ptrace_pokeuser(&mut *state, data.ptr() as usize, addr.ptr() as usize)?;
974            return Ok(starnix_syscalls::SUCCESS);
975        }
976        PTRACE_GETREGSET => {
977            if let Some(ref mut captured) = state.captured_thread_state {
978                let uiv = IOVecPtr::new(current_task, data);
979                let mut iv = current_task.read_multi_arch_object(uiv)?;
980                let base = iv.iov_base.addr;
981                let mut len = iv.iov_len as usize;
982                ptrace_getregset(
983                    current_task,
984                    &captured.thread_state,
985                    ElfNoteType::try_from(addr.ptr() as usize)?,
986                    base,
987                    &mut len,
988                )?;
989                iv.iov_len = len as u64;
990                current_task.write_multi_arch_object(uiv, iv)?;
991                return Ok(starnix_syscalls::SUCCESS);
992            }
993            error!(ESRCH)
994        }
995        PTRACE_SETREGSET => {
996            if let Some(ref mut captured) = state.captured_thread_state {
997                captured.dirty = true;
998                let uiv = IOVecPtr::new(current_task, data);
999                let iv = current_task.read_multi_arch_object(uiv)?;
1000                let base = iv.iov_base.addr;
1001                let len = iv.iov_len as usize;
1002                ptrace_setregset(
1003                    current_task,
1004                    &mut captured.thread_state,
1005                    ElfNoteType::try_from(addr.ptr() as usize)?,
1006                    base,
1007                    len,
1008                )?;
1009                return Ok(starnix_syscalls::SUCCESS);
1010            }
1011            error!(ESRCH)
1012        }
1013        #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
1014        PTRACE_GETREGS => {
1015            if let Some(captured) = &mut state.captured_thread_state {
1016                let mut len = usize::MAX;
1017                ptrace_getregset(
1018                    current_task,
1019                    &captured.thread_state,
1020                    ElfNoteType::PrStatus,
1021                    data.ptr() as u64,
1022                    &mut len,
1023                )?;
1024                return Ok(starnix_syscalls::SUCCESS);
1025            }
1026            error!(ESRCH)
1027        }
1028        PTRACE_SETSIGMASK => {
1029            // addr is the size of the buffer pointed to
1030            // by data, but has to be sizeof(sigset_t).
1031            if addr.ptr() != std::mem::size_of::<SigSet>() {
1032                return error!(EINVAL);
1033            }
1034            // sigset comes from *data.
1035            let src: UserRef<SigSet> = UserRef::from(data);
1036            let val = current_task.read_object(src)?;
1037            state.set_signal_mask(val);
1038
1039            Ok(starnix_syscalls::SUCCESS)
1040        }
1041        PTRACE_GETSIGMASK => {
1042            // addr is the size of the buffer pointed to
1043            // by data, but has to be sizeof(sigset_t).
1044            if addr.ptr() != std::mem::size_of::<SigSet>() {
1045                return error!(EINVAL);
1046            }
1047            // sigset goes in *data.
1048            let dst: UserRef<SigSet> = UserRef::from(data);
1049            let val = state.signal_mask();
1050            current_task.write_object(dst, &val)?;
1051            Ok(starnix_syscalls::SUCCESS)
1052        }
1053        PTRACE_GETSIGINFO => {
1054            if let Some(ptrace) = &state.ptrace {
1055                if let Some(signal) = ptrace.last_signal.as_ref() {
1056                    let dst = MultiArchUserRef::<uapi::siginfo_t, uapi::arch32::siginfo_t>::new(
1057                        current_task,
1058                        data,
1059                    );
1060                    signal.write(current_task, dst)?;
1061                } else {
1062                    return error!(EINVAL);
1063                }
1064            }
1065            Ok(starnix_syscalls::SUCCESS)
1066        }
1067        PTRACE_SETSIGINFO => {
1068            let mut siginfo: SignalInfo =
1069                UncheckedSignalInfo::read_from_siginfo(current_task, data)?.try_into()?;
1070            if let Some(ptrace) = &mut state.ptrace {
1071                if let Some(last_signal) = &ptrace.last_signal {
1072                    if last_signal.signal == siginfo.signal {
1073                        siginfo.force = last_signal.force;
1074                    }
1075                }
1076                ptrace.last_signal = Some(siginfo);
1077            }
1078            Ok(starnix_syscalls::SUCCESS)
1079        }
1080        PTRACE_GET_SYSCALL_INFO => {
1081            if let Some(ptrace) = &state.ptrace {
1082                let (size, info) = ptrace.get_target_syscall(&tracee, &state)?;
1083                let dst: UserRef<ptrace_syscall_info> = UserRef::from(data);
1084                let len = std::cmp::min(std::mem::size_of::<ptrace_syscall_info>(), addr.ptr());
1085                // SAFETY: ptrace_syscall_info does not implement FromBytes/IntoBytes,
1086                // so this has to happen manually.
1087                let src = unsafe {
1088                    std::slice::from_raw_parts(
1089                        &info as *const ptrace_syscall_info as *const u8,
1090                        len as usize,
1091                    )
1092                };
1093                current_task.write_memory(dst.addr(), src)?;
1094                Ok(size.into())
1095            } else {
1096                error!(ESRCH)
1097            }
1098        }
1099        PTRACE_SETOPTIONS => {
1100            let mask = data.ptr() as u32;
1101            // This is what we currently support.
1102            if mask != 0
1103                && (mask
1104                    & !(PTRACE_O_TRACESYSGOOD
1105                        | PTRACE_O_TRACECLONE
1106                        | PTRACE_O_TRACEFORK
1107                        | PTRACE_O_TRACEVFORK
1108                        | PTRACE_O_TRACEVFORKDONE
1109                        | PTRACE_O_TRACEEXEC
1110                        | PTRACE_O_TRACEEXIT
1111                        | PTRACE_O_EXITKILL)
1112                    != 0)
1113            {
1114                track_stub!(TODO("https://fxbug.dev/322874463"), "ptrace(PTRACE_SETOPTIONS)", mask);
1115                return error!(ENOSYS);
1116            }
1117            if let Some(ptrace) = &mut state.ptrace {
1118                ptrace.set_options_from_bits(mask)?;
1119            }
1120            Ok(starnix_syscalls::SUCCESS)
1121        }
1122        PTRACE_GETEVENTMSG => {
1123            if let Some(ptrace) = &state.ptrace {
1124                if let Some(event_data) = &ptrace.event_data {
1125                    let dst = LongPtr::new(current_task, data);
1126                    current_task.write_multi_arch_object(dst, event_data.msg)?;
1127                    return Ok(starnix_syscalls::SUCCESS);
1128                }
1129            }
1130            error!(EIO)
1131        }
1132        _ => {
1133            track_stub!(TODO("https://fxbug.dev/322874463"), "ptrace", request);
1134            error!(ENOSYS)
1135        }
1136    }
1137}
1138
1139/// Makes the given thread group trace the given task.
1140fn do_attach(
1141    thread_group: &ThreadGroup,
1142    tracer_task: Weak<Task>,
1143    task: &Arc<Task>,
1144    attach_type: PtraceAttachType,
1145    options: PtraceOptions,
1146) -> Result<(), Errno> {
1147    let mut ptracees = thread_group.ptracees.lock();
1148
1149    if !thread_group.read().is_running() {
1150        return error!(ESRCH);
1151    }
1152
1153    let process_state = &mut task.thread_group().write();
1154    let mut state = task.write();
1155    state.set_ptrace(Some(PtraceState::new(
1156        tracer_task,
1157        thread_group.weak_self.clone(),
1158        attach_type,
1159        options,
1160    )))?;
1161
1162    ptracees.insert(task.persistent_info.clone());
1163
1164    // If the tracee is already stopped, make sure that the tracer can
1165    // identify that right away.
1166    if process_state.is_waitable()
1167        && process_state.base.load_stopped() == StopState::GroupStopped
1168        && task.load_stopped() == StopState::GroupStopped
1169    {
1170        if let Some(ptrace) = &mut state.ptrace {
1171            ptrace.last_signal_waitable = true;
1172        }
1173    }
1174
1175    Ok(())
1176}
1177
1178/// Uses the given core ptrace state (including tracer, attach type, etc) to
1179/// attach to another task, given by `tracee_task`.  Also sends a signal to stop
1180/// tracee_task.  Typical for when inheriting ptrace state from another task.
1181pub fn ptrace_attach_from_state(
1182    tracee_task: &Arc<Task>,
1183    ptrace_state: PtraceCoreState,
1184) -> Result<(), Errno> {
1185    {
1186        let tracer_tg = ptrace_state.thread_group.upgrade().ok_or_else(|| errno!(ESRCH))?;
1187        do_attach(
1188            &tracer_tg,
1189            ptrace_state.task.clone(),
1190            tracee_task,
1191            ptrace_state.attach_type,
1192            ptrace_state.options,
1193        )?;
1194    }
1195    let mut state = tracee_task.write();
1196    if let Some(ptrace) = &mut state.ptrace {
1197        ptrace.core_state.tracer_waiters = Arc::clone(&ptrace_state.tracer_waiters);
1198    }
1199
1200    // The newly started tracee starts with a signal that depends on the attach type.
1201    let signal = if ptrace_state.attach_type == PtraceAttachType::Seize {
1202        if let Some(ptrace) = &mut state.ptrace {
1203            ptrace.set_last_event(Some(PtraceEventData::new_from_event(PtraceEvent::Stop, 0)));
1204        }
1205        // Ptrace-emitted SIGTRAP signal cannot be blocked.
1206        SignalInfo::forced(SIGTRAP)
1207    } else {
1208        // Note, SIGSTOP can never be blocked, but we use `forced` anyway to be consistent.
1209        SignalInfo::forced(SIGSTOP)
1210    };
1211    send_signal_first(tracee_task, state, signal);
1212
1213    // If the tracer is already sleeping in waitpid, it is waiting on the shared `tracer_waiters`
1214    // queue. We must wake it up here so it can register on the new tracee's queue (and update its
1215    // wait registration loop) rather than missing the initial stopped status notification.
1216    ptrace_state.tracer_waiters.notify_all();
1217
1218    Ok(())
1219}
1220
1221pub fn ptrace_traceme(current_task: &mut CurrentTask) -> Result<SyscallResult, Errno> {
1222    let parent = current_task.thread_group().read().parent.clone();
1223    if let Some(parent) = parent {
1224        let parent = parent.upgrade();
1225        // TODO: Move this check into `do_attach()` so that there is a single `ptrace_access_check(tracer, tracee)`?
1226        let parent_task = parent.leader.get_task().map_err(|_| errno!(EINVAL))?;
1227        security::ptrace_traceme(current_task, &parent_task)?;
1228        let parent_task = Arc::downgrade(&parent_task);
1229
1230        do_attach(
1231            &parent,
1232            parent_task,
1233            &current_task.task,
1234            PtraceAttachType::Attach,
1235            PtraceOptions::empty(),
1236        )?;
1237        Ok(starnix_syscalls::SUCCESS)
1238    } else {
1239        error!(EPERM)
1240    }
1241}
1242
1243pub fn ptrace_attach(
1244    current_task: &mut CurrentTask,
1245    pid: &Pid,
1246    attach_type: PtraceAttachType,
1247    data: UserAddress,
1248) -> Result<SyscallResult, Errno> {
1249    let tracee = pid.get_task()?;
1250
1251    if tracee.thread_group == current_task.thread_group {
1252        return error!(EPERM);
1253    }
1254
1255    current_task.check_ptrace_access_mode(PTRACE_MODE_ATTACH_REALCREDS, &tracee)?;
1256    let tracer_task = Arc::downgrade(&current_task.task);
1257    do_attach(
1258        current_task.thread_group(),
1259        tracer_task,
1260        &tracee,
1261        attach_type,
1262        PtraceOptions::empty(),
1263    )?;
1264    if attach_type == PtraceAttachType::Attach {
1265        send_standard_signal(&tracee, SignalInfo::kernel(SIGSTOP));
1266    } else if attach_type == PtraceAttachType::Seize {
1267        // When seizing, |data| should be used as the options bitmask.
1268        let mut state = tracee.write();
1269        if let Some(ptrace) = &mut state.ptrace {
1270            ptrace.set_options_from_bits(data.ptr() as u32)?;
1271        }
1272    }
1273    Ok(starnix_syscalls::SUCCESS)
1274}
1275
1276/// Implementation of ptrace(PTRACE_PEEKUSER).  The user struct holds the
1277/// registers and other information about the process.  See ptrace(2) and
1278/// sys/user.h for full details.
1279pub fn ptrace_peekuser(
1280    thread_state: &mut ThreadState<HeapRegs>,
1281    offset: usize,
1282) -> Result<usize, Errno> {
1283    #[cfg(any(target_arch = "x86_64"))]
1284    if offset >= std::mem::size_of::<user>() {
1285        return error!(EIO);
1286    }
1287    if offset < UserRegsStructPtr::size_of_object_for(thread_state) {
1288        let result = thread_state.get_user_register(offset)?;
1289        return Ok(result);
1290    }
1291    error!(EIO)
1292}
1293
1294pub fn ptrace_pokeuser(
1295    state: &mut TaskMutableState,
1296    value: usize,
1297    offset: usize,
1298) -> Result<(), Errno> {
1299    if let Some(ref mut thread_state) = state.captured_thread_state {
1300        thread_state.dirty = true;
1301
1302        #[cfg(any(target_arch = "x86_64"))]
1303        if offset >= std::mem::size_of::<user>() {
1304            return error!(EIO);
1305        }
1306        if offset < UserRegsStructPtr::size_of_object_for(thread_state.as_ref()) {
1307            return thread_state.thread_state.set_user_register(offset, value);
1308        }
1309    }
1310    error!(EIO)
1311}
1312
1313pub fn ptrace_getregset(
1314    current_task: &CurrentTask,
1315    thread_state: &ThreadState<HeapRegs>,
1316    regset_type: ElfNoteType,
1317    base: u64,
1318    len: &mut usize,
1319) -> Result<(), Errno> {
1320    match regset_type {
1321        ElfNoteType::PrStatus => {
1322            let user_regs_struct_len = UserRegsStructPtr::size_of_object_for(thread_state);
1323            *len = std::cmp::min(*len, user_regs_struct_len);
1324
1325            if thread_state.is_arch32() {
1326                let regs = thread_state.registers.to_user_regs_struct_arch32();
1327                current_task.write_memory(UserAddress::from(base), &regs.as_bytes()[..*len])?;
1328            } else {
1329                let regs = thread_state.registers.to_user_regs_struct();
1330                current_task.write_memory(UserAddress::from(base), &regs.as_bytes()[..*len])?;
1331            }
1332            Ok(())
1333        }
1334        _ => {
1335            error!(EINVAL)
1336        }
1337    }
1338}
1339
1340pub fn ptrace_setregset(
1341    current_task: &CurrentTask,
1342    thread_state: &mut ThreadState<HeapRegs>,
1343    regset_type: ElfNoteType,
1344    base: u64,
1345    len: usize,
1346) -> Result<(), Errno> {
1347    match regset_type {
1348        ElfNoteType::PrStatus => {
1349            let user_regs_struct_len = UserRegsStructPtr::size_of_object_for(thread_state);
1350            if len < user_regs_struct_len {
1351                return error!(EINVAL);
1352            }
1353
1354            if thread_state.is_arch32() {
1355                let mut regs = starnix_uapi::arch32::user_regs_struct::default();
1356                current_task.read_memory_to_slice(UserAddress::from(base), regs.as_mut_bytes())?;
1357                thread_state.registers.from_user_regs_struct_arch32(&regs);
1358            } else {
1359                let mut regs = starnix_uapi::user_regs_struct::default();
1360                current_task.read_memory_to_slice(UserAddress::from(base), regs.as_mut_bytes())?;
1361                thread_state.registers.from_user_regs_struct(&regs);
1362            }
1363            Ok(())
1364        }
1365        _ => error!(EINVAL),
1366    }
1367}
1368
1369#[inline(never)]
1370pub fn ptrace_syscall_enter(current_task: &mut CurrentTask) {
1371    let block = {
1372        let mut state = current_task.write();
1373        if state.ptrace.is_some() {
1374            current_task.trace_syscalls.store(false, Ordering::Relaxed);
1375            let mut sig = SignalInfo::with_detail(
1376                SIGTRAP,
1377                (linux_uapi::SIGTRAP | 0x80) as i32,
1378                SignalDetail::None,
1379            );
1380            if state
1381                .ptrace
1382                .as_ref()
1383                .is_some_and(|ptrace| ptrace.has_option(PtraceOptions::TRACESYSGOOD))
1384            {
1385                sig.signal.set_ptrace_syscall_bit();
1386            }
1387            state.set_stopped(StopState::SyscallEnterStopping, Some(sig), None, None);
1388            true
1389        } else {
1390            false
1391        }
1392    };
1393    if block {
1394        current_task.block_if_stopped();
1395    }
1396}
1397
1398#[inline(never)]
1399pub fn ptrace_syscall_exit(current_task: &mut CurrentTask, is_error: bool) {
1400    let block = {
1401        let mut state = current_task.write();
1402        current_task.trace_syscalls.store(false, Ordering::Relaxed);
1403        if state.ptrace.is_some() {
1404            let mut sig = SignalInfo::with_detail(
1405                SIGTRAP,
1406                (linux_uapi::SIGTRAP | 0x80) as i32,
1407                SignalDetail::None,
1408            );
1409            if state
1410                .ptrace
1411                .as_ref()
1412                .is_some_and(|ptrace| ptrace.has_option(PtraceOptions::TRACESYSGOOD))
1413            {
1414                sig.signal.set_ptrace_syscall_bit();
1415            }
1416
1417            state.set_stopped(StopState::SyscallExitStopping, Some(sig), None, None);
1418            if let Some(ptrace) = &mut state.ptrace {
1419                ptrace.last_syscall_was_error = is_error;
1420            }
1421            true
1422        } else {
1423            false
1424        }
1425    };
1426    if block {
1427        current_task.block_if_stopped();
1428    }
1429}
1430
1431#[cfg(test)]
1432mod tests {
1433    use super::*;
1434    use crate::task::syscalls::sys_prctl;
1435    use crate::testing::{create_task, spawn_kernel_and_run};
1436    use starnix_uapi::PR_SET_PTRACER;
1437    use starnix_uapi::auth::CAP_SYS_PTRACE;
1438
1439    #[::fuchsia::test]
1440    async fn test_set_ptracer() {
1441        spawn_kernel_and_run(async |current_task| {
1442            let kernel = current_task.kernel().clone();
1443            let mut tracee = create_task(&kernel, "tracee");
1444            let mut tracer = create_task(&kernel, "tracer");
1445
1446            let mut creds = tracer.real_creds().clone();
1447            creds.cap_effective &= !CAP_SYS_PTRACE;
1448            tracer.set_creds(creds);
1449
1450            kernel.ptrace_scope.store(security::yama::SCOPE_RESTRICTED, Ordering::Relaxed);
1451            assert_eq!(sys_prctl(&mut tracee, PR_SET_PTRACER, 0xFFF, 0, 0, 0), error!(EINVAL));
1452
1453            assert_eq!(
1454                ptrace_attach(
1455                    &mut tracer,
1456                    &tracee.as_ref().task.tid,
1457                    PtraceAttachType::Attach,
1458                    UserAddress::NULL,
1459                ),
1460                error!(EPERM)
1461            );
1462
1463            assert!(
1464                sys_prctl(
1465                    &mut tracee,
1466                    PR_SET_PTRACER,
1467                    tracer.thread_group().leader.id as u64,
1468                    0,
1469                    0,
1470                    0
1471                )
1472                .is_ok()
1473            );
1474
1475            let mut not_tracer = create_task(&kernel, "not-tracer");
1476            not_tracer.set_creds(tracer.real_creds().clone());
1477            assert_eq!(
1478                ptrace_attach(
1479                    &mut not_tracer,
1480                    &tracee.as_ref().task.tid,
1481                    PtraceAttachType::Attach,
1482                    UserAddress::NULL,
1483                ),
1484                error!(EPERM)
1485            );
1486
1487            assert!(
1488                ptrace_attach(
1489                    &mut tracer,
1490                    &tracee.as_ref().task.tid,
1491                    PtraceAttachType::Attach,
1492                    UserAddress::NULL,
1493                )
1494                .is_ok()
1495            );
1496        })
1497        .await;
1498    }
1499
1500    #[::fuchsia::test]
1501    async fn test_set_ptracer_any() {
1502        spawn_kernel_and_run(async |current_task| {
1503            let kernel = current_task.kernel().clone();
1504            let mut tracee = create_task(&kernel, "tracee");
1505            let mut tracer = create_task(&kernel, "tracer");
1506
1507            let mut creds = tracer.real_creds().clone();
1508            creds.cap_effective &= !CAP_SYS_PTRACE;
1509            tracer.set_creds(creds);
1510
1511            kernel.ptrace_scope.store(security::yama::SCOPE_RESTRICTED, Ordering::Relaxed);
1512            assert_eq!(sys_prctl(&mut tracee, PR_SET_PTRACER, 0xFFF, 0, 0, 0), error!(EINVAL));
1513
1514            assert_eq!(
1515                ptrace_attach(
1516                    &mut tracer,
1517                    &tracee.as_ref().task.tid,
1518                    PtraceAttachType::Attach,
1519                    UserAddress::NULL,
1520                ),
1521                error!(EPERM)
1522            );
1523
1524            assert!(sys_prctl(&mut tracee, PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0).is_ok());
1525
1526            assert!(
1527                ptrace_attach(
1528                    &mut tracer,
1529                    &tracee.as_ref().task.tid,
1530                    PtraceAttachType::Attach,
1531                    UserAddress::NULL,
1532                )
1533                .is_ok()
1534            );
1535        })
1536        .await;
1537    }
1538
1539    #[::fuchsia::test]
1540    async fn test_unspawned_task_remove_does_not_register_zombie() {
1541        spawn_kernel_and_run(async |current_task| {
1542            let kernel = current_task.kernel().clone();
1543            let tracee = create_task(&kernel, "tracee");
1544            let mut tracer = create_task(&kernel, "tracer");
1545
1546            assert!(
1547                ptrace_attach(
1548                    &mut tracer,
1549                    &tracee.as_ref().task.tid,
1550                    PtraceAttachType::Attach,
1551                    UserAddress::NULL,
1552                )
1553                .is_ok()
1554            );
1555
1556            // create_task() returns an unspawned task. Dropping the tracee causes it to exit, which
1557            // triggers zombie tracee registration. The tracee must not register with the tracer
1558            // because it never spawned.
1559            assert!(!tracee.is_spawned());
1560            drop(tracee);
1561            assert!(tracer.thread_group().write().zombie_ptracees.is_empty());
1562        })
1563        .await;
1564    }
1565}