Changeset View
Standalone View
sys/kern/sys_process.c
Show First 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | |||||
#include <sys/priv.h> | #include <sys/priv.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
#include <sys/vnode.h> | #include <sys/vnode.h> | ||||
#include <sys/ptrace.h> | #include <sys/ptrace.h> | ||||
#include <sys/rwlock.h> | #include <sys/rwlock.h> | ||||
#include <sys/sx.h> | #include <sys/sx.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/signalvar.h> | #include <sys/signalvar.h> | ||||
#include <sys/caprights.h> | |||||
#include <sys/filedesc.h> | |||||
#include <machine/reg.h> | #include <machine/reg.h> | ||||
#include <security/audit/audit.h> | #include <security/audit/audit.h> | ||||
#include <vm/vm.h> | #include <vm/vm.h> | ||||
#include <vm/pmap.h> | #include <vm/pmap.h> | ||||
#include <vm/vm_extern.h> | #include <vm/vm_extern.h> | ||||
▲ Show 20 Lines • Show All 402 Lines • ▼ Show 20 Lines | sys_ptrace(struct thread *td, struct ptrace_args *uap) | ||||
/* | /* | ||||
* XXX this obfuscation is to reduce stack usage, but the register | * XXX this obfuscation is to reduce stack usage, but the register | ||||
* structs may be too large to put on the stack anyway. | * structs may be too large to put on the stack anyway. | ||||
*/ | */ | ||||
union { | union { | ||||
struct ptrace_io_desc piod; | struct ptrace_io_desc piod; | ||||
struct ptrace_lwpinfo pl; | struct ptrace_lwpinfo pl; | ||||
struct ptrace_vm_entry pve; | struct ptrace_vm_entry pve; | ||||
struct ptrace_coredump pc; | |||||
struct dbreg dbreg; | struct dbreg dbreg; | ||||
struct fpreg fpreg; | struct fpreg fpreg; | ||||
struct reg reg; | struct reg reg; | ||||
char args[sizeof(td->td_sa.args)]; | char args[sizeof(td->td_sa.args)]; | ||||
struct ptrace_sc_ret psr; | struct ptrace_sc_ret psr; | ||||
int ptevents; | int ptevents; | ||||
} r; | } r; | ||||
void *addr; | void *addr; | ||||
Show All 34 Lines | else | ||||
error = copyin(uap->addr, &r.ptevents, uap->data); | error = copyin(uap->addr, &r.ptevents, uap->data); | ||||
break; | break; | ||||
case PT_IO: | case PT_IO: | ||||
error = copyin(uap->addr, &r.piod, sizeof(r.piod)); | error = copyin(uap->addr, &r.piod, sizeof(r.piod)); | ||||
break; | break; | ||||
case PT_VM_ENTRY: | case PT_VM_ENTRY: | ||||
error = copyin(uap->addr, &r.pve, sizeof(r.pve)); | error = copyin(uap->addr, &r.pve, sizeof(r.pve)); | ||||
break; | break; | ||||
case PT_COREDUMP: | |||||
if (uap->data != sizeof(r.pc)) | |||||
error = EINVAL; | |||||
else | |||||
error = copyin(uap->addr, &r.pc, uap->data); | |||||
break; | |||||
default: | default: | ||||
addr = uap->addr; | addr = uap->addr; | ||||
break; | break; | ||||
} | } | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
error = kern_ptrace(td, uap->req, uap->pid, addr, uap->data); | error = kern_ptrace(td, uap->req, uap->pid, addr, uap->data); | ||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | proc_set_traced(struct proc *p, bool stop) | ||||
sx_assert(&proctree_lock, SX_XLOCKED); | sx_assert(&proctree_lock, SX_XLOCKED); | ||||
PROC_LOCK_ASSERT(p, MA_OWNED); | PROC_LOCK_ASSERT(p, MA_OWNED); | ||||
p->p_flag |= P_TRACED; | p->p_flag |= P_TRACED; | ||||
if (stop) | if (stop) | ||||
p->p_flag2 |= P2_PTRACE_FSTP; | p->p_flag2 |= P2_PTRACE_FSTP; | ||||
p->p_ptevents = PTRACE_DEFAULT; | p->p_ptevents = PTRACE_DEFAULT; | ||||
} | } | ||||
static int | |||||
proc_can_ptrace(struct thread *td, struct proc *p) | |||||
{ | |||||
int error; | |||||
PROC_LOCK_ASSERT(p, MA_OWNED); | |||||
if ((p->p_flag & P_WEXIT) != 0) | |||||
return (ESRCH); | |||||
if ((error = p_cansee(td, p)) != 0) | |||||
return (error); | |||||
if ((error = p_candebug(td, p)) != 0) | |||||
return (error); | |||||
/* not being traced... */ | |||||
if ((p->p_flag & P_TRACED) == 0) | |||||
return (EPERM); | |||||
/* not being traced by YOU */ | |||||
if (p->p_pptr != td->td_proc) | |||||
return (EBUSY); | |||||
/* not currently stopped */ | |||||
if ((p->p_flag & P_STOPPED_TRACE) == 0 || | |||||
p->p_suspcount != p->p_numthreads || | |||||
(p->p_flag & P_WAITED) == 0) | |||||
return (EBUSY); | |||||
return (0); | |||||
} | |||||
static struct thread * | |||||
ptrace_sel_coredump_thread(struct proc *p) | |||||
{ | |||||
struct thread *td2; | |||||
PROC_LOCK_ASSERT(p, MA_OWNED); | |||||
MPASS((p->p_flag & P_STOPPED_TRACE) != 0); | |||||
FOREACH_THREAD_IN_PROC(p, td2) { | |||||
if ((td2->td_dbgflags & TDB_SSWITCH) != 0) | |||||
return (td2); | |||||
} | |||||
return (NULL); | |||||
} | |||||
int | int | ||||
kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) | kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) | ||||
{ | { | ||||
struct iovec iov; | struct iovec iov; | ||||
struct uio uio; | struct uio uio; | ||||
struct proc *curp, *p, *pp; | struct proc *curp, *p, *pp; | ||||
struct thread *td2 = NULL, *td3; | struct thread *td2 = NULL, *td3; | ||||
struct ptrace_io_desc *piod = NULL; | struct ptrace_io_desc *piod = NULL; | ||||
struct ptrace_lwpinfo *pl; | struct ptrace_lwpinfo *pl; | ||||
struct ptrace_sc_ret *psr; | struct ptrace_sc_ret *psr; | ||||
struct file *fp; | |||||
struct ptrace_coredump *pc; | |||||
struct thr_coredump_req *tcq; | |||||
int error, num, tmp; | int error, num, tmp; | ||||
int proctree_locked = 0; | |||||
lwpid_t tid = 0, *buf; | lwpid_t tid = 0, *buf; | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
int wrap32 = 0, safe = 0; | int wrap32 = 0, safe = 0; | ||||
#endif | #endif | ||||
bool proctree_locked, p2_req_set; | |||||
curp = td->td_proc; | curp = td->td_proc; | ||||
proctree_locked = false; | |||||
p2_req_set = false; | |||||
/* Lock proctree before locking the process. */ | /* Lock proctree before locking the process. */ | ||||
switch (req) { | switch (req) { | ||||
case PT_TRACE_ME: | case PT_TRACE_ME: | ||||
case PT_ATTACH: | case PT_ATTACH: | ||||
case PT_STEP: | case PT_STEP: | ||||
case PT_CONTINUE: | case PT_CONTINUE: | ||||
case PT_TO_SCE: | case PT_TO_SCE: | ||||
case PT_TO_SCX: | case PT_TO_SCX: | ||||
case PT_SYSCALL: | case PT_SYSCALL: | ||||
case PT_FOLLOW_FORK: | case PT_FOLLOW_FORK: | ||||
case PT_LWP_EVENTS: | case PT_LWP_EVENTS: | ||||
case PT_GET_EVENT_MASK: | case PT_GET_EVENT_MASK: | ||||
case PT_SET_EVENT_MASK: | case PT_SET_EVENT_MASK: | ||||
case PT_DETACH: | case PT_DETACH: | ||||
case PT_GET_SC_ARGS: | case PT_GET_SC_ARGS: | ||||
sx_xlock(&proctree_lock); | sx_xlock(&proctree_lock); | ||||
proctree_locked = 1; | proctree_locked = true; | ||||
break; | break; | ||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
if (req == PT_TRACE_ME) { | if (req == PT_TRACE_ME) { | ||||
p = td->td_proc; | p = td->td_proc; | ||||
PROC_LOCK(p); | PROC_LOCK(p); | ||||
▲ Show 20 Lines • Show All 104 Lines • ▼ Show 20 Lines | #endif | ||||
case PT_CLEARSTEP: | case PT_CLEARSTEP: | ||||
/* Allow thread to clear single step for itself */ | /* Allow thread to clear single step for itself */ | ||||
if (td->td_tid == tid) | if (td->td_tid == tid) | ||||
break; | break; | ||||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||||
default: | default: | ||||
/* not being traced... */ | /* | ||||
if ((p->p_flag & P_TRACED) == 0) { | * Check for ptrace eligibility before waiting for | ||||
error = EPERM; | * holds to drain. | ||||
*/ | |||||
error = proc_can_ptrace(td, p); | |||||
if (error != 0) | |||||
goto fail; | goto fail; | ||||
} | |||||
/* not being traced by YOU */ | /* | ||||
if (p->p_pptr != td->td_proc) { | * Block parallel ptrace requests. Most important, do | ||||
error = EBUSY; | * not allow other thread in debugger to continue the | ||||
goto fail; | * debuggee until coredump finished. | ||||
*/ | |||||
while ((p->p_flag2 & P2_PTRACEREQ) != 0) { | |||||
if (proctree_locked) | |||||
markj: Should it be interruptible? | |||||
Done Inline ActionsAlso added missed wmsg kib: Also added missed wmsg | |||||
sx_xunlock(&proctree_lock); | |||||
error = msleep(&p->p_flag2, &p->p_mtx, PPAUSE | PCATCH | | |||||
(proctree_locked ? PDROP : 0), "pptrace", 0); | |||||
if (proctree_locked) { | |||||
sx_xlock(&proctree_lock); | |||||
PROC_LOCK(p); | |||||
} | } | ||||
if (error == 0) | |||||
/* not currently stopped */ | error = proc_can_ptrace(td, p); | ||||
if ((p->p_flag & P_STOPPED_TRACE) == 0 || | if (error == 0 && td2->td_proc != p) | ||||
markjUnsubmitted Done Inline ActionsWhy not check td2->td_proc != p before calling proc_can_trace()? markj: Why not check `td2->td_proc != p` before calling proc_can_trace()? | |||||
p->p_suspcount != p->p_numthreads || | error = ESRCH; | ||||
(p->p_flag & P_WAITED) == 0) { | if (error != 0) | ||||
error = EBUSY; | |||||
goto fail; | goto fail; | ||||
} | } | ||||
/* OK */ | /* Ok */ | ||||
break; | break; | ||||
} | } | ||||
/* Keep this process around until we finish this request. */ | /* | ||||
* Keep this process around and request parallel ptrace() | |||||
* request to wait until we finish this request. | |||||
Done Inline ActionsBlocking of parallel requests happens earlier. markj: Blocking of parallel requests happens earlier. | |||||
Done Inline ActionsWell, this is the place which informs other threads to block. kib: Well, this is the place which informs other threads to block. | |||||
*/ | |||||
MPASS((p->p_flag2 & P2_PTRACEREQ) == 0); | |||||
p->p_flag2 |= P2_PTRACEREQ; | |||||
p2_req_set = true; | |||||
_PHOLD(p); | _PHOLD(p); | ||||
/* | /* | ||||
* Actually do the requests | * Actually do the requests | ||||
*/ | */ | ||||
td->td_retval[0] = 0; | td->td_retval[0] = 0; | ||||
Show All 18 Lines | case PT_ATTACH: | ||||
* on a "detach". | * on a "detach". | ||||
*/ | */ | ||||
proc_set_traced(p, true); | proc_set_traced(p, true); | ||||
proc_reparent(p, td->td_proc, false); | proc_reparent(p, td->td_proc, false); | ||||
CTR2(KTR_PTRACE, "PT_ATTACH: pid %d, oppid %d", p->p_pid, | CTR2(KTR_PTRACE, "PT_ATTACH: pid %d, oppid %d", p->p_pid, | ||||
p->p_oppid); | p->p_oppid); | ||||
sx_xunlock(&proctree_lock); | sx_xunlock(&proctree_lock); | ||||
proctree_locked = 0; | proctree_locked = false; | ||||
MPASS(p->p_xthread == NULL); | MPASS(p->p_xthread == NULL); | ||||
MPASS((p->p_flag & P_STOPPED_TRACE) == 0); | MPASS((p->p_flag & P_STOPPED_TRACE) == 0); | ||||
/* | /* | ||||
* If already stopped due to a stop signal, clear the | * If already stopped due to a stop signal, clear the | ||||
* existing stop before triggering a traced SIGSTOP. | * existing stop before triggering a traced SIGSTOP. | ||||
*/ | */ | ||||
if ((p->p_flag & P_STOPPED_SIG) != 0) { | if ((p->p_flag & P_STOPPED_SIG) != 0) { | ||||
▲ Show 20 Lines • Show All 219 Lines • ▼ Show 20 Lines | case PT_DETACH: | ||||
p->p_flag2 &= ~P2_PTRACE_FSTP; | p->p_flag2 &= ~P2_PTRACE_FSTP; | ||||
} | } | ||||
/* should we send SIGCHLD? */ | /* should we send SIGCHLD? */ | ||||
/* childproc_continued(p); */ | /* childproc_continued(p); */ | ||||
break; | break; | ||||
} | } | ||||
sx_xunlock(&proctree_lock); | sx_xunlock(&proctree_lock); | ||||
Done Inline ActionsWhich case does this handle? markj: Which case does this handle? | |||||
Done Inline ActionsI think this is stray/mismerge. kib: I think this is stray/mismerge. | |||||
proctree_locked = 0; | proctree_locked = false; | ||||
sendsig: | sendsig: | ||||
MPASS(proctree_locked == 0); | MPASS(!proctree_locked); | ||||
/* | /* | ||||
* Clear the pending event for the thread that just | * Clear the pending event for the thread that just | ||||
* reported its event (p_xthread). This may not be | * reported its event (p_xthread). This may not be | ||||
* the thread passed to PT_CONTINUE, PT_STEP, etc. if | * the thread passed to PT_CONTINUE, PT_STEP, etc. if | ||||
* the debugger is resuming a different thread. | * the debugger is resuming a different thread. | ||||
* | * | ||||
* Deliver any pending signal via the reporting thread. | * Deliver any pending signal via the reporting thread. | ||||
▲ Show 20 Lines • Show All 226 Lines • ▼ Show 20 Lines | case PT_VM_TIMESTAMP: | ||||
break; | break; | ||||
case PT_VM_ENTRY: | case PT_VM_ENTRY: | ||||
PROC_UNLOCK(p); | PROC_UNLOCK(p); | ||||
error = ptrace_vm_entry(td, p, addr); | error = ptrace_vm_entry(td, p, addr); | ||||
PROC_LOCK(p); | PROC_LOCK(p); | ||||
break; | break; | ||||
case PT_COREDUMP: | |||||
pc = addr; | |||||
CTR2(KTR_PTRACE, "PT_COREDUMP: pid %d, fd %d", | |||||
p->p_pid, pc->pc_fd); | |||||
if ((pc->pc_flags & ~(PC_COMPRESS | PC_ALL)) != 0) { | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
PROC_UNLOCK(p); | |||||
tcq = malloc(sizeof(*tcq), M_TEMP, M_WAITOK | M_ZERO); | |||||
fp = NULL; | |||||
error = fget_write(td, pc->pc_fd, &cap_write_rights, &fp); | |||||
if (error != 0) | |||||
goto coredump_cleanup_nofp; | |||||
if (fp->f_type != DTYPE_VNODE || fp->f_vnode->v_type != VREG) { | |||||
Done Inline ActionsHmm, what if it is a O_PATH file? The ELF core dump code calls vn_rdwr_inchunks() to write to the vnode, I believe this bypasses the fo_write indirection. markj: Hmm, what if it is a O_PATH file? The ELF core dump code calls vn_rdwr_inchunks() to write to… | |||||
Done Inline ActionsI think fget_write() should return EBADF in this case actually. markj: I think fget_write() should return EBADF in this case actually. | |||||
Done Inline ActionsYes this was the intent, initial version lack any writeability check on fd, and fget_write() is the same method as used e.g. by write(2). kib: Yes this was the intent, initial version lack any writeability check on fd, and fget_write() is… | |||||
Done Inline ActionsIs there some fundamental reason we couldn't support dumping to a named pipe as well? It could be useful for doing compression in userspace, for instance. I am not suggesting to do it in this review. markj: Is there some fundamental reason we couldn't support dumping to a named pipe as well? It could… | |||||
Done Inline ActionsI did not inspected code to claim that we can. One easy to see reason is that we do not want a user process (reader) to be able to block the process in this state for indefinite time. This might cause cascaded debugger' hangs. kib: I did not inspected code to claim that we can.
One easy to see reason is that we do not want a… | |||||
Done Inline ActionsOk, but the debugger can decide who the reader is. One can imagine e.g., gcore that uses a named pipe to stream the core dump through a compression library, using a second thread to read from the pipe. You don't even need a named pipe for this. markj: Ok, but the debugger can decide who the reader is. One can imagine e.g., gcore that uses a… | |||||
Done Inline ActionsIt still means that user SIGSTOPping the debugger leaves debuggee in unacceptable state. Lets postpone this till real consumers appear. kib: It still means that user SIGSTOPping the debugger leaves debuggee in unacceptable state. Lets… | |||||
error = EPIPE; | |||||
goto coredump_cleanup; | |||||
} | |||||
PROC_LOCK(p); | |||||
error = proc_can_ptrace(td, p); | |||||
if (error != 0) | |||||
goto coredump_cleanup_locked; | |||||
td2 = ptrace_sel_coredump_thread(p); | |||||
if (td2 == NULL) { | |||||
error = EBUSY; | |||||
goto coredump_cleanup_locked; | |||||
} | |||||
KASSERT((td2->td_dbgflags & TDB_COREDUMPRQ) == 0, | |||||
("proc %d tid %d req coredump", p->p_pid, td2->td_tid)); | |||||
tcq->tc_vp = fp->f_vnode; | |||||
tcq->tc_limit = pc->pc_limit == 0 ? OFF_MAX : pc->pc_limit; | |||||
tcq->tc_flags = SVC_PT_COREDUMP; | |||||
if ((pc->pc_flags & PC_COMPRESS) == 0) | |||||
tcq->tc_flags |= SVC_NOCOMPRESS; | |||||
if ((pc->pc_flags & PC_ALL) != 0) | |||||
Done Inline ActionsShould we really permit PC_ALL as a non-root user? Is the reasoning that gcore can pull NOCORE pages out of the process anyway? markj: Should we really permit PC_ALL as a non-root user? Is the reasoning that gcore can pull NOCORE… | |||||
Done Inline ActionsDoes not make sense to limit it to root, since, as you notes, PT_IO allows to read the same mappings anyway. kib: Does not make sense to limit it to root, since, as you notes, PT_IO allows to read the same… | |||||
tcq->tc_flags |= SVC_ALL; | |||||
td2->td_coredump = tcq; | |||||
td2->td_dbgflags |= TDB_COREDUMPRQ; | |||||
thread_run_flash(td2); | |||||
while ((td2->td_dbgflags & TDB_COREDUMPRQ) != 0) | |||||
msleep(p, &p->p_mtx, PPAUSE, "crdmp", 0); | |||||
error = tcq->tc_error; | |||||
coredump_cleanup_locked: | |||||
PROC_UNLOCK(p); | |||||
coredump_cleanup: | |||||
Done Inline ActionsI would set td2->td_coredump = NULL here. markj: I would set `td2->td_coredump = NULL` here. | |||||
Done Inline ActionsIt is done by the victim thread, see the end of ptrace_coredump(). kib: It is done by the victim thread, see the end of ptrace_coredump(). | |||||
Done Inline ActionsOk. markj: Ok. | |||||
fdrop(fp, td); | |||||
coredump_cleanup_nofp: | |||||
free(tcq, M_TEMP); | |||||
PROC_LOCK(p); | |||||
break; | |||||
default: | default: | ||||
#ifdef __HAVE_PTRACE_MACHDEP | #ifdef __HAVE_PTRACE_MACHDEP | ||||
if (req >= PT_FIRSTMACH) { | if (req >= PT_FIRSTMACH) { | ||||
PROC_UNLOCK(p); | PROC_UNLOCK(p); | ||||
error = cpu_ptrace(td2, req, addr, data); | error = cpu_ptrace(td2, req, addr, data); | ||||
PROC_LOCK(p); | PROC_LOCK(p); | ||||
} else | } else | ||||
#endif | #endif | ||||
/* Unknown request. */ | /* Unknown request. */ | ||||
error = EINVAL; | error = EINVAL; | ||||
break; | break; | ||||
} | } | ||||
out: | out: | ||||
/* Drop our hold on this process now that the request has completed. */ | /* Drop our hold on this process now that the request has completed. */ | ||||
_PRELE(p); | _PRELE(p); | ||||
fail: | fail: | ||||
if (p2_req_set) { | |||||
if ((p->p_flag2 & P2_PTRACEREQ) != 0) | |||||
wakeup(&p->p_flag2); | |||||
p->p_flag2 &= ~P2_PTRACEREQ; | |||||
} | |||||
PROC_UNLOCK(p); | PROC_UNLOCK(p); | ||||
if (proctree_locked) | if (proctree_locked) | ||||
sx_xunlock(&proctree_lock); | sx_xunlock(&proctree_lock); | ||||
return (error); | return (error); | ||||
} | } | ||||
#undef PROC_READ | #undef PROC_READ | ||||
#undef PROC_WRITE | #undef PROC_WRITE |
Should it be interruptible?