[PATCH 8/9] x86: Support arbitrary fs/gs base in getregs

From: Andi Kleen
Date: Mon Mar 21 2016 - 12:18:19 EST


From: Andi Kleen <ak@xxxxxxxxxxxxxxx>

The ptrace code for fs/gs base made some assumptions on
the state of fs/gs which are not true anymore on kernels
running with FSGSBASE.

With the new instructions it is very easy to access
the values, and they are always stored in the thread
struct. So just implement the straight forward code
to access it directly.

Note the direct access code path is only used for core dumps,
as with real ptrace the process is always blocked
and the state can be read from memory.

Signed-off-by: Andi Kleen <ak@xxxxxxxxxxxxxxx>
---
arch/x86/include/asm/proto.h | 1 +
arch/x86/kernel/process_64.c | 15 +++++++++++++--
arch/x86/kernel/ptrace.c | 15 ++++++++++++++-
3 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/arch/x86/include/asm/proto.h b/arch/x86/include/asm/proto.h
index 9b9b30b..9f235e0 100644
--- a/arch/x86/include/asm/proto.h
+++ b/arch/x86/include/asm/proto.h
@@ -31,5 +31,6 @@ void x86_report_nx(void);
extern int reboot_force;

long do_arch_prctl(struct task_struct *task, int code, unsigned long addr);
+unsigned long read_user_gsbase(void);

#endif /* _ASM_X86_PROTO_H */
diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
index 5f40517..d7674d9 100644
--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -272,7 +272,7 @@ static noinline __kprobes void switch_gs_base(unsigned long gs)
}

/* Interrupts are disabled here. */
-static noinline __kprobes unsigned long read_user_gsbase(void)
+static noinline __kprobes unsigned long __read_user_gsbase(void)
{
unsigned long gs;

@@ -282,6 +282,17 @@ static noinline __kprobes unsigned long read_user_gsbase(void)
return gs;
}

+unsigned long read_user_gsbase(void)
+{
+ unsigned long flags;
+ unsigned long gs;
+
+ local_irq_save(flags);
+ gs = __read_user_gsbase();
+ local_irq_restore(flags);
+ return gs;
+}
+
/*
* switch_to(x,y) should switch tasks from x to y.
*
@@ -315,7 +326,7 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
savesegment(gs, gsindex);
if (static_cpu_has(X86_FEATURE_FSGSBASE)) {
prev->fs = rdfsbase();
- prev->gs = read_user_gsbase();
+ prev->gs = __read_user_gsbase();
}

/*
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index 32e9d9c..b68b15b 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -38,6 +38,7 @@
#include <asm/hw_breakpoint.h>
#include <asm/traps.h>
#include <asm/syscall.h>
+#include <asm/fsgs.h>

#include "tls.h"

@@ -452,12 +453,18 @@ static unsigned long getreg(struct task_struct *task, unsigned long offset)

#ifdef CONFIG_X86_64
case offsetof(struct user_regs_struct, fs_base): {
+ unsigned int seg = task->thread.fsindex;
+ if (boot_cpu_has(X86_FEATURE_FSGSBASE)) {
+ if (task == current)
+ return rdfsbase();
+ else
+ return task->thread.fs;
+ }
/*
* do_arch_prctl may have used a GDT slot instead of
* the MSR. To userland, it appears the same either
* way, except the %fs segment selector might not be 0.
*/
- unsigned int seg = task->thread.fsindex;
if (task->thread.fs != 0)
return task->thread.fs;
if (task == current)
@@ -471,6 +478,12 @@ static unsigned long getreg(struct task_struct *task, unsigned long offset)
* Exactly the same here as the %fs handling above.
*/
unsigned int seg = task->thread.gsindex;
+ if (boot_cpu_has(X86_FEATURE_FSGSBASE)) {
+ if (task == current)
+ return read_user_gsbase();
+ else
+ return task->thread.gs;
+ }
if (task->thread.gs != 0)
return task->thread.gs;
if (task == current)
--
2.5.5