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