Changeset View
Standalone View
sys/kern/sys_process.c
Show First 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | |||||
#include <vm/vm_object.h> | #include <vm/vm_object.h> | ||||
#include <vm/vm_page.h> | #include <vm/vm_page.h> | ||||
#include <vm/vm_param.h> | #include <vm/vm_param.h> | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
#include <sys/procfs.h> | #include <sys/procfs.h> | ||||
#endif | #endif | ||||
/* Assert it's safe to unlock a process, e.g. to allocate working memory */ | |||||
#define PROC_ASSERT_TRACEREQ(p) MPASS(((p)->p_flag2 & P2_PTRACEREQ) != 0) | |||||
/* | /* | ||||
* Functions implemented using PROC_ACTION(): | * Functions implemented using PROC_ACTION(): | ||||
* | * | ||||
* proc_read_regs(proc, regs) | * proc_read_regs(proc, regs) | ||||
* Get the current user-visible register set from the process | * Get the current user-visible register set from the process | ||||
* and copy it into the regs structure (<machine/reg.h>). | * and copy it into the regs structure (<machine/reg.h>). | ||||
* The process is stopped at the time read_regs is called. | * The process is stopped at the time read_regs is called. | ||||
* | * | ||||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | |||||
int | int | ||||
proc_write_fpregs(struct thread *td, struct fpreg *fpregs) | proc_write_fpregs(struct thread *td, struct fpreg *fpregs) | ||||
{ | { | ||||
PROC_ACTION(set_fpregs(td, fpregs)); | PROC_ACTION(set_fpregs(td, fpregs)); | ||||
} | } | ||||
static struct regset * | |||||
proc_find_regset(struct thread *td, int note) | |||||
{ | |||||
struct regset **regsetp, **regset_end, *regset; | |||||
struct sysentvec *sv; | |||||
sv = td->td_proc->p_sysent; | |||||
regsetp = sv->sv_regset_begin; | |||||
if (regsetp == NULL) | |||||
return (NULL); | |||||
regset_end = sv->sv_regset_end; | |||||
MPASS(regset_end != NULL); | |||||
for (; regsetp < regset_end; regsetp++) { | |||||
regset = *regsetp; | |||||
if (regset->note != note) | |||||
continue; | |||||
return (regset); | |||||
} | |||||
return (NULL); | |||||
} | |||||
static int | |||||
proc_read_regset(struct thread *td, int note, struct iovec *iov) | |||||
{ | |||||
struct regset *regset; | |||||
struct proc *p; | |||||
void *buf; | |||||
size_t size; | |||||
int error; | |||||
regset = proc_find_regset(td, note); | |||||
if (regset == NULL) | |||||
return (EINVAL); | |||||
if (iov->iov_base == NULL) { | |||||
iov->iov_len = regset->size; | |||||
if (iov->iov_len == 0) | |||||
return (EINVAL); | |||||
return (0); | |||||
} | |||||
/* The length is wrong, return an error */ | |||||
jhb: We may consider in the future removing this check and always passing down 'vec->iov_len' as the… | |||||
Done Inline ActionsThe intention is for userspace to query the size by passing in a NULL base and for it to handle data that's not the size it was expecting, e.g. new data was added to the kernel userspace doesn't expect. I have an initial patch for lldb to use this interface on arm64, so will make sure it can handle incorrectly sized data. andrew: The intention is for userspace to query the size by passing in a NULL base and for it to handle… | |||||
Not Done Inline ActionsI don't know that userland is capable of handling forwards compatibility in that way unless we promise userland that if a register set grows beyond a a size it expects, the original N bytes it understands still have the same meaning. OTOH, if the kernel is the one to manage compatibility, it will know what the old format an old application might expect is and be able to read/write the old format. This is generally consistent with what we do with other compat shims where the kernel is the one that deals with this, not applications. However, perhaps we won't ever grow NT_PRSTATUS and instead just need to handle the variable-sized cases like XSAVE and SVE where the register set size is not a compile-time constant. jhb: I don't know that userland is capable of handling forwards compatibility in that way unless we… | |||||
Done Inline ActionsWouldn't userspace already have to handle it when reading a coredump? I was testing on Linux to see what they expose & found it to be just the registers. You can ask for any length that is a multiple of the base register size, e.g. on amd64 it is 27 * 8 byte registers, so you could pass in any multiple of 8 & get up to 216 bytes of register data back. andrew: Wouldn't userspace already have to handle it when reading a coredump?
I was testing on Linux… | |||||
if (iov->iov_len != regset->size) | |||||
return (EINVAL); | |||||
if (regset->get == NULL) | |||||
return (EINVAL); | |||||
error = 0; | |||||
size = regset->size; | |||||
p = td->td_proc; | |||||
/* Drop the proc lock while allocating the temp buffer */ | |||||
PROC_ASSERT_TRACEREQ(p); | |||||
PROC_UNLOCK(p); | |||||
buf = malloc(size, M_TEMP, M_WAITOK); | |||||
PROC_LOCK(p); | |||||
if (!regset->get(regset, td, buf, &size)) { | |||||
error = EINVAL; | |||||
} else { | |||||
KASSERT(size == regset->size, | |||||
("%s: Getter function changed the size", __func__)); | |||||
iov->iov_len = size; | |||||
PROC_UNLOCK(p); | |||||
error = copyout(buf, iov->iov_base, size); | |||||
PROC_LOCK(p); | |||||
} | |||||
free(buf, M_TEMP); | |||||
return (error); | |||||
} | |||||
static int | |||||
proc_write_regset(struct thread *td, int note, struct iovec *iov) | |||||
{ | |||||
struct regset *regset; | |||||
struct proc *p; | |||||
void *buf; | |||||
size_t size; | |||||
int error; | |||||
regset = proc_find_regset(td, note); | |||||
if (regset == NULL) | |||||
return (EINVAL); | |||||
/* The length is wrong, return an error */ | |||||
if (iov->iov_len != regset->size) | |||||
return (EINVAL); | |||||
if (regset->set == NULL) | |||||
return (EINVAL); | |||||
size = regset->size; | |||||
p = td->td_proc; | |||||
/* Drop the proc lock while allocating the temp buffer */ | |||||
PROC_ASSERT_TRACEREQ(p); | |||||
PROC_UNLOCK(p); | |||||
buf = malloc(size, M_TEMP, M_WAITOK); | |||||
Not Done Inline ActionsI am not sure this is safe. At very least, you could loose the rights to debug the target. Since some time, we block parallel ptrace(2) requests for the same process, so I do not think that PT_DETACH could occur. But e.g. thread could be reassigned to different process etc. kib: I am not sure this is safe. At very least, you could loose the rights to debug the target. | |||||
error = copyin(iov->iov_base, buf, size); | |||||
PROC_LOCK(p); | |||||
if (error == 0) { | |||||
if (!regset->set(regset, td, buf, size)) { | |||||
error = EINVAL; | |||||
Not Done Inline ActionsThis KASSERT() can be removed since regset->set() doesn't change size. jhb: This KASSERT() can be removed since `regset->set()` doesn't change `size`. | |||||
} | |||||
} | |||||
free(buf, M_TEMP); | |||||
return (error); | |||||
} | |||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
/* For 32 bit binaries, we need to expose the 32 bit regs layouts. */ | /* For 32 bit binaries, we need to expose the 32 bit regs layouts. */ | ||||
int | int | ||||
proc_read_regs32(struct thread *td, struct reg32 *regs32) | proc_read_regs32(struct thread *td, struct reg32 *regs32) | ||||
{ | { | ||||
PROC_ACTION(fill_regs32(td, regs32)); | PROC_ACTION(fill_regs32(td, regs32)); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 305 Lines • ▼ Show 20 Lines | sys_ptrace(struct thread *td, struct ptrace_args *uap) | ||||
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 ptrace_coredump pc; | ||||
struct dbreg dbreg; | struct dbreg dbreg; | ||||
struct fpreg fpreg; | struct fpreg fpreg; | ||||
struct reg reg; | struct reg reg; | ||||
struct iovec vec; | |||||
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; | ||||
int error; | int error; | ||||
if (!allow_ptrace) | if (!allow_ptrace) | ||||
Show All 14 Lines | case PT_GETREGS: | ||||
bzero(&r.reg, sizeof(r.reg)); | bzero(&r.reg, sizeof(r.reg)); | ||||
break; | break; | ||||
case PT_GETFPREGS: | case PT_GETFPREGS: | ||||
bzero(&r.fpreg, sizeof(r.fpreg)); | bzero(&r.fpreg, sizeof(r.fpreg)); | ||||
break; | break; | ||||
case PT_GETDBREGS: | case PT_GETDBREGS: | ||||
bzero(&r.dbreg, sizeof(r.dbreg)); | bzero(&r.dbreg, sizeof(r.dbreg)); | ||||
break; | break; | ||||
case PT_SETREGSET: | |||||
error = copyin(uap->addr, &r.vec, sizeof(r.vec)); | |||||
break; | |||||
case PT_GETREGSET: | |||||
error = copyin(uap->addr, &r.vec, sizeof(r.vec)); | |||||
break; | |||||
case PT_SETREGS: | case PT_SETREGS: | ||||
error = copyin(uap->addr, &r.reg, sizeof(r.reg)); | error = copyin(uap->addr, &r.reg, sizeof(r.reg)); | ||||
Not Done Inline Actionsstyle(9) is to use ()'s with sizeof jhb: style(9) is to use ()'s with sizeof | |||||
Done Inline ActionsThis is mostly to be consistent with the style in the switch. I'm planning on adding COMPAT32 support, but will need to add an iovec32 first. This will need this non-style sizeof. andrew: This is mostly to be consistent with the style in the switch. I'm planning on adding COMPAT32… | |||||
break; | break; | ||||
case PT_SETFPREGS: | case PT_SETFPREGS: | ||||
error = copyin(uap->addr, &r.fpreg, sizeof(r.fpreg)); | error = copyin(uap->addr, &r.fpreg, sizeof(r.fpreg)); | ||||
break; | break; | ||||
case PT_SETDBREGS: | case PT_SETDBREGS: | ||||
error = copyin(uap->addr, &r.dbreg, sizeof(r.dbreg)); | error = copyin(uap->addr, &r.dbreg, sizeof(r.dbreg)); | ||||
break; | break; | ||||
case PT_SET_EVENT_MASK: | case PT_SET_EVENT_MASK: | ||||
Show All 18 Lines | 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); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
Not Done Inline ActionsPerhaps use 'goto out' here instead? jhb: Perhaps use 'goto out' here instead? | |||||
switch (uap->req) { | switch (uap->req) { | ||||
case PT_VM_ENTRY: | case PT_VM_ENTRY: | ||||
error = copyout(&r.pve, uap->addr, sizeof(r.pve)); | error = copyout(&r.pve, uap->addr, sizeof(r.pve)); | ||||
break; | break; | ||||
case PT_IO: | case PT_IO: | ||||
error = copyout(&r.piod, uap->addr, sizeof(r.piod)); | error = copyout(&r.piod, uap->addr, sizeof(r.piod)); | ||||
break; | break; | ||||
case PT_GETREGS: | case PT_GETREGS: | ||||
error = copyout(&r.reg, uap->addr, sizeof(r.reg)); | error = copyout(&r.reg, uap->addr, sizeof(r.reg)); | ||||
break; | break; | ||||
case PT_GETFPREGS: | case PT_GETFPREGS: | ||||
error = copyout(&r.fpreg, uap->addr, sizeof(r.fpreg)); | error = copyout(&r.fpreg, uap->addr, sizeof(r.fpreg)); | ||||
break; | break; | ||||
case PT_GETDBREGS: | case PT_GETDBREGS: | ||||
error = copyout(&r.dbreg, uap->addr, sizeof(r.dbreg)); | error = copyout(&r.dbreg, uap->addr, sizeof(r.dbreg)); | ||||
break; | break; | ||||
case PT_GETREGSET: | |||||
error = copyout(&r.vec, uap->addr, sizeof(r.vec)); | |||||
break; | |||||
case PT_GET_EVENT_MASK: | case PT_GET_EVENT_MASK: | ||||
/* NB: The size in uap->data is validated in kern_ptrace(). */ | /* NB: The size in uap->data is validated in kern_ptrace(). */ | ||||
error = copyout(&r.ptevents, uap->addr, uap->data); | error = copyout(&r.ptevents, uap->addr, uap->data); | ||||
break; | break; | ||||
case PT_LWPINFO: | case PT_LWPINFO: | ||||
/* NB: The size in uap->data is validated in kern_ptrace(). */ | /* NB: The size in uap->data is validated in kern_ptrace(). */ | ||||
error = copyout(&r.pl, uap->addr, uap->data); | error = copyout(&r.pl, uap->addr, uap->data); | ||||
break; | break; | ||||
case PT_GET_SC_ARGS: | case PT_GET_SC_ARGS: | ||||
error = copyout(r.args, uap->addr, MIN(uap->data, | error = copyout(r.args, uap->addr, MIN(uap->data, | ||||
sizeof(r.args))); | sizeof(r.args))); | ||||
break; | break; | ||||
case PT_GET_SC_RET: | case PT_GET_SC_RET: | ||||
error = copyout(&r.psr, uap->addr, MIN(uap->data, | error = copyout(&r.psr, uap->addr, MIN(uap->data, | ||||
sizeof(r.psr))); | sizeof(r.psr))); | ||||
break; | break; | ||||
} | } | ||||
return (error); | return (error); | ||||
Not Done Inline ActionsAnd move the free of kern_vec.iov_base to here under an 'out' label. This fixes a leak when the first copyout fails. It will also make adding other functions that use an iovec simpler by consolidating the error handling. jhb: And move the free of kern_vec.iov_base to here under an 'out' label. This fixes a leak when… | |||||
} | } | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
/* | /* | ||||
* PROC_READ(regs, td2, addr); | * PROC_READ(regs, td2, addr); | ||||
* becomes either: | * becomes either: | ||||
* proc_read_regs(td2, addr); | * proc_read_regs(td2, addr); | ||||
* or | * or | ||||
▲ Show 20 Lines • Show All 277 Lines • ▼ Show 20 Lines | default: | ||||
} | } | ||||
/* Ok */ | /* Ok */ | ||||
break; | break; | ||||
} | } | ||||
/* | /* | ||||
* Keep this process around and request parallel ptrace() | * Keep this process around and request parallel ptrace() | ||||
* request to wait until we finish this request. | * request to wait until we finish this request. | ||||
Not Done Inline ActionsI do not understand this. proc_read/write_regset_alloc_iov() call malloc(M_WAITOK), while the process lock is owned. kib: I do not understand this. proc_read/write_regset_alloc_iov() call malloc(M_WAITOK), while the… | |||||
*/ | */ | ||||
MPASS((p->p_flag2 & P2_PTRACEREQ) == 0); | MPASS((p->p_flag2 & P2_PTRACEREQ) == 0); | ||||
p->p_flag2 |= P2_PTRACEREQ; | p->p_flag2 |= P2_PTRACEREQ; | ||||
p2_req_set = true; | p2_req_set = true; | ||||
_PHOLD(p); | _PHOLD(p); | ||||
/* | /* | ||||
* Actually do the requests | * Actually do the requests | ||||
▲ Show 20 Lines • Show All 402 Lines • ▼ Show 20 Lines | case PT_SETDBREGS: | ||||
td2->td_dbgflags |= TDB_USERWR; | td2->td_dbgflags |= TDB_USERWR; | ||||
error = PROC_WRITE(dbregs, td2, addr); | error = PROC_WRITE(dbregs, td2, addr); | ||||
break; | break; | ||||
case PT_GETDBREGS: | case PT_GETDBREGS: | ||||
CTR2(KTR_PTRACE, "PT_GETDBREGS: tid %d (pid %d)", td2->td_tid, | CTR2(KTR_PTRACE, "PT_GETDBREGS: tid %d (pid %d)", td2->td_tid, | ||||
p->p_pid); | p->p_pid); | ||||
error = PROC_READ(dbregs, td2, addr); | error = PROC_READ(dbregs, td2, addr); | ||||
break; | |||||
case PT_SETREGSET: | |||||
CTR2(KTR_PTRACE, "PT_SETREGSET: tid %d (pid %d)", td2->td_tid, | |||||
p->p_pid); | |||||
error = proc_write_regset(td2, data, addr); | |||||
break; | |||||
case PT_GETREGSET: | |||||
CTR2(KTR_PTRACE, "PT_GETREGSET: tid %d (pid %d)", td2->td_tid, | |||||
p->p_pid); | |||||
error = proc_read_regset(td2, data, addr); | |||||
break; | break; | ||||
case PT_LWPINFO: | case PT_LWPINFO: | ||||
if (data <= 0 || data > sizeof(*pl)) { | if (data <= 0 || data > sizeof(*pl)) { | ||||
error = EINVAL; | error = EINVAL; | ||||
break; | break; | ||||
} | } | ||||
pl = addr; | pl = addr; | ||||
▲ Show 20 Lines • Show All 176 Lines • Show Last 20 Lines |
We may consider in the future removing this check and always passing down 'vec->iov_len' as the size. This would let us grow an existing register set while still supporting the old size for compatibility. (Examples to consider here are fs_base/gs_base which Linux added to NT_PRSTATUS, or if we wanted to expand NT_PRSTATUS on arm64 to include the TLS register.) In practice I think we will just add new regsets for missing registers instead, but it's a thought. (fs_base and gs_base are kind of odd as we have ptrace ops for them today, but we don't include them in core dumps yet)