git: d3b7bbee9275 - main - ptrace(2): add PT_GET_CHILDREN
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 20 Jul 2026 20:20:15 UTC
The branch main has been updated by kib:
URL: https://cgit.FreeBSD.org/src/commit/?id=d3b7bbee9275d5a3c58a9e75d1fffc60a9333352
commit d3b7bbee9275d5a3c58a9e75d1fffc60a9333352
Author: Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-07-17 14:57:04 +0000
Commit: Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-07-20 20:19:12 +0000
ptrace(2): add PT_GET_CHILDREN
Reviewed by: markj
Sponsored by: The FreeBSD Foundation
MFC after: 1 week
Differential revision: https://reviews.freebsd.org/D58315
---
sys/compat/freebsd32/freebsd32_misc.c | 16 ++++
sys/kern/sys_process.c | 139 +++++++++++++++++++++++++++++++++-
sys/sys/ptrace.h | 12 +++
3 files changed, 165 insertions(+), 2 deletions(-)
diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c
index e07000015d54..e899c55ee5b1 100644
--- a/sys/compat/freebsd32/freebsd32_misc.c
+++ b/sys/compat/freebsd32/freebsd32_misc.c
@@ -1031,6 +1031,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
register_t args[nitems(td->td_sa.args)];
struct ptrace_sc_ret psr;
int ptevents;
+ struct ptrace_child *children;
} r;
union {
struct ptrace_io_desc32 piod;
@@ -1173,6 +1174,14 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
pscr_args[i] = pscr_args32[i];
r.sr.pscr_args = pscr_args;
break;
+ case PT_GET_CHILDREN:
+ if (uap->addr == NULL)
+ addr = NULL;
+ else if (uap->data < 0)
+ error = EINVAL;
+ else
+ addr = &r.children;
+ break;
case PTINTERNAL_FIRST ... PTINTERNAL_LAST:
error = EINVAL;
break;
@@ -1242,6 +1251,13 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
offsetof(struct ptrace_sc_remote32, pscr_ret),
sizeof(r32.psr));
break;
+ case PT_GET_CHILDREN:
+ if (uap->addr != 0) {
+ error = copyout(r.children, uap->addr,
+ td->td_retval[0] * sizeof(struct ptrace_child));
+ free(r.children, M_TEMP);
+ }
+ break;
}
return (error);
diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c
index 2ac0a33c3421..fc81ac4ca6c8 100644
--- a/sys/kern/sys_process.c
+++ b/sys/kern/sys_process.c
@@ -733,6 +733,7 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap)
syscallarg_t args[nitems(td->td_sa.args)];
struct ptrace_sc_ret psr;
int ptevents;
+ struct ptrace_child *children;
} r;
syscallarg_t pscr_args[nitems(td->td_sa.args)];
void *addr;
@@ -816,6 +817,14 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap)
break;
r.sr.pscr_args = pscr_args;
break;
+ case PT_GET_CHILDREN:
+ if (uap->addr == NULL)
+ addr = NULL;
+ else if (uap->data < 0)
+ error = EINVAL;
+ else
+ addr = &r.children;
+ break;
case PTINTERNAL_FIRST ... PTINTERNAL_LAST:
error = EINVAL;
break;
@@ -870,6 +879,13 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap)
offsetof(struct ptrace_sc_remote, pscr_ret),
sizeof(r.sr.pscr_ret));
break;
+ case PT_GET_CHILDREN:
+ if (uap->addr != NULL) {
+ error = copyout(r.children, uap->addr,
+ td->td_retval[0] * sizeof(struct ptrace_child));
+ free(r.children, M_TEMP);
+ }
+ break;
}
return (error);
@@ -965,6 +981,64 @@ proc_can_ptrace(struct thread *td, struct proc *p)
return (0);
}
+static int
+ptrace_count_children(struct thread *td, struct proc *p, bool count_everything)
+{
+ struct proc *pp;
+ int error, num;
+
+ sx_assert(&proctree_lock, SX_LOCKED);
+ num = 0;
+ LIST_FOREACH(pp, &p->p_children, p_sibling) {
+ if (count_everything) {
+ error = 0;
+ } else {
+ PROC_LOCK(pp);
+ error = p_cansee(td, pp);
+ PROC_UNLOCK(pp);
+ }
+ if (error != 0)
+ continue;
+ num++;
+ }
+ LIST_FOREACH(pp, &p->p_orphans, p_orphan) {
+ if (count_everything) {
+ error = 0;
+ } else {
+ PROC_LOCK(pp);
+ error = p_cansee(td, pp);
+ PROC_UNLOCK(pp);
+ }
+ if (error != 0)
+ continue;
+ num++;
+ }
+ return (num);
+}
+
+static bool
+ptrace_report_child(struct thread *td, struct proc *p, struct proc *pp,
+ struct ptrace_child *ptc)
+{
+ sx_assert(&proctree_lock, SX_LOCKED);
+
+ PROC_LOCK(pp);
+ if (p_cansee(td, pp) != 0) {
+ PROC_UNLOCK(pp);
+ return (false);
+ }
+ ptc->pid = pp->p_pid;
+ if ((pp->p_flag & P_TRACED) != 0) {
+ ptc->flags |= PTCHLD_TRACED;
+ if (pp->p_pptr == td->td_proc)
+ ptc->flags |= PTCHLD_TRACED_BY_ME;
+ }
+ if ((pp->p_flag & P_WEXIT) != 0)
+ ptc->flags |= PTCHLD_EXITED;
+ PROC_UNLOCK(pp);
+ return (true);
+}
+
static struct thread *
ptrace_sel_coredump_thread(struct proc *p)
{
@@ -995,7 +1069,8 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
struct ptrace_coredump *pc;
struct thr_coredump_req *tcq;
struct thr_syscall_req *tsr;
- int error, num, tmp;
+ struct ptrace_child *children, *ptc;
+ int error, num, num1, tmp;
lwpid_t tid = 0, *buf;
#ifdef COMPAT_FREEBSD32
int wrap32 = 0, safe = 0;
@@ -1021,6 +1096,7 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
case PT_SET_EVENT_MASK:
case PT_DETACH:
case PT_GET_SC_ARGS:
+ case PT_GET_CHILDREN:
sx_xlock(&proctree_lock);
proctree_locked = true;
break;
@@ -1132,8 +1208,14 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
/* Allow thread to clear single step for itself */
if (td->td_tid == tid)
break;
+ goto default_check;
+
+ case PT_GET_CHILDREN:
+ if (p == curp)
+ break;
+ goto default_check;
- /* FALLTHROUGH */
+default_check:
default:
/*
* Check for ptrace eligibility before waiting for
@@ -1864,6 +1946,59 @@ coredump_cleanup_nofp:
free(tsr, M_TEMP);
break;
+ case PT_GET_CHILDREN:
+ PROC_UNLOCK(p);
+get_children_repeat:
+ /*
+ * If addr != NULL, we should ignore p_cansee() to
+ * allocate enough space for the children array,
+ * because the process is allowed to change visibility
+ * between loops. But do not count children which
+ * we cannot see when only returning the count, to
+ * avoid a leak of information.
+ */
+ num = ptrace_count_children(td, p, addr != NULL);
+
+ if (addr == NULL) {
+ td->td_retval[0] = num;
+ PROC_LOCK(p);
+ break;
+ }
+ if (data < num * sizeof(struct ptrace_child)) {
+ error = ENOMEM;
+ PROC_LOCK(p);
+ break;
+ }
+ sx_xunlock(&proctree_lock);
+ children = mallocarray(num, sizeof(struct ptrace_child),
+ M_TEMP, M_WAITOK | M_ZERO);
+ sx_xlock(&proctree_lock);
+ num1 = ptrace_count_children(td, p, true);
+ if (num1 > num) {
+ free(children, M_TEMP);
+ goto get_children_repeat;
+ }
+ num = num1;
+ num1 = 0;
+ LIST_FOREACH(pp, &p->p_children, p_sibling) {
+ MPASS(num1 < num);
+ ptc = &children[num1];
+ if (ptrace_report_child(td, p, pp, ptc))
+ num1++;
+ }
+ LIST_FOREACH(pp, &p->p_orphans, p_orphan) {
+ MPASS(num1 < num);
+ ptc = &children[num1];
+ if (ptrace_report_child(td, p, pp, ptc)) {
+ num1++;
+ ptc->flags |= PTCHLD_ORPHAN;
+ }
+ }
+ *(struct ptrace_child **)addr = children;
+ td->td_retval[0] = num1;
+ PROC_LOCK(p);
+ break;
+
default:
#ifdef __HAVE_PTRACE_MACHDEP
if (req >= PT_FIRSTMACH) {
diff --git a/sys/sys/ptrace.h b/sys/sys/ptrace.h
index 2aa82ce07a1d..54669493ca75 100644
--- a/sys/sys/ptrace.h
+++ b/sys/sys/ptrace.h
@@ -86,6 +86,7 @@
#define PT_SETREGSET 43 /* Set a target register set */
#define PT_SC_REMOTE 44 /* Execute a syscall */
#define PT_SET_SC_RET 45 /* Set (fake) syscall results */
+#define PT_GET_CHILDREN 46 /* Report children */
#define PT_FIRSTMACH 64 /* for machine-specific requests */
#define PT_LASTMACH 127
@@ -206,6 +207,17 @@ struct ptrace_sc_remote {
syscallarg_t *pscr_args;
};
+#define PTCHLD_TRACED 0x00000001
+#define PTCHLD_TRACED_BY_ME 0x00000002
+#define PTCHLD_EXITED 0x00000004
+#define PTCHLD_ORPHAN 0x00000008
+
+struct ptrace_child {
+ pid_t pid;
+ int flags;
+ uint64ptr_t rsrv[3];
+};
+
#ifdef _KERNEL
#include <sys/proc.h>