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