Changeset View
Standalone View
sys/kern/sys_process.c
Show First 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | struct ptrace_vm_entry32 { | ||||
u_int pve_prot; | u_int pve_prot; | ||||
u_int pve_pathlen; | u_int pve_pathlen; | ||||
int32_t pve_fileid; | int32_t pve_fileid; | ||||
u_int pve_fsid; | u_int pve_fsid; | ||||
uint32_t pve_path; | uint32_t pve_path; | ||||
}; | }; | ||||
#endif | #endif | ||||
struct ptrace_regset { | |||||
void *prs_addr; | |||||
size_t prs_size; | |||||
}; | |||||
/* | /* | ||||
* 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)); | ||||
} | } | ||||
SET_DECLARE(elf_regset, struct regset); | |||||
static size_t | |||||
proc_regset_maxsize(int note) | |||||
{ | |||||
struct regset **regsetpp, *regsetp; | |||||
SET_FOREACH(regsetpp, elf_regset) { | |||||
regsetp = *regsetpp; | |||||
if (regsetp->note != note) | |||||
continue; | |||||
return (regsetp->size); | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
proc_read_regset(struct thread *td, int note, struct iovec *vec) | |||||
{ | |||||
struct regset **regsetpp, *regsetp; | |||||
size_t size; | |||||
int err; | |||||
SET_FOREACH(regsetpp, elf_regset) { | |||||
regsetp = *regsetpp; | |||||
if (regsetp->note != note) | |||||
continue; | |||||
err = 0; | |||||
size = vec->iov_len; | |||||
if (!regsetp->get(regsetp, td, vec->iov_base, &size)) | |||||
err = EINVAL; | |||||
KASSERT(size == vec->iov_len, | |||||
("proc_read_regset: Get function changed the size")); | |||||
return (err); | |||||
} | |||||
return (EINVAL); | |||||
} | |||||
static int | |||||
proc_write_regset(struct thread *td, int note, struct iovec *vec) | |||||
{ | |||||
struct regset **regsetpp, *regsetp; | |||||
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… | |||||
size_t size; | |||||
int err; | |||||
SET_FOREACH(regsetpp, elf_regset) { | |||||
regsetp = *regsetpp; | |||||
if (regsetp->note != note) | |||||
continue; | |||||
err = 0; | |||||
size = vec->iov_len; | |||||
if (!regsetp->set(regsetp, td, vec->iov_base, &size)) | |||||
err = EINVAL; | |||||
KASSERT(size == vec->iov_len, | |||||
("proc_write_regset: Set function changed the size")); | |||||
return (err); | |||||
} | |||||
return (EINVAL); | |||||
} | |||||
#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 All 23 Lines | |||||
proc_read_fpregs32(struct thread *td, struct fpreg32 *fpregs32) | proc_read_fpregs32(struct thread *td, struct fpreg32 *fpregs32) | ||||
{ | { | ||||
PROC_ACTION(fill_fpregs32(td, fpregs32)); | PROC_ACTION(fill_fpregs32(td, fpregs32)); | ||||
} | } | ||||
int | int | ||||
proc_write_fpregs32(struct thread *td, struct fpreg32 *fpregs32) | proc_write_fpregs32(struct thread *td, struct fpreg32 *fpregs32) | ||||
{ | { | ||||
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. | |||||
PROC_ACTION(set_fpregs32(td, fpregs32)); | PROC_ACTION(set_fpregs32(td, fpregs32)); | ||||
} | } | ||||
#endif | #endif | ||||
int | int | ||||
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`. | |||||
proc_sstep(struct thread *td) | proc_sstep(struct thread *td) | ||||
{ | { | ||||
PROC_ACTION(ptrace_single_step(td)); | PROC_ACTION(ptrace_single_step(td)); | ||||
} | } | ||||
int | int | ||||
proc_rwmem(struct proc *p, struct uio *uio) | proc_rwmem(struct proc *p, struct uio *uio) | ||||
▲ Show 20 Lines • Show All 335 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 dbreg dbreg; | struct dbreg dbreg; | ||||
struct fpreg fpreg; | struct fpreg fpreg; | ||||
struct reg reg; | struct reg reg; | ||||
struct iovec vec; | |||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
struct dbreg32 dbreg32; | struct dbreg32 dbreg32; | ||||
struct fpreg32 fpreg32; | struct fpreg32 fpreg32; | ||||
struct reg32 reg32; | struct reg32 reg32; | ||||
struct ptrace_io_desc32 piod32; | struct ptrace_io_desc32 piod32; | ||||
struct ptrace_lwpinfo32 pl32; | struct ptrace_lwpinfo32 pl32; | ||||
struct ptrace_vm_entry32 pve32; | struct ptrace_vm_entry32 pve32; | ||||
#endif | #endif | ||||
char args[sizeof(td->td_sa.args)]; | char args[sizeof(td->td_sa.args)]; | ||||
int ptevents; | int ptevents; | ||||
} r; | } r; | ||||
struct iovec kern_vec; | |||||
void *addr; | void *addr; | ||||
int error = 0; | int error = 0; | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
int wrap32 = 0; | int wrap32 = 0; | ||||
if (SV_CURPROC_FLAG(SV_ILP32)) | if (SV_CURPROC_FLAG(SV_ILP32)) | ||||
wrap32 = 1; | wrap32 = 1; | ||||
#endif | #endif | ||||
AUDIT_ARG_PID(uap->pid); | AUDIT_ARG_PID(uap->pid); | ||||
AUDIT_ARG_CMD(uap->req); | AUDIT_ARG_CMD(uap->req); | ||||
AUDIT_ARG_VALUE(uap->data); | AUDIT_ARG_VALUE(uap->data); | ||||
addr = &r; | addr = &r; | ||||
kern_vec.iov_base = NULL; | |||||
kern_vec.iov_len = 0; | |||||
switch (uap->req) { | switch (uap->req) { | ||||
case PT_GET_EVENT_MASK: | case PT_GET_EVENT_MASK: | ||||
case PT_LWPINFO: | case PT_LWPINFO: | ||||
case PT_GET_SC_ARGS: | case PT_GET_SC_ARGS: | ||||
break; | break; | ||||
case PT_GETREGS: | 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: | |||||
if (wrap32) { | |||||
/* TODO: compat32 support */ | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
error = copyin(uap->addr, &r.vec, sizeof r.vec); | |||||
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… | |||||
if (error != 0) | |||||
break; | |||||
kern_vec.iov_len = proc_regset_maxsize(uap->data); | |||||
/* Check the regset is valid and the length is correct */ | |||||
if (kern_vec.iov_len == 0 || | |||||
kern_vec.iov_len != r.vec.iov_len) { | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
kern_vec.iov_base = malloc(kern_vec.iov_len, M_TEMP, | |||||
M_WAITOK); | |||||
error = copyin(r.vec.iov_base, kern_vec.iov_base, | |||||
kern_vec.iov_len); | |||||
if (error != 0) | |||||
break; | |||||
addr = &kern_vec; | |||||
break; | |||||
case PT_GETREGSET: | |||||
if (wrap32) { | |||||
/* TODO: compat32 support */ | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
error = copyin(uap->addr, &r.vec, sizeof r.vec); | |||||
if (error != 0) | |||||
break; | |||||
kern_vec.iov_len = proc_regset_maxsize(uap->data); | |||||
if (kern_vec.iov_len == 0) { | |||||
error = EINVAL; | |||||
break; | |||||
} | |||||
/* Just return the size when the base is NULL */ | |||||
if (r.vec.iov_base == NULL) { | |||||
r.vec.iov_len = kern_vec.iov_len; | |||||
error = copyout(&r.vec, uap->addr, sizeof r.vec); | |||||
break; | |||||
} | |||||
kern_vec.iov_base = malloc(kern_vec.iov_len, M_TEMP, | |||||
M_WAITOK | M_ZERO); | |||||
addr = &kern_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); | ||||
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); | ||||
Show All 10 Lines | #endif | ||||
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; | ||||
default: | default: | ||||
addr = uap->addr; | addr = uap->addr; | ||||
break; | break; | ||||
} | } | ||||
if (error) | if (error) | ||||
return (error); | goto out; | ||||
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); | goto out; | ||||
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: | |||||
r.vec.iov_len = MIN(r.vec.iov_len, kern_vec.iov_len); | |||||
error = copyout(kern_vec.iov_base, r.vec.iov_base, | |||||
r.vec.iov_len); | |||||
if (error != 0) | |||||
break; | |||||
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; | ||||
} | } | ||||
out: | |||||
free(kern_vec.iov_base, M_TEMP); | |||||
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… | |||||
} | } | ||||
#undef COPYIN | #undef COPYIN | ||||
#undef COPYOUT | #undef COPYOUT | ||||
#undef BZERO | #undef BZERO | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
/* | /* | ||||
* PROC_READ(regs, td2, addr); | * PROC_READ(regs, td2, addr); | ||||
▲ Show 20 Lines • Show All 208 Lines • ▼ Show 20 Lines | default: | ||||
} | } | ||||
/* OK */ | /* OK */ | ||||
break; | break; | ||||
} | } | ||||
/* Keep this process around until we finish this request. */ | /* Keep this process around until we finish this request. */ | ||||
_PHOLD(p); | _PHOLD(p); | ||||
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… | |||||
#ifdef FIX_SSTEP | #ifdef FIX_SSTEP | ||||
/* | /* | ||||
* Single step fixup ala procfs | * Single step fixup ala procfs | ||||
*/ | */ | ||||
FIX_SSTEP(td2); | FIX_SSTEP(td2); | ||||
#endif | #endif | ||||
/* | /* | ||||
▲ Show 20 Lines • Show All 408 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 || | if (data <= 0 || | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
(!wrap32 && data > sizeof(*pl)) || | (!wrap32 && data > sizeof(*pl)) || | ||||
(wrap32 && data > sizeof(*pl32))) { | (wrap32 && data > sizeof(*pl32))) { | ||||
#else | #else | ||||
▲ Show 20 Lines • Show All 168 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)