Skip to main content

starnix_core/ptrace/
hooks.rs

1// Copyright 2026 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 std::sync::Arc;
6use std::sync::atomic::Ordering;
7
8use starnix_logging::CATEGORY_STARNIX;
9use starnix_uapi::signals::SIGTRAP;
10use starnix_uapi::{CLONE_PTRACE, CLONE_UNTRACED, CLONE_VFORK, SIGCHLD, clone_args};
11
12use crate::ptrace::{PtraceEventData, PtraceOptions, StopState, ptrace_attach_from_state};
13use crate::signals::{SignalDetail, SignalInfo, send_signal_first};
14use crate::task::{CurrentTask, Task};
15
16/// Provides access to ptrace hooks for a [`CurrentTask`].
17#[derive(Debug)]
18pub struct CurrentTaskPtrace<'a> {
19    current_task: &'a mut CurrentTask,
20}
21
22impl CurrentTask {
23    /// Returns a [`CurrentTaskPtrace`] wrapper to invoke ptrace hooks.
24    pub fn ptrace(&mut self) -> CurrentTaskPtrace<'_> {
25        CurrentTaskPtrace { current_task: self }
26    }
27}
28
29impl<'a> CurrentTaskPtrace<'a> {
30    /// Invoked upon entering a syscall to handle ptrace syscall entry tracing.
31    pub fn on_syscall_enter(self) {
32        if self.current_task.trace_syscalls.load(Ordering::Relaxed) {
33            fuchsia_trace::duration!(CATEGORY_STARNIX, "ptrace:syscall_enter");
34            self.syscall_stop(StopState::SyscallEnterStopping, None);
35        }
36    }
37
38    /// Invoked upon exiting a syscall to handle ptrace syscall exit tracing.
39    pub fn on_syscall_exit(self, is_error: bool) {
40        if self.current_task.trace_syscalls.load(Ordering::Relaxed) {
41            fuchsia_trace::duration!(CATEGORY_STARNIX, "ptrace:syscall_exit");
42            self.syscall_stop(StopState::SyscallExitStopping, Some(is_error));
43        }
44    }
45
46    #[inline(never)]
47    fn syscall_stop(self, stop_state: StopState, is_error: Option<bool>) {
48        {
49            let mut state = self.current_task.write();
50            self.current_task.trace_syscalls.store(false, Ordering::Relaxed);
51            let Some(ptrace) = &mut state.ptrace else {
52                return;
53            };
54
55            let mut sig = SignalInfo::with_detail(
56                SIGTRAP,
57                (linux_uapi::SIGTRAP | 0x80) as i32,
58                SignalDetail::None,
59            );
60            if ptrace.has_option(PtraceOptions::TRACESYSGOOD) {
61                sig.signal.set_ptrace_syscall_bit();
62            }
63            if let Some(is_error) = is_error {
64                ptrace.last_syscall_was_error = is_error;
65            }
66            state.set_stopped(stop_state, Some(sig), None, None);
67        }
68        self.current_task.block_if_stopped();
69    }
70
71    /// If currently being ptraced with the given option, emit the appropriate
72    /// event.  PTRACE_EVENTMSG will return the given message.  Also emits the
73    /// appropriate event for execve in the absence of TRACEEXEC.
74    ///
75    /// Note that the Linux kernel has a documented bug where, if TRACEEXIT is
76    /// enabled, SIGKILL will trigger an event.  We do not exhibit this
77    /// behavior.
78    pub fn on_event(self, trace_kind: PtraceOptions, msg: u64) {
79        if trace_kind.is_empty() {
80            return;
81        }
82        {
83            let mut state = self.current_task.write();
84            let Some(ptrace) = &mut state.ptrace else {
85                return;
86            };
87
88            if !ptrace.has_option(trace_kind) {
89                // If this would be a TRACEEXEC, but TRACEEXEC is not
90                // turned on, then send a SIGTRAP.
91                if trace_kind == PtraceOptions::TRACEEXEC && !ptrace.is_seized() {
92                    // Send a SIGTRAP so that the parent can gain control.
93                    send_signal_first(self.current_task, state, SignalInfo::kernel(SIGTRAP));
94                }
95
96                return;
97            }
98            let event_data = PtraceEventData::new(trace_kind, msg);
99            let siginfo = SignalInfo::with_detail(
100                SIGTRAP,
101                (((event_data.event as u32) << 8) | SIGTRAP.number()) as i32,
102                SignalDetail::None,
103            );
104            state.set_stopped(
105                StopState::PtraceEventStopping,
106                Some(siginfo),
107                None,
108                Some(event_data),
109            );
110        }
111        self.current_task.block_if_stopped();
112    }
113
114    /// For traced tasks, attaches `child_task` to the same tracer if required by the clone
115    /// flags/ptrace options, and returns the `PtraceOptions` event to emit after spawning.
116    pub fn on_clone(self, clone_args: &clone_args, child_task: &Arc<Task>) -> PtraceOptions {
117        let (trace_type, ptrace_state) = {
118            let state = self.current_task.read();
119            let Some(ptrace) = &state.ptrace else {
120                return PtraceOptions::empty();
121            };
122
123            // ptrace(2): If the tracee calls clone(2) with the CLONE_VFORK flag,
124            // PTRACE_EVENT_VFORK will be delivered instead if PTRACE_O_TRACEVFORK
125            // is set, otherwise if the tracee calls clone(2) with the exit signal
126            // set to SIGCHLD, PTRACE_EVENT_FORK will be delivered if
127            // PTRACE_O_TRACEFORK is set.
128            let trace_type = if clone_args.flags & (CLONE_UNTRACED as u64) != 0 {
129                PtraceOptions::empty()
130            } else if clone_args.flags & (CLONE_VFORK as u64) != 0 {
131                PtraceOptions::TRACEVFORK
132            } else if clone_args.exit_signal != (SIGCHLD as u64) {
133                PtraceOptions::TRACECLONE
134            } else {
135                PtraceOptions::TRACEFORK
136            };
137
138            let trace_type = if ptrace.has_option(trace_type) {
139                trace_type
140            } else if clone_args.flags & (CLONE_PTRACE as u64) != 0 {
141                PtraceOptions::empty()
142            } else {
143                return PtraceOptions::empty();
144            };
145
146            (trace_type, ptrace.get_core_state())
147        };
148
149        if ptrace_attach_from_state(child_task, ptrace_state).is_err() {
150            return PtraceOptions::empty();
151        }
152        trace_type
153    }
154}