Implement x86 ptrace(2) requests PT_{GET,SET}{FS,GS}BASE.

MFC r284918:
Add helper fill_based_sd(9).

MFC r284919:
Add x86 PT_GETFSBASE, PT_GETGSBASE machine-depended ptrace requests to
obtain the thread %fs and %gs bases.  Add x86 PT_SETFSBASE and
PT_SETGSBASE requests to set the bases from debuggers.  The set
requests, similarly to the sysarch({I386,AMD64}_SET_FSBASE), override
the corresponding segment registers.

MFC r284965:
Document x86 machine-specific ptrace(2) requests.

MFC r285011:
Disallow a debugger on 64bit system to set fs/gs bases of the 32bit
process beyond the end of the process address space.

MFC r285104:
Grammar and language fixes.
This commit is contained in:
kib 2015-08-05 08:17:10 +00:00
parent 13079235af
commit 83f30eda37
6 changed files with 333 additions and 37 deletions

View File

@ -2,7 +2,7 @@
.\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $
.\"
.\" This file is in the public domain.
.Dd July 22, 2013
.Dd July 3, 2015
.Dt PTRACE 2
.Os
.Sh NAME
@ -503,8 +503,163 @@ The
.Fa data
argument is ignored.
.El
.Sh x86 MACHINE-SPECIFIC REQUESTS
.Bl -tag -width "Dv PT_GETXSTATE_INFO"
.It Dv PT_GETXMMREGS
Copy the XMM FPU state into the buffer pointed to by the
argument
.Fa addr .
The buffer has the same layout as the 32-bit save buffer for the
machine instruction
.Dv FXSAVE .
.Pp
Additionally, machine-specific requests can exist.
This request is only valid for i386 programs, both on native 32-bit
systems and on amd64 kernels.
For 64-bit amd64 programs, the XMM state is reported as part of
the FPU state returned by the
.Dv PT_GETFPREGS
request.
.Pp
The
.Fa data
argument is ignored.
.It Dv PT_SETXMMREGS
Load the XMM FPU state for the thread from the buffer pointed to
by the argument
.Fa addr .
The buffer has the same layout as the 32-bit load buffer for the
machine instruction
.Dv FXRSTOR .
.Pp
As with
.Dv PT_GETXMMREGS,
this request is only valid for i386 programs.
.Pp
The
.Fa data
argument is ignored.
.It Dv PT_GETXSTATE_INFO
Report which XSAVE FPU extensions are supported by the CPU
and allowed in userspace programs.
The
.Fa addr
argument must point to a variable of type
.Vt struct ptrace_xstate_info ,
which contains the information on the request return.
.Vt struct ptrace_xstate_info
is defined as follows:
.Bd -literal
struct ptrace_xstate_info {
uint64_t xsave_mask;
uint32_t xsave_len;
};
.Ed
The
.Dv xsave_mask
field is a bitmask of the currently enabled extensions.
The meaning of the bits is defined in the Intel and AMD
processor documentation.
The
.Dv xsave_len
field reports the length of the XSAVE area for storing the hardware
state for currently enabled extensions in the format defined by the x86
.Dv XSAVE
machine instruction.
.Pp
The
.Fa data
argument value must be equal to the size of the
.Vt struct ptrace_xstate_info .
.It Dv PT_GETXSTATE
Return the content of the XSAVE area for the thread.
The
.Fa addr
argument points to the buffer where the content is copied, and the
.Fa data
argument specifies the size of the buffer.
The kernel copies out as much content as allowed by the buffer size.
The buffer layout is specified by the layout of the save area for the
.Dv XSAVE
machine instruction.
.It Dv PT_SETXSTATE
Load the XSAVE state for the thread from the buffer specified by the
.Fa addr
pointer.
The buffer size is passed in the
.Fa data
argument.
The buffer must be at least as large as the
.Vt struct savefpu
(defined in
.Pa x86/fpu.h )
to allow the complete x87 FPU and XMM state load.
It must not be larger than the XSAVE state length, as reported by the
.Dv xsave_len
field from the
.Vt struct ptrace_xstate_info
of the
.Dv PT_GETXSTATE_INFO
request.
Layout of the buffer is identical to the layout of the load area for the
.Dv XRSTOR
machine instruction.
.It Dv PT_GETFSBASE
Return the value of the base used when doing segmented
memory addressing using the %fs segment register.
The
.Fa addr
argument points to an
.Vt unsigned long
variable where the base value is stored.
.Pp
The
.Fa data
argument is ignored.
.It Dv PT_GETGSBASE
Like the
.Dv PT_GETFSBASE
request, but returns the base for the %gs segment register.
.It Dv PT_SETFSBASE
Set the base for the %fs segment register to the value pointed to
by the
.Fa addr
argument.
.Fa addr
must point to the
.Vt unsigned long
variable containing the new base.
.Pp
The
.Fa data
argument is ignored.
.It Dv PT_SETGSBASE
Like the
.Dv PT_SETFSBASE
request, but sets the base for the %gs segment register.
.El
.Sh PowerPC MACHINE-SPECIFIC REQUESTS
.Bl -tag -width "Dv PT_SETVRREGS"
.It Dv PT_GETVRREGS
Return the thread's
.Dv ALTIVEC
machine state in the buffer pointed to by
.Fa addr .
.Pp
The
.Fa data
argument is ignored.
.It Dv PT_SETVRREGS
Set the thread's
.Dv ALTIVEC
machine state from the buffer pointed to by
.Fa addr .
.Pp
The
.Fa data
argument is ignored.
.El
.Pp
Additionally, other machine-specific requests can exist.
.Sh RETURN VALUES
Some requests can cause
.Fn ptrace
@ -564,6 +719,38 @@ provided to
was less than or equal to zero, or larger than the
.Vt ptrace_lwpinfo
structure known to the kernel.
.It
The size (in
.Fa data )
provided to the x86-specific
.Dv PT_GETXSTATE_INFO
request was not equal to the size of the
.Vt struct ptrace_xstate_info .
.It
The size (in
.Fa data )
provided to the x86-specific
.Dv PT_SETXSTATE
request was less than the size of the x87 plus the XMM save area.
.It
The size (in
.Fa data )
provided to the x86-specific
.Dv PT_SETXSTATE
request was larger than returned in the
.Dv xsave_len
member of the
.Vt struct ptrace_xstate_info
from the
.Dv PT_GETXSTATE_INFO
request.
.It
The base value, provided to the amd64-specific requests
.Dv PT_SETFSBASE
or
.Dv PT_SETGSBASE ,
pointed outside of the valid user address space.
This error will not occur in 32-bit programs.
.El
.It Bq Er EBUSY
.Bl -bullet -compact

