1use 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#[derive(Clone, Default, PartialEq)]
94pub enum PtraceStatus {
95 #[default]
97 Default,
98 Continuing,
100 Listening,
107}
108
109impl PtraceStatus {
110 pub fn is_continuing(&self) -> bool {
111 *self == PtraceStatus::Continuing
112 }
113}
114
115#[derive(Copy, Clone, PartialEq)]
117pub enum PtraceAttachType {
118 Attach,
120 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
171pub struct PtraceEventData {
173 pub event: PtraceEvent,
175
176 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#[derive(Clone)]
192pub struct PtraceCoreState {
193 pub pid: pid_t,
196
197 pub task: Weak<Task>,
199
200 pub thread_group: Weak<ThreadGroup>,
202
203 pub attach_type: PtraceAttachType,
206
207 pub options: PtraceOptions,
209
210 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
221pub struct PtraceState {
223 pub core_state: PtraceCoreState,
225
226 pub tracee_waiters: WaitQueue,
229
230 pub last_signal: Option<SignalInfo>,
233
234 pub last_signal_waitable: bool,
237
238 pub event_data: Option<PtraceEventData>,
240
241 pub stop_status: PtraceStatus,
244
245 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 return self.last_signal_waitable;
295 }
296 if !options.wait_for_continued && !stop.is_stopping_or_stopped() {
297 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 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 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 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 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 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 pub fn get_core_state_for_clone(
445 &self,
446 clone_args: &clone_args,
447 ) -> (PtraceOptions, Option<PtraceCoreState>) {
448 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
475struct TracedZombie {
477 artificial_zombie: ZombieProcess,
479
480 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#[derive(Default)]
525pub struct ZombiePtracees {
526 zombies: BTreeMap<tid_t, ReleaseGuard<TracedZombie>>,
529}
530
531impl ZombiePtracees {
532 pub fn new() -> Self {
533 Self::default()
534 }
535
536 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 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 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 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 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 pub fn has_zombie_matching(&self, selector: &ProcessSelector) -> bool {
615 self.zombies.values().any(|z| z.artificial_zombie.matches_selector(selector))
616 }
617
618 pub fn has_tracee(&self, tid: tid_t) -> bool {
621 self.zombies.contains_key(&tid)
622 }
623
624 pub fn get_waitable_entry(
628 &mut self,
629 selector: &ProcessSelector,
630 options: &WaitingOptions,
631 ) -> Option<(ZombieProcess, Option<(Weak<ThreadGroup>, OwnedRef<ZombieProcess>)>)> {
632 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 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
658pub const PR_SET_PTRACER_ANY: i32 = -1;
661
662#[derive(Copy, Clone, Default, PartialEq)]
665pub enum PtraceAllowedPtracers {
666 #[default]
667 None,
668 Some(pid_t),
669 Any,
670}
671
672fn 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 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 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
809pub 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 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 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 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 if addr.ptr() != std::mem::size_of::<SigSet>() {
973 return error!(EINVAL);
974 }
975 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 if addr.ptr() != std::mem::size_of::<SigSet>() {
986 return error!(EINVAL);
987 }
988 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 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 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
1074fn 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 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
1108pub 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 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 SignalInfo::forced(SIGTRAP)
1143 } else {
1144 SignalInfo::forced(SIGSTOP)
1146 };
1147 send_signal_first(locked, tracee_task, state, signal);
1148
1149 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 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 ¤t_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(¤t_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 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
1223pub 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), ®s.as_bytes()[..*len])?;
1275 } else {
1276 let regs = thread_state.registers.to_user_regs_struct();
1277 current_task.write_memory(UserAddress::from(base), ®s.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(®s);
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(®s);
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}