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) | |||||
{ | |||||
PROC_LOCK_ASSERT(p, MA_OWNED); | |||||
if ((p->p_flag & P_WEXIT) != 0) | |||||
return (ESRCH); | |||||
/* 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; | 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 | ||||
curp = td->td_proc; | curp = td->td_proc; | ||||
▲ Show 20 Lines • Show All 130 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 | ||||
* debuggee until coredump finished. | |||||
*/ | |||||
while (p->p_lock > 0) { | |||||
msleep(&p->p_lock, &p->p_mtx, PPAUSE, "", 0); | |||||
markj: Should it be interruptible? | |||||
Done Inline ActionsAlso added missed wmsg kib: Also added missed wmsg | |||||
error = proc_can_ptrace(td, p); | |||||
if (error != 0) | |||||
goto fail; | goto fail; | ||||
} | } | ||||
/* not currently stopped */ | /* Ok */ | ||||
if ((p->p_flag & P_STOPPED_TRACE) == 0 || | |||||
p->p_suspcount != p->p_numthreads || | |||||
(p->p_flag & P_WAITED) == 0) { | |||||
error = EBUSY; | |||||
goto fail; | |||||
} | |||||
/* OK */ | |||||
break; | break; | ||||
} | } | ||||
/* Keep this process around until we finish this request. */ | /* | ||||
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()? | |||||
* Keep this process around and block parallel ptrace() | |||||
* request 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. | |||||
*/ | |||||
_PHOLD(p); | _PHOLD(p); | ||||
/* | /* | ||||
* Actually do the requests | * Actually do the requests | ||||
*/ | */ | ||||
td->td_retval[0] = 0; | td->td_retval[0] = 0; | ||||
▲ Show 20 Lines • Show All 254 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; | ||||
} | } | ||||
if (error != 0) | |||||
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. | |||||
break; | |||||
sx_xunlock(&proctree_lock); | sx_xunlock(&proctree_lock); | ||||
proctree_locked = 0; | proctree_locked = 0; | ||||
sendsig: | sendsig: | ||||
MPASS(proctree_locked == 0); | MPASS(proctree_locked == 0); | ||||
/* | /* | ||||
* Clear the pending event for the thread that just | * Clear the pending event for the thread that just | ||||
▲ Show 20 Lines • Show All 231 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)) != 0) { | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
PROC_UNLOCK(p); | |||||
tcq = malloc(sizeof(*tcq), M_TEMP, M_WAITOK | M_ZERO); | |||||
fp = NULL; | |||||
error = getvnode(td, pc->pc_fd, &cap_write_rights, &fp); | |||||
if (error != 0 || fp->f_vnode->v_type != VREG) { | |||||
error = EINVAL; | |||||
goto coredump_cleanup; | |||||
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… | |||||
} | |||||
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; | |||||
if ((pc->pc_flags & PC_COMPRESS) == 0) | |||||
tcq->tc_flags |= SVC_NOCOMPRESS; | |||||
td2->td_coredump = tcq; | |||||
td2->td_dbgflags |= TDB_COREDUMPRQ; | |||||
if (thread_run_flash(td2)) { | |||||
PROC_UNLOCK(p); | |||||
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… | |||||
kick_proc0(); | |||||
PROC_LOCK(p); | |||||
} | |||||
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: | |||||
if (fp != NULL) | |||||
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); | |||||
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: | ||||
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?