View File

@ -36,8 +36,12 @@ __FBSDID("$FreeBSD$");
#include <sys/proc.h>
#include <sys/ptrace.h>
#include <sys/sysent.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/md_var.h>
#include <machine/pcb.h>
#include <machine/frame.h>
#include <machine/vmparam.h>
static int
cpu_ptrace_xstate(struct thread *td, int req, void *addr, int data)
@ -110,6 +114,20 @@ cpu_ptrace_xstate(struct thread *td, int req, void *addr, int data)
return (error);
}
static void
cpu_ptrace_setbase(struct thread *td, int req, register_t r)
{
if (req == PT_SETFSBASE) {
td->td_pcb->pcb_fsbase = r;
td->td_frame->tf_fs = _ufssel;
} else {
td->td_pcb->pcb_gsbase = r;
td->td_frame->tf_gs = _ugssel;
}
set_pcb_flags(td->td_pcb, PCB_FULL_IRET);
}
#ifdef COMPAT_FREEBSD32
#define PT_I386_GETXMMREGS (PT_FIRSTMACH + 0)
#define PT_I386_SETXMMREGS (PT_FIRSTMACH + 1)
@ -118,6 +136,7 @@ static int
cpu32_ptrace(struct thread *td, int req, void *addr, int data)
{
struct savefpu *fpstate;
uint32_t r;
int error;
switch (req) {
@ -142,6 +161,29 @@ cpu32_ptrace(struct thread *td, int req, void *addr, int data)
error = cpu_ptrace_xstate(td, req, addr, data);
break;
case PT_GETFSBASE:
case PT_GETGSBASE:
if (!SV_PROC_FLAG(td->td_proc, SV_ILP32)) {
error = EINVAL;
break;
}
r = req == PT_GETFSBASE ? td->td_pcb->pcb_fsbase :
td->td_pcb->pcb_gsbase;
error = copyout(&r, addr, sizeof(r));
break;
case PT_SETFSBASE:
case PT_SETGSBASE:
if (!SV_PROC_FLAG(td->td_proc, SV_ILP32)) {
error = EINVAL;
break;
}
error = copyin(addr, &r, sizeof(r));
if (error != 0)
break;
cpu_ptrace_setbase(td, req, r);
break;
default:
error = EINVAL;
break;
@ -154,6 +196,7 @@ cpu32_ptrace(struct thread *td, int req, void *addr, int data)
int
cpu_ptrace(struct thread *td, int req, void *addr, int data)
{
register_t *r, rv;
int error;
#ifdef COMPAT_FREEBSD32
@ -176,6 +219,25 @@ cpu_ptrace(struct thread *td, int req, void *addr, int data)
error = cpu_ptrace_xstate(td, req, addr, data);
break;
case PT_GETFSBASE:
case PT_GETGSBASE:
r = req == PT_GETFSBASE ? &td->td_pcb->pcb_fsbase :
&td->td_pcb->pcb_gsbase;
error = copyout(r, addr, sizeof(*r));
break;
case PT_SETFSBASE:
case PT_SETGSBASE:
error = copyin(addr, &rv, sizeof(rv));
if (error != 0)
break;
if (rv >= td->td_proc->p_sysent->sv_maxuser) {
error = EINVAL;
break;
}
cpu_ptrace_setbase(td, req, rv);
break;
default:
error = EINVAL;
break;

View File

@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/ptrace.h>
#include <machine/frame.h>
#include <machine/md_var.h>
#include <machine/pcb.h>
@ -115,8 +116,8 @@ cpu_ptrace_xstate(struct thread *td, int req, void *addr, int data)
}
#endif
int
cpu_ptrace(struct thread *td, int req, void *addr, int data)
static int
cpu_ptrace_xmm(struct thread *td, int req, void *addr, int data)
{
#ifdef CPU_ENABLE_SSE
struct savexmm *fpstate;
@ -155,3 +156,51 @@ cpu_ptrace(struct thread *td, int req, void *addr, int data)
return (EINVAL);
#endif
}
int
cpu_ptrace(struct thread *td, int req, void *addr, int data)
{
struct segment_descriptor *sdp, sd;
register_t r;
int error;
switch (req) {
case PT_GETXMMREGS:
case PT_SETXMMREGS:
case PT_GETXSTATE_OLD:
case PT_SETXSTATE_OLD:
case PT_GETXSTATE_INFO:
case PT_GETXSTATE:
case PT_SETXSTATE:
error = cpu_ptrace_xmm(td, req, addr, data);
break;
case PT_GETFSBASE:
case PT_GETGSBASE:
sdp = req == PT_GETFSBASE ? &td->td_pcb->pcb_fsd :
&td->td_pcb->pcb_gsd;
r = sdp->sd_hibase << 24 | sdp->sd_lobase;
error = copyout(&r, addr, sizeof(r));
break;
case PT_SETFSBASE:
case PT_SETGSBASE:
error = copyin(addr, &r, sizeof(r));
if (error != 0)
break;
fill_based_sd(&sd, r);
if (req == PT_SETFSBASE) {
td->td_pcb->pcb_fsd = sd;
td->td_frame->tf_fs = GSEL(GUFS_SEL, SEL_UPL);
} else {
td->td_pcb->pcb_gsd = sd;
td->td_pcb->pcb_gs = GSEL(GUGS_SEL, SEL_UPL);
}
break;
default:
return (EINVAL);
}
return (error);
}

View File

@ -88,6 +88,27 @@ static int i386_set_ldt_data(struct thread *, int start, int num,
union descriptor *descs);
static int i386_ldt_grow(struct thread *td, int len);
void
fill_based_sd(struct segment_descriptor *sdp, uint32_t base)
{
sdp->sd_lobase = base & 0xffffff;
sdp->sd_hibase = (base >> 24) & 0xff;
#ifdef XEN
/* need to do nosegneg like Linux */
sdp->sd_lolimit = (HYPERVISOR_VIRT_START >> 12) & 0xffff;
#else
sdp->sd_lolimit = 0xffff; /* 4GB limit, wraps around */
#endif
sdp->sd_hilimit = 0xf;
sdp->sd_type = SDT_MEMRWA;
sdp->sd_dpl = SEL_UPL;
sdp->sd_p = 1;
sdp->sd_xx = 0;
sdp->sd_def32 = 1;
sdp->sd_gran = 1;
}
#ifndef _SYS_SYSPROTO_H_
struct sysarch_args {
int op;
@ -202,28 +223,14 @@ sysarch(td, uap)
break;
case I386_SET_FSBASE:
error = copyin(uap->parms, &base, sizeof(base));
if (!error) {
if (error == 0) {
/*
* Construct a descriptor and store it in the pcb for
* the next context switch. Also store it in the gdt
* so that the load of tf_fs into %fs will activate it
* at return to userland.
*/
sd.sd_lobase = base & 0xffffff;
sd.sd_hibase = (base >> 24) & 0xff;
#ifdef XEN
/* need to do nosegneg like Linux */
sd.sd_lolimit = (HYPERVISOR_VIRT_START >> 12) & 0xffff;
#else
sd.sd_lolimit = 0xffff; /* 4GB limit, wraps around */
#endif
sd.sd_hilimit = 0xf;
sd.sd_type = SDT_MEMRWA;
sd.sd_dpl = SEL_UPL;
sd.sd_p = 1;
sd.sd_xx = 0;
sd.sd_def32 = 1;
sd.sd_gran = 1;
fill_based_sd(&sd, base);
critical_enter();
td->td_pcb->pcb_fsd = sd;
#ifdef XEN
@ -243,28 +250,13 @@ sysarch(td, uap)
break;
case I386_SET_GSBASE:
error = copyin(uap->parms, &base, sizeof(base));
if (!error) {
if (error == 0) {
/*
* Construct a descriptor and store it in the pcb for
* the next context switch. Also store it in the gdt
* because we have to do a load_gs() right now.
*/
sd.sd_lobase = base & 0xffffff;
sd.sd_hibase = (base >> 24) & 0xff;
#ifdef XEN
/* need to do nosegneg like Linux */
sd.sd_lolimit = (HYPERVISOR_VIRT_START >> 12) & 0xffff;
#else
sd.sd_lolimit = 0xffff; /* 4GB limit, wraps around */
#endif
sd.sd_hilimit = 0xf;
sd.sd_type = SDT_MEMRWA;
sd.sd_dpl = SEL_UPL;
sd.sd_p = 1;
sd.sd_xx = 0;
sd.sd_def32 = 1;
sd.sd_gran = 1;
fill_based_sd(&sd, base);
critical_enter();
td->td_pcb->pcb_gsd = sd;
#ifdef XEN

View File

@ -94,6 +94,7 @@ struct reg;
struct fpreg;
struct dbreg;
struct dumperinfo;
struct segment_descriptor;
void *alloc_fpusave(int flags);
void bcopyb(const void *from, void *to, size_t len);
@ -112,6 +113,7 @@ void dump_add_page(vm_paddr_t);
void dump_drop_page(vm_paddr_t);
void finishidentcpu(void);
void fillw(int /*u_short*/ pat, void *base, size_t cnt);
void fill_based_sd(struct segment_descriptor *sdp, uint32_t base);
void initializecpu(void);
void initializecpucache(void);
void i686_pagezero(void *addr);

View File

@ -51,6 +51,10 @@
#define PT_GETXSTATE_INFO (PT_FIRSTMACH + 4)
#define PT_GETXSTATE (PT_FIRSTMACH + 5)
#define PT_SETXSTATE (PT_FIRSTMACH + 6)
#define PT_GETFSBASE (PT_FIRSTMACH + 7)
#define PT_SETFSBASE (PT_FIRSTMACH + 8)
#define PT_GETGSBASE (PT_FIRSTMACH + 9)
#define PT_SETGSBASE (PT_FIRSTMACH + 10)
/* Argument structure for PT_GETXSTATE_INFO. */
struct ptrace_xstate_info {