summaryrefslogtreecommitdiff
path: root/sys/kern
diff options
context:
space:
mode:
authorPhilip Guenther <guenther@cvs.openbsd.org>2017-10-14 10:17:09 +0000
committerPhilip Guenther <guenther@cvs.openbsd.org>2017-10-14 10:17:09 +0000
commit22dad987c0d11944d70c083972e60694c525f65f (patch)
treeddea8ae73233b6dc9cb7144e659b0818dc12eb69 /sys/kern
parentc89113362f6824f3ddd2eec0f87c116144020613 (diff)
Split sys_ptrace() by request type:
- control operations: trace_me, attach, detach, step, kill, continue. Manipulate process relation/state or send a signal - kernel-state get/set: thread list, event mask, trace state. About the process and don't require target to be stopped, need copyin/out - user-state get/set: memory, register, window cookie. Often thread-specific, require target to be stopped, need copyin/out sys_ptrace() changes to handle request checking, copyin/out to kernel buffers with size check and zeroing, and dispatching to the routines above for the real work. This simplfies the permission checks and copyin/out handling and will simplify lock handling in the future. Inspired in part by FreeBSD. ok mpi@ visa@
Diffstat (limited to 'sys/kern')
-rw-r--r--sys/kern/sys_process.c836
1 files changed, 477 insertions, 359 deletions
diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c
index 3700c0d2486..647b488af65 100644
--- a/sys/kern/sys_process.c
+++ b/sys/kern/sys_process.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sys_process.c,v 1.77 2017/07/19 14:17:49 deraadt Exp $ */
+/* $OpenBSD: sys_process.c,v 1.78 2017/10/14 10:17:08 guenther Exp $ */
/* $NetBSD: sys_process.c,v 1.55 1996/05/15 06:17:47 tls Exp $ */
/*-
@@ -67,10 +67,20 @@
#include <machine/reg.h>
+#ifdef PTRACE
+
+static inline int process_checktracestate(struct process *_curpr,
+ struct process *_tr, struct proc *_t);
+static inline struct process *process_tprfind(pid_t _tpid, struct proc **_tp);
+
+int ptrace_ctrl(struct proc *, int, pid_t, caddr_t, int);
+int ptrace_ustate(struct proc *, int, pid_t, void *, int, register_t *);
+int ptrace_kstate(struct proc *, int, pid_t, void *);
int process_auxv_offset(struct proc *, struct process *, struct uio *);
-#ifdef PTRACE
int global_ptrace; /* permit tracing of not children */
+
+
/*
* Process debugging system call.
*/
@@ -83,106 +93,238 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
syscallarg(caddr_t) addr;
syscallarg(int) data;
} */ *uap = v;
- struct proc *t; /* target thread */
- struct process *tr; /* target process */
- struct uio uio;
- struct iovec iov;
- struct ptrace_io_desc piod;
- struct ptrace_event pe;
- struct ptrace_thread_state pts;
- struct reg *regs;
-#if defined (PT_SETFPREGS) || defined (PT_GETFPREGS)
- struct fpreg *fpregs;
-#endif
-#if defined (PT_SETXMMREGS) || defined (PT_GETXMMREGS)
- struct xmmregs *xmmregs;
-#endif
-#ifdef PT_WCOOKIE
- register_t wcookie;
-#endif
- int error, write;
- int temp = 0;
int req = SCARG(uap, req);
pid_t pid = SCARG(uap, pid);
- caddr_t addr = SCARG(uap, addr);
+ caddr_t uaddr = SCARG(uap, addr); /* userspace */
+ void *kaddr = NULL; /* kernelspace */
int data = SCARG(uap, data);
- int s;
+ union {
+ struct ptrace_thread_state u_pts;
+ struct ptrace_io_desc u_piod;
+ struct ptrace_event u_pe;
+ struct ptrace_state u_ps;
+ register_t u_wcookie;
+ } u;
+ int size = 0;
+ enum { NONE, IN, IN_ALLOC, OUT, OUT_ALLOC, IN_OUT } mode;
+ int kstate = 0;
+ int error;
- /* "A foolish consistency..." XXX */
+ *retval = 0;
+
+ /* Figure out what sort of copyin/out operations we'll do */
switch (req) {
case PT_TRACE_ME:
- t = p;
- tr = t->p_p;
- break;
+ case PT_CONTINUE:
+ case PT_KILL:
+ case PT_ATTACH:
+ case PT_DETACH:
+#ifdef PT_STEP
+ case PT_STEP:
+#endif
+ /* control operations do no copyin/out; dispatch directly */
+ return ptrace_ctrl(p, req, pid, uaddr, data);
- /* calls that only operate on the PID */
case PT_READ_I:
case PT_READ_D:
case PT_WRITE_I:
case PT_WRITE_D:
- case PT_KILL:
- case PT_ATTACH:
+ mode = NONE;
+ break;
case PT_IO:
- case PT_SET_EVENT_MASK:
- case PT_GET_EVENT_MASK:
- case PT_GET_PROCESS_STATE:
+ mode = IN_OUT;
+ size = sizeof u.u_piod;
+ data = size; /* suppress the data == size check */
+ break;
case PT_GET_THREAD_FIRST:
+ mode = OUT;
+ size = sizeof u.u_pts;
+ kstate = 1;
+ break;
case PT_GET_THREAD_NEXT:
- case PT_DETACH:
- default:
- /* Find the process we're supposed to be operating on. */
- if ((tr = prfind(pid)) == NULL)
- return (ESRCH);
- t = tr->ps_mainproc; /* XXX */
+ mode = IN_OUT;
+ size = sizeof u.u_pts;
+ kstate = 1;
+ break;
+ case PT_GET_EVENT_MASK:
+ mode = OUT;
+ size = sizeof u.u_pe;
+ kstate = 1;
+ break;
+ case PT_SET_EVENT_MASK:
+ mode = IN;
+ size = sizeof u.u_pe;
+ kstate = 1;
+ break;
+ case PT_GET_PROCESS_STATE:
+ mode = OUT;
+ size = sizeof u.u_ps;
+ kstate = 1;
break;
-
- /* calls that accept a PID or a thread ID */
- case PT_CONTINUE:
-#ifdef PT_STEP
- case PT_STEP:
-#endif
-#ifdef PT_WCOOKIE
- case PT_WCOOKIE:
-#endif
case PT_GETREGS:
+ mode = OUT_ALLOC;
+ size = sizeof(struct reg);
+ break;
case PT_SETREGS:
+ mode = IN_ALLOC;
+ size = sizeof(struct reg);
+ break;
#ifdef PT_GETFPREGS
case PT_GETFPREGS:
+ mode = OUT_ALLOC;
+ size = sizeof(struct fpreg);
+ break;
#endif
#ifdef PT_SETFPREGS
case PT_SETFPREGS:
+ mode = IN_ALLOC;
+ size = sizeof(struct fpreg);
+ break;
#endif
#ifdef PT_GETXMMREGS
case PT_GETXMMREGS:
+ mode = OUT_ALLOC;
+ size = sizeof(struct xmmregs);
+ break;
#endif
#ifdef PT_SETXMMREGS
case PT_SETXMMREGS:
+ mode = IN_ALLOC;
+ size = sizeof(struct xmmregs);
+ break;
#endif
- if (pid > THREAD_PID_OFFSET) {
- t = tfind(pid - THREAD_PID_OFFSET);
- if (t == NULL)
- return (ESRCH);
- tr = t->p_p;
- } else {
- if ((tr = prfind(pid)) == NULL)
- return (ESRCH);
- t = tr->ps_mainproc; /* XXX */
+#ifdef PT_WCOOKIE
+ case PT_WCOOKIE:
+ mode = OUT;
+ size = sizeof u.u_wcookie;
+ data = size; /* suppress the data == size check */
+ break;
+#endif
+ default:
+ return EINVAL;
+ }
+
+
+ /* Now do any copyin()s and allocations in a consistent manner */
+ switch (mode) {
+ case NONE:
+ kaddr = uaddr;
+ break;
+ case IN:
+ case IN_OUT:
+ case OUT:
+ KASSERT(size <= sizeof u);
+ if (data != size)
+ return EINVAL;
+ if (mode == OUT)
+ memset(&u, 0, size);
+ else { /* IN or IN_OUT */
+ if ((error = copyin(uaddr, &u, size)))
+ return error;
+ }
+ kaddr = &u;
+ break;
+ case IN_ALLOC:
+ kaddr = malloc(size, M_TEMP, M_WAITOK);
+ if ((error = copyin(uaddr, kaddr, size))) {
+ free(kaddr, M_TEMP, size);
+ return error;
}
break;
+ case OUT_ALLOC:
+ kaddr = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
+ break;
}
- if ((tr->ps_flags & PS_INEXEC) != 0)
- return (EAGAIN);
+ if (kstate)
+ error = ptrace_kstate(p, req, pid, kaddr);
+ else
+ error = ptrace_ustate(p, req, pid, kaddr, data, retval);
+
+ /* Do any copyout()s and frees */
+ if (error == 0) {
+ switch (mode) {
+ case NONE:
+ case IN:
+ case IN_ALLOC:
+ break;
+ case IN_OUT:
+ case OUT:
+ error = copyout(&u, uaddr, size);
+ if (req == PT_IO) {
+ /* historically, errors here are ignored */
+ error = 0;
+ }
+ break;
+ case OUT_ALLOC:
+ error = copyout(kaddr, uaddr, size);
+ break;
+ }
+ }
+
+ if (mode == IN_ALLOC || mode == OUT_ALLOC)
+ free(kaddr, M_TEMP, size);
+ return error;
+}
+
+/*
+ * ptrace control requests: attach, detach, continue, kill, single-step, etc
+ */
+int
+ptrace_ctrl(struct proc *p, int req, pid_t pid, caddr_t addr, int data)
+{
+ struct proc *t; /* target thread */
+ struct process *tr; /* target process */
+ int error = 0;
+ int s;
- /* Make sure we can operate on it. */
switch (req) {
- case PT_TRACE_ME:
- /* Saying that you're being traced is always legal. */
+ case PT_TRACE_ME:
+ /* Just set the trace flag. */
+ tr = p->p_p;
+ atomic_setbits_int(&tr->ps_flags, PS_TRACED);
+ tr->ps_oppid = tr->ps_pptr->ps_pid;
+ if (tr->ps_ptstat == NULL)
+ tr->ps_ptstat = malloc(sizeof(*tr->ps_ptstat),
+ M_SUBPROC, M_WAITOK);
+ memset(tr->ps_ptstat, 0, sizeof(*tr->ps_ptstat));
+ return 0;
+
+ /* calls that only operate on the PID */
+ case PT_KILL:
+ case PT_ATTACH:
+ case PT_DETACH:
+ /* Find the process we're supposed to be operating on. */
+ if ((tr = prfind(pid)) == NULL)
+ return (ESRCH);
+ t = TAILQ_FIRST(&tr->ps_threads);
+ break;
+
+ /* calls that accept a PID or a thread ID */
+ case PT_CONTINUE:
+#ifdef PT_STEP
+ case PT_STEP:
+#endif
+ if ((tr = process_tprfind(pid, &t)) == NULL)
+ return ESRCH;
break;
+ }
+
+ /* Check permissions/state */
+ if (req != PT_ATTACH) {
+ /* Check that the data is a valid signal number or zero. */
+ if (req != PT_KILL && (data < 0 || data >= NSIG))
+ return EINVAL;
+
+ /* Most operations require the target to already be traced */
+ if ((error = process_checktracestate(p->p_p, tr, t)))
+ return error;
- case PT_ATTACH:
+ /* Do single-step fixup if needed. */
+ FIX_SSTEP(t);
+ } else {
/*
- * You can't attach to a process if:
+ * PT_ATTACH is the opposite; you can't attach to a process if:
* (1) it's the process that's doing the attaching,
*/
if (tr == p->p_p)
@@ -201,7 +343,13 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
return (EBUSY);
/*
- * (4) it's not owned by you, or the last exec
+ * (4) it's in the middle of execve(2)
+ */
+ if (ISSET(tr->ps_flags, PS_INEXEC))
+ return (EAGAIN);
+
+ /*
+ * (5) it's not owned by you, or the last exec
* gave us setuid/setgid privs (unless
* you're root), or...
*
@@ -217,14 +365,14 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
return (error);
/*
- * (4.5) it's not a child of the tracing process.
+ * (5.5) it's not a child of the tracing process.
*/
if (global_ptrace == 0 && !inferior(tr, p->p_p) &&
(error = suser(p, 0)) != 0)
return (error);
/*
- * (5) ...it's init, which controls the security level
+ * (6) ...it's init, which controls the security level
* of the entire system, and the system was not
* compiled with permanently insecure mode turned
* on.
@@ -233,202 +381,18 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
return (EPERM);
/*
- * (6) it's an ancestor of the current process and
+ * (7) it's an ancestor of the current process and
* not init (because that would create a loop in
* the process graph).
*/
if (tr->ps_pid != 1 && inferior(p->p_p, tr))
return (EINVAL);
- break;
-
- case PT_READ_I:
- case PT_READ_D:
- case PT_WRITE_I:
- case PT_WRITE_D:
- case PT_IO:
- case PT_CONTINUE:
- case PT_KILL:
- case PT_DETACH:
-#ifdef PT_STEP
- case PT_STEP:
-#endif
- case PT_SET_EVENT_MASK:
- case PT_GET_EVENT_MASK:
- case PT_GET_PROCESS_STATE:
- case PT_GETREGS:
- case PT_SETREGS:
-#ifdef PT_GETFPREGS
- case PT_GETFPREGS:
-#endif
-#ifdef PT_SETFPREGS
- case PT_SETFPREGS:
-#endif
-#ifdef PT_GETXMMREGS
- case PT_GETXMMREGS:
-#endif
-#ifdef PT_SETXMMREGS
- case PT_SETXMMREGS:
-#endif
-#ifdef PT_WCOOKIE
- case PT_WCOOKIE:
-#endif
- /*
- * You can't do what you want to the process if:
- * (1) It's not being traced at all,
- */
- if (!ISSET(tr->ps_flags, PS_TRACED))
- return (EPERM);
-
- /*
- * (2) it's not being traced by _you_, or
- */
- if (tr->ps_pptr != p->p_p)
- return (EBUSY);
-
- /*
- * (3) it's not currently stopped.
- */
- if (t->p_stat != SSTOP || !ISSET(tr->ps_flags, PS_WAITED))
- return (EBUSY);
- break;
-
- case PT_GET_THREAD_FIRST:
- case PT_GET_THREAD_NEXT:
- /*
- * You can't do what you want to the process if:
- * (1) It's not being traced at all,
- */
- if (!ISSET(tr->ps_flags, PS_TRACED))
- return (EPERM);
-
- /*
- * (2) it's not being traced by _you_, or
- */
- if (tr->ps_pptr != p->p_p)
- return (EBUSY);
-
- /*
- * Do the work here because the request isn't actually
- * associated with 't'
- */
- if (data != sizeof(pts))
- return (EINVAL);
-
- if (req == PT_GET_THREAD_NEXT) {
- error = copyin(addr, &pts, sizeof(pts));
- if (error)
- return (error);
-
- t = tfind(pts.pts_tid - THREAD_PID_OFFSET);
- if (t == NULL || ISSET(t->p_flag, P_WEXIT))
- return (ESRCH);
- if (t->p_p != tr)
- return (EINVAL);
- t = TAILQ_NEXT(t, p_thr_link);
- } else {
- t = TAILQ_FIRST(&tr->ps_threads);
- }
-
- if (t == NULL)
- pts.pts_tid = -1;
- else
- pts.pts_tid = t->p_tid + THREAD_PID_OFFSET;
- return (copyout(&pts, addr, sizeof(pts)));
-
- default: /* It was not a legal request. */
- return (EINVAL);
}
- /* Do single-step fixup if needed. */
- FIX_SSTEP(t);
-
- /* Now do the operation. */
- write = 0;
- *retval = 0;
-
switch (req) {
- case PT_TRACE_ME:
- /* Just set the trace flag. */
- atomic_setbits_int(&tr->ps_flags, PS_TRACED);
- tr->ps_oppid = tr->ps_pptr->ps_pid;
- if (tr->ps_ptstat == NULL)
- tr->ps_ptstat = malloc(sizeof(*tr->ps_ptstat),
- M_SUBPROC, M_WAITOK);
- memset(tr->ps_ptstat, 0, sizeof(*tr->ps_ptstat));
- return (0);
- case PT_WRITE_I: /* XXX no separate I and D spaces */
- case PT_WRITE_D:
- write = 1;
- temp = data;
- case PT_READ_I: /* XXX no separate I and D spaces */
- case PT_READ_D:
- /* write = 0 done above. */
- iov.iov_base = (caddr_t)&temp;
- iov.iov_len = sizeof(int);
- uio.uio_iov = &iov;
- uio.uio_iovcnt = 1;
- uio.uio_offset = (off_t)(vaddr_t)addr;
- uio.uio_resid = sizeof(int);
- uio.uio_segflg = UIO_SYSSPACE;
- uio.uio_rw = write ? UIO_WRITE : UIO_READ;
- uio.uio_procp = p;
- error = process_domem(p, tr, &uio, write ? PT_WRITE_I :
- PT_READ_I);
- if (write == 0)
- *retval = temp;
- return (error);
- case PT_IO:
- error = copyin(addr, &piod, sizeof(piod));
- if (error)
- return (error);
- iov.iov_base = piod.piod_addr;
- iov.iov_len = piod.piod_len;
- uio.uio_iov = &iov;
- uio.uio_iovcnt = 1;
- uio.uio_offset = (off_t)(vaddr_t)piod.piod_offs;
- uio.uio_resid = piod.piod_len;
- uio.uio_segflg = UIO_USERSPACE;
- uio.uio_procp = p;
- switch (piod.piod_op) {
- case PIOD_READ_I:
- req = PT_READ_I;
- uio.uio_rw = UIO_READ;
- break;
- case PIOD_READ_D:
- req = PT_READ_D;
- uio.uio_rw = UIO_READ;
- break;
- case PIOD_WRITE_I:
- req = PT_WRITE_I;
- uio.uio_rw = UIO_WRITE;
- break;
- case PIOD_WRITE_D:
- req = PT_WRITE_D;
- uio.uio_rw = UIO_WRITE;
- break;
- case PIOD_READ_AUXV:
- req = PT_READ_D;
- uio.uio_rw = UIO_READ;
- temp = tr->ps_emul->e_arglen * sizeof(char *);
- if (uio.uio_offset > temp)
- return (EIO);
- if (uio.uio_resid > temp - uio.uio_offset)
- uio.uio_resid = temp - uio.uio_offset;
- piod.piod_len = iov.iov_len = uio.uio_resid;
- error = process_auxv_offset(p, tr, &uio);
- if (error)
- return (error);
- break;
- default:
- return (EINVAL);
- }
- error = process_domem(p, tr, &uio, req);
- piod.piod_len -= uio.uio_resid;
- (void) copyout(&piod, addr, sizeof(piod));
- return (error);
#ifdef PT_STEP
- case PT_STEP:
+ case PT_STEP:
/*
* From the 4.4BSD PRM:
* "Execution continues as in request PT_CONTINUE; however
@@ -436,7 +400,7 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
* instruction, execution stops again. [ ... ]"
*/
#endif
- case PT_CONTINUE:
+ case PT_CONTINUE:
/*
* From the 4.4BSD PRM:
* "The data argument is taken as a signal number and the
@@ -452,10 +416,6 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
if (pid < THREAD_PID_OFFSET && tr->ps_single)
t = tr->ps_single;
- /* Check that the data is a valid signal number or zero. */
- if (data < 0 || data >= NSIG)
- return (EINVAL);
-
/* If the address parameter is not (int *)1, set the pc. */
if ((int *)addr != (int *)1)
if ((error = process_set_pc(t, addr)) != 0)
@@ -471,7 +431,7 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
#endif
goto sendsig;
- case PT_DETACH:
+ case PT_DETACH:
/*
* From the 4.4BSD PRM:
* "The data argument is taken as a signal number and the
@@ -487,10 +447,6 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
if (pid < THREAD_PID_OFFSET && tr->ps_single)
t = tr->ps_single;
- /* Check that the data is a valid signal number or zero. */
- if (data < 0 || data >= NSIG)
- return (EINVAL);
-
#ifdef PT_STEP
/*
* Stop single stepping.
@@ -525,10 +481,9 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
if (data != 0)
psignal(t, data);
}
+ break;
- return (0);
-
- case PT_KILL:
+ case PT_KILL:
if (pid < THREAD_PID_OFFSET && tr->ps_single)
t = tr->ps_single;
@@ -536,7 +491,7 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
data = SIGKILL;
goto sendsig; /* in PT_CONTINUE, above. */
- case PT_ATTACH:
+ case PT_ATTACH:
/*
* As was done in procfs:
* Go ahead and set the trace flag.
@@ -555,121 +510,284 @@ sys_ptrace(struct proc *p, void *v, register_t *retval)
M_SUBPROC, M_WAITOK);
data = SIGSTOP;
goto sendsig;
+ default:
+ KASSERTMSG(0, "%s: unhandled request %d", __func__, req);
+ break;
+ }
- case PT_GET_EVENT_MASK:
- if (data != sizeof(pe))
- return (EINVAL);
- memset(&pe, 0, sizeof(pe));
- pe.pe_set_event = tr->ps_ptmask;
- return (copyout(&pe, addr, sizeof(pe)));
- case PT_SET_EVENT_MASK:
- if (data != sizeof(pe))
- return (EINVAL);
- if ((error = copyin(addr, &pe, sizeof(pe))))
- return (error);
- tr->ps_ptmask = pe.pe_set_event;
- return (0);
+ return error;
+}
- case PT_GET_PROCESS_STATE:
- if (data != sizeof(*tr->ps_ptstat))
- return (EINVAL);
+/*
+ * ptrace kernel-state requests: thread list, event mask, process state
+ */
+int
+ptrace_kstate(struct proc *p, int req, pid_t pid, void *addr)
+{
+ struct process *tr; /* target process */
+ struct ptrace_event *pe = addr;
+ int error;
+
+ KASSERT((p->p_flag & P_SYSTEM) == 0);
+
+ /* Find the process we're supposed to be operating on. */
+ if ((tr = prfind(pid)) == NULL)
+ return ESRCH;
+
+ if ((error = process_checktracestate(p->p_p, tr, NULL)))
+ return error;
+
+ switch (req) {
+ case PT_GET_THREAD_FIRST:
+ case PT_GET_THREAD_NEXT:
+ {
+ struct ptrace_thread_state *pts = addr;
+ struct proc *t;
+
+ if (req == PT_GET_THREAD_NEXT) {
+ t = tfind(pts->pts_tid - THREAD_PID_OFFSET);
+ if (t == NULL || ISSET(t->p_flag, P_WEXIT))
+ return ESRCH;
+ if (t->p_p != tr)
+ return EINVAL;
+ t = TAILQ_NEXT(t, p_thr_link);
+ } else {
+ t = TAILQ_FIRST(&tr->ps_threads);
+ }
+
+ if (t == NULL)
+ pts->pts_tid = -1;
+ else
+ pts->pts_tid = t->p_tid + THREAD_PID_OFFSET;
+ return 0;
+ }
+ }
+ switch (req) {
+ case PT_GET_EVENT_MASK:
+ pe->pe_set_event = tr->ps_ptmask;
+ break;
+ case PT_SET_EVENT_MASK:
+ tr->ps_ptmask = pe->pe_set_event;
+ break;
+ case PT_GET_PROCESS_STATE:
if (tr->ps_single)
tr->ps_ptstat->pe_tid =
tr->ps_single->p_tid + THREAD_PID_OFFSET;
+ memcpy(addr, tr->ps_ptstat, sizeof *tr->ps_ptstat);
+ break;
+ default:
+ KASSERTMSG(0, "%s: unhandled request %d", __func__, req);
+ break;
+ }
- return (copyout(tr->ps_ptstat, addr, sizeof(*tr->ps_ptstat)));
+ return 0;
+}
- case PT_SETREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
+/*
+ * ptrace user-state requests: memory access, registers, stack cookie
+ */
+int
+ptrace_ustate(struct proc *p, int req, pid_t pid, void *addr, int data,
+ register_t *retval)
+{
+ struct proc *t; /* target thread */
+ struct process *tr; /* target process */
+ struct uio uio;
+ struct iovec iov;
+ int error, write;
+ int temp = 0;
- regs = malloc(sizeof(*regs), M_TEMP, M_WAITOK);
- error = copyin(addr, regs, sizeof(*regs));
- if (error == 0) {
- error = process_write_regs(t, regs);
- }
- free(regs, M_TEMP, sizeof(*regs));
- return (error);
- case PT_GETREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
+ KASSERT((p->p_flag & P_SYSTEM) == 0);
- regs = malloc(sizeof(*regs), M_TEMP, M_WAITOK);
- error = process_read_regs(t, regs);
- if (error == 0)
- error = copyout(regs, addr, sizeof (*regs));
- free(regs, M_TEMP, sizeof(*regs));
- return (error);
-#ifdef PT_SETFPREGS
- case PT_SETFPREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
+ /* Accept either PID or TID */
+ if ((tr = process_tprfind(pid, &t)) == NULL)
+ return ESRCH;
+
+ if ((error = process_checktracestate(p->p_p, tr, t)))
+ return error;
+
+ FIX_SSTEP(t);
- fpregs = malloc(sizeof(*fpregs), M_TEMP, M_WAITOK);
- error = copyin(addr, fpregs, sizeof(*fpregs));
- if (error == 0) {
- error = process_write_fpregs(t, fpregs);
+ /* Now do the operation. */
+ write = 0;
+
+ if ((error = process_checkioperm(p, tr)) != 0)
+ return error;
+
+ switch (req) {
+ case PT_WRITE_I: /* XXX no separate I and D spaces */
+ case PT_WRITE_D:
+ write = 1;
+ temp = data;
+ case PT_READ_I: /* XXX no separate I and D spaces */
+ case PT_READ_D:
+ /* write = 0 done above. */
+ iov.iov_base = (caddr_t)&temp;
+ iov.iov_len = sizeof(int);
+ uio.uio_iov = &iov;
+ uio.uio_iovcnt = 1;
+ uio.uio_offset = (off_t)(vaddr_t)addr;
+ uio.uio_resid = sizeof(int);
+ uio.uio_segflg = UIO_SYSSPACE;
+ uio.uio_rw = write ? UIO_WRITE : UIO_READ;
+ uio.uio_procp = p;
+ error = process_domem(p, tr, &uio, write ? PT_WRITE_I :
+ PT_READ_I);
+ if (write == 0)
+ *retval = temp;
+ return error;
+
+ case PT_IO:
+ {
+ struct ptrace_io_desc *piod = addr;
+
+ iov.iov_base = piod->piod_addr;
+ iov.iov_len = piod->piod_len;
+ uio.uio_iov = &iov;
+ uio.uio_iovcnt = 1;
+ uio.uio_offset = (off_t)(vaddr_t)piod->piod_offs;
+ uio.uio_resid = piod->piod_len;
+ uio.uio_segflg = UIO_USERSPACE;
+ uio.uio_procp = p;
+ switch (piod->piod_op) {
+ case PIOD_READ_I:
+ req = PT_READ_I;
+ uio.uio_rw = UIO_READ;
+ break;
+ case PIOD_READ_D:
+ req = PT_READ_D;
+ uio.uio_rw = UIO_READ;
+ break;
+ case PIOD_WRITE_I:
+ req = PT_WRITE_I;
+ uio.uio_rw = UIO_WRITE;
+ break;
+ case PIOD_WRITE_D:
+ req = PT_WRITE_D;
+ uio.uio_rw = UIO_WRITE;
+ break;
+ case PIOD_READ_AUXV:
+ req = PT_READ_D;
+ uio.uio_rw = UIO_READ;
+ temp = tr->ps_emul->e_arglen * sizeof(char *);
+ if (uio.uio_offset > temp)
+ return EIO;
+ if (uio.uio_resid > temp - uio.uio_offset)
+ uio.uio_resid = temp - uio.uio_offset;
+ piod->piod_len = iov.iov_len = uio.uio_resid;
+ error = process_auxv_offset(p, tr, &uio);
+ if (error)
+ return error;
+ break;
+ default:
+ return EINVAL;
}
- free(fpregs, M_TEMP, sizeof(*fpregs));
- return (error);
-#endif
-#ifdef PT_GETFPREGS
- case PT_GETFPREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
+ error = process_domem(p, tr, &uio, req);
+ piod->piod_len -= uio.uio_resid;
+ return error;
+ }
- fpregs = malloc(sizeof(*fpregs), M_TEMP, M_WAITOK);
- error = process_read_fpregs(t, fpregs);
- if (error == 0)
- error = copyout(fpregs, addr, sizeof(*fpregs));
- free(fpregs, M_TEMP, sizeof(*fpregs));
- return (error);
+ case PT_SETREGS:
+ return process_write_regs(t, addr);
+ case PT_GETREGS:
+ return process_read_regs(t, addr);
+
+#ifdef PT_SETFPREGS
+ case PT_SETFPREGS:
+ return process_write_fpregs(t, addr);
+#endif
+#ifdef PT_SETFPREGS
+ case PT_GETFPREGS:
+ return process_read_fpregs(t, addr);
#endif
#ifdef PT_SETXMMREGS
- case PT_SETXMMREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
-
- xmmregs = malloc(sizeof(*xmmregs), M_TEMP, M_WAITOK);
- error = copyin(addr, xmmregs, sizeof(*xmmregs));
- if (error == 0) {
- error = process_write_xmmregs(t, xmmregs);
- }
- free(xmmregs, M_TEMP, sizeof(*xmmregs));
- return (error);
+ case PT_SETXMMREGS:
+ return process_write_xmmregs(t, addr);
#endif
-#ifdef PT_GETXMMREGS
- case PT_GETXMMREGS:
- KASSERT((p->p_flag & P_SYSTEM) == 0);
- if ((error = process_checkioperm(p, tr)) != 0)
- return (error);
-
- xmmregs = malloc(sizeof(*xmmregs), M_TEMP, M_WAITOK);
- error = process_read_xmmregs(t, xmmregs);
- if (error == 0)
- error = copyout(xmmregs, addr, sizeof(*xmmregs));
- free(xmmregs, M_TEMP, sizeof(*xmmregs));
- return (error);
+#ifdef PT_SETXMMREGS
+ case PT_GETXMMREGS:
+ return process_read_xmmregs(t, addr);
#endif
#ifdef PT_WCOOKIE
- case PT_WCOOKIE:
- wcookie = process_get_wcookie (t);
- return (copyout(&wcookie, addr, sizeof (register_t)));
+ case PT_WCOOKIE:
+ *(register_t *)addr = process_get_wcookie(t);
+ return 0;
#endif
+ default:
+ KASSERTMSG(0, "%s: unhandled request %d", __func__, req);
+ break;
}
-#ifdef DIAGNOSTIC
- panic("ptrace: impossible");
-#endif
return 0;
}
+
+/*
+ * Helper for doing "it could be a PID or TID" lookup. On failure
+ * returns NULL; on success returns the selected process and sets *tp
+ * to an appropriate thread in that process.
+ */
+static inline struct process *
+process_tprfind(pid_t tpid, struct proc **tp)
+{
+ if (tpid > THREAD_PID_OFFSET) {
+ struct proc *t = tfind(tpid - THREAD_PID_OFFSET);
+
+ if (t == NULL)
+ return NULL;
+ *tp = t;
+ return t->p_p;
+ } else {
+ struct process *tr = prfind(tpid);
+
+ if (tr == NULL)
+ return NULL;
+ *tp = TAILQ_FIRST(&tr->ps_threads);
+ return tr;
+ }
+}
+
+
+/*
+ * Check whether 'tr' is currently traced by 'curpr' and in a state
+ * to be manipulated. If 't' is supplied then it must be stopped and
+ * waited for.
+ */
+static inline int
+process_checktracestate(struct process *curpr, struct process *tr,
+ struct proc *t)
+{
+ /*
+ * You can't do what you want to the process if:
+ * (1) It's not being traced at all,
+ */
+ if (!ISSET(tr->ps_flags, PS_TRACED))
+ return EPERM;
+
+ /*
+ * (2) it's not being traced by _you_, or
+ */
+ if (tr->ps_pptr != curpr)
+ return EBUSY;
+
+ /*
+ * (3) it's in the middle of execve(2)
+ */
+ if (ISSET(tr->ps_flags, PS_INEXEC))
+ return EAGAIN;
+
+ /*
+ * (4) if a thread was specified and it's not currently stopped.
+ */
+ if (t != NULL &&
+ (t->p_stat != SSTOP || !ISSET(tr->ps_flags, PS_WAITED)))
+ return EBUSY;
+
+ return 0;
+}
+
+
/*
* Check if a process is allowed to fiddle with the memory of another.
*
@@ -700,7 +818,7 @@ process_checkioperm(struct proc *p, struct process *tr)
if ((tr->ps_pid == 1) && (securelevel > -1))
return (EPERM);
- if (tr->ps_flags & PS_INEXEC)
+ if (ISSET(tr->ps_flags, PS_INEXEC))
return (EAGAIN);
return (0);