aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/x86/kernel/ptrace_64.c35
1 files changed, 31 insertions, 4 deletions
diff --git a/arch/x86/kernel/ptrace_64.c b/arch/x86/kernel/ptrace_64.c
index 607085f3f08..1edece36044 100644
--- a/arch/x86/kernel/ptrace_64.c
+++ b/arch/x86/kernel/ptrace_64.c
@@ -22,6 +22,7 @@
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/processor.h>
+#include <asm/prctl.h>
#include <asm/i387.h>
#include <asm/debugreg.h>
#include <asm/ldt.h>
@@ -260,12 +261,22 @@ static int putreg(struct task_struct *child,
case offsetof(struct user_regs_struct,fs_base):
if (value >= TASK_SIZE_OF(child))
return -EIO;
- child->thread.fs = value;
+ /*
+ * When changing the segment base, use do_arch_prctl
+ * to set either thread.fs or thread.fsindex and the
+ * corresponding GDT slot.
+ */
+ if (child->thread.fs != value)
+ return do_arch_prctl(child, ARCH_SET_FS, value);
return 0;
case offsetof(struct user_regs_struct,gs_base):
+ /*
+ * Exactly the same here as the %fs handling above.
+ */
if (value >= TASK_SIZE_OF(child))
return -EIO;
- child->thread.gs = value;
+ if (child->thread.gs != value)
+ return do_arch_prctl(child, ARCH_SET_GS, value);
return 0;
case offsetof(struct user_regs_struct, eflags):
value &= FLAG_MASK;
@@ -296,9 +307,25 @@ static unsigned long getreg(struct task_struct *child, unsigned long regno)
case offsetof(struct user_regs_struct, es):
return child->thread.es;
case offsetof(struct user_regs_struct, fs_base):
- return child->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.
+ */
+ if (child->thread.fs != 0)
+ return child->thread.fs;
+ if (child->thread.fsindex != FS_TLS_SEL)
+ return 0;
+ return get_desc_base(&child->thread.tls_array[FS_TLS]);
case offsetof(struct user_regs_struct, gs_base):
- return child->thread.gs;
+ /*
+ * Exactly the same here as the %fs handling above.
+ */
+ if (child->thread.gs != 0)
+ return child->thread.gs;
+ if (child->thread.gsindex != GS_TLS_SEL)
+ return 0;
+ return get_desc_base(&child->thread.tls_array[GS_TLS]);
default:
regno = regno - sizeof(struct pt_regs);
val = get_stack_long(child, regno);