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