[PATCH v3 6/7] x86/tls,ptrace: provide regset access to the GDT

From: H. Peter Anvin, Intel
Date: Thu Jun 21 2018 - 17:18:10 EST


From: "H. Peter Anvin" <hpa@xxxxxxxxxxxxxxx>

Provide access to the user-visible part of the GDT via a regset in
ptrace(). Note that we already provide a regset for the TLS area part
of the GDT; these can trivially be unified by looking at the contents
of the regset structure, especially since the TLS area is the only
user-modifiable part of the GDT.

Signed-off-by: H. Peter Anvin (Intel) <hpa@xxxxxxxxx>
Cc: Ingo Molnar <mingo@xxxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Andy Lutomirski <luto@xxxxxxxxxx>
Cc: Chang S. Bae <chang.seok.bae@xxxxxxxxx>
Cc: Markus T. Metzger <markus.t.metzger@xxxxxxxxx>
---
arch/x86/kernel/ptrace.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++--
arch/x86/kernel/tls.c | 83 ++++++++++++++++++------------------------------
arch/x86/kernel/tls.h | 8 +++--
include/uapi/linux/elf.h | 1 +
4 files changed, 118 insertions(+), 57 deletions(-)

diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index e2ee403865eb..5ce10310f440 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -50,6 +50,7 @@ enum x86_regset {
REGSET_XSTATE,
REGSET_TLS,
REGSET_IOPERM32,
+ REGSET_GDT
};

struct pt_regs_offset {
@@ -747,6 +748,60 @@ static int ioperm_get(struct task_struct *target,
0, IO_BITMAP_BYTES);
}

+/*
+ * These provide read access to the GDT. As the only part that is
+ * writable is the TLS area, that code is in tls.c.
+ */
+static int gdt_get(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ void *kbuf, void __user *ubuf)
+{
+ struct desc_struct gdt_copy[GDT_LAST_USER + 1];
+ const struct desc_struct *p;
+ struct user_desc udesc;
+ unsigned int index, endindex;
+ int err;
+
+ if (pos % sizeof(struct user_desc))
+ return -EINVAL;
+
+ /* Get a snapshot of the GDT from an arbitrary CPU */
+ memcpy(gdt_copy, get_current_gdt_ro(), sizeof(gdt_copy));
+
+ /* Copy over the TLS area */
+ memcpy(&gdt_copy[GDT_ENTRY_TLS_MIN], target->thread.tls_array,
+ sizeof(target->thread.tls_array));
+
+ /* Descriptor zero is never accessible */
+ memset(&gdt_copy[0], 0, sizeof(gdt_copy[0]));
+
+ index = pos/sizeof(struct user_desc);
+ endindex = index + count/sizeof(struct user_desc);
+ endindex = min_t(unsigned int, GDT_LAST_USER + 1 - regset->bias,
+ endindex);
+
+ p = &gdt_copy[index + regset->bias];
+
+ while (count && index < endindex) {
+ fill_user_desc(&udesc, index++, p++);
+ err = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &udesc,
+ pos, pos + sizeof(udesc));
+ if (err)
+ return err;
+ }
+
+ /* Return zero for the rest of the regset, if applicable. */
+ return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, 0, -1);
+}
+
+static int gdt_active(struct task_struct *target,
+ const struct user_regset *regset)
+{
+ (void)target;
+ return GDT_LAST_USER + 1;
+}
+
/*
* Called by kernel/ptrace.c when detaching..
*
@@ -1262,7 +1317,8 @@ static struct user_regset x86_64_regsets[] __ro_after_init = {
.core_note_type = NT_PRFPREG,
.n = sizeof(struct user_i387_struct) / sizeof(long),
.size = sizeof(long), .align = sizeof(long),
- .active = regset_xregset_fpregs_active, .get = xfpregs_get, .set = xfpregs_set
+ .active = regset_xregset_fpregs_active, .get = xfpregs_get,
+ .set = xfpregs_set
},
[REGSET_XSTATE] = {
.core_note_type = NT_X86_XSTATE,
@@ -1276,6 +1332,14 @@ static struct user_regset x86_64_regsets[] __ro_after_init = {
.size = sizeof(long), .align = sizeof(long),
.active = ioperm_active, .get = ioperm_get
},
+ [REGSET_GDT] = {
+ .core_note_type = NT_X86_GDT,
+ .n = LDT_ENTRIES, /* Theoretical maximum */
+ .size = sizeof(struct user_desc),
+ .align = sizeof(struct user_desc),
+ .active = gdt_active, .get = gdt_get,
+ .set = regset_gdt_set
+ },
};

static const struct user_regset_view user_x86_64_view = {
@@ -1309,7 +1373,8 @@ static struct user_regset x86_32_regsets[] __ro_after_init = {
.core_note_type = NT_PRXFPREG,
.n = sizeof(struct user32_fxsr_struct) / sizeof(u32),
.size = sizeof(u32), .align = sizeof(u32),
- .active = regset_xregset_fpregs_active, .get = xfpregs_get, .set = xfpregs_set
+ .active = regset_xregset_fpregs_active, .get = xfpregs_get,
+ .set = xfpregs_set
},
[REGSET_XSTATE] = {
.core_note_type = NT_X86_XSTATE,
@@ -1323,7 +1388,7 @@ static struct user_regset x86_32_regsets[] __ro_after_init = {
.size = sizeof(struct user_desc),
.align = sizeof(struct user_desc),
.active = regset_tls_active,
- .get = regset_tls_get, .set = regset_tls_set
+ .get = gdt_get, .set = regset_gdt_set
},
[REGSET_IOPERM32] = {
.core_note_type = NT_386_IOPERM,
@@ -1331,6 +1396,14 @@ static struct user_regset x86_32_regsets[] __ro_after_init = {
.size = sizeof(u32), .align = sizeof(u32),
.active = ioperm_active, .get = ioperm_get
},
+ [REGSET_GDT] = {
+ .core_note_type = NT_X86_GDT,
+ .n = LDT_ENTRIES, /* Theoretical maximum */
+ .size = sizeof(struct user_desc),
+ .align = sizeof(struct user_desc),
+ .active = gdt_active,
+ .get = gdt_get, .set = regset_gdt_set
+ },
};

static const struct user_regset_view user_x86_32_view = {
@@ -1399,3 +1472,7 @@ void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs,
/* Send us the fake SIGTRAP */
force_sig_info(SIGTRAP, &info, tsk);
}
+
+/*
+ * Copy out a set of segment descriptors in user_desc format.
+ */
diff --git a/arch/x86/kernel/tls.c b/arch/x86/kernel/tls.c
index 7b8ecb760707..fd8aa21654ff 100644
--- a/arch/x86/kernel/tls.c
+++ b/arch/x86/kernel/tls.c
@@ -231,67 +231,46 @@ int regset_tls_active(struct task_struct *target,
return n;
}

-int regset_tls_get(struct task_struct *target, const struct user_regset *regset,
- unsigned int pos, unsigned int count,
- void *kbuf, void __user *ubuf)
-{
- const struct desc_struct *tls;
-
- if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) ||
- (pos % sizeof(struct user_desc)) != 0 ||
- (count % sizeof(struct user_desc)) != 0)
- return -EINVAL;
-
- pos /= sizeof(struct user_desc);
- count /= sizeof(struct user_desc);
-
- tls = &target->thread.tls_array[pos];
-
- if (kbuf) {
- struct user_desc *info = kbuf;
- while (count-- > 0)
- fill_user_desc(info++, GDT_ENTRY_TLS_MIN + pos++,
- tls++);
- } else {
- struct user_desc __user *u_info = ubuf;
- while (count-- > 0) {
- struct user_desc info;
- fill_user_desc(&info, GDT_ENTRY_TLS_MIN + pos++, tls++);
- if (__copy_to_user(u_info++, &info, sizeof(info)))
- return -EFAULT;
- }
- }
-
- return 0;
-}
-
-int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
+/* The only part of the GDT that is settable is the TLS area */
+int regset_gdt_set(struct task_struct *target,
+ const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES];
- const struct user_desc *info;
- int i;
-
- if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) ||
- (pos % sizeof(struct user_desc)) != 0 ||
- (count % sizeof(struct user_desc)) != 0)
+ const struct user_desc * const info = infobuf;
+ const unsigned int minpos =
+ GDT_ENTRY_TLS_MIN * sizeof(struct user_desc);
+ const unsigned int maxpos =
+ (GDT_ENTRY_TLS_MAX+1) * sizeof(struct user_desc);
+ int err;
+ unsigned int index, ntls, i;
+
+ if (pos % sizeof(struct user_desc))
return -EINVAL;

- if (kbuf)
- info = kbuf;
- else if (__copy_from_user(infobuf, ubuf, count))
- return -EFAULT;
- else
- info = infobuf;
+ pos += regset->bias * sizeof(struct user_desc);
+
+ /* Ignore entries before the TLS region */
+ err = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, minpos);
+ if (err)
+ return err;

- for (i = 0; i < count / sizeof(struct user_desc); i++)
- if (!tls_desc_okay(info + i))
+ /* Load the TLS descriptor information */
+ index = pos/sizeof(struct user_desc);
+ ntls = count/sizeof(struct user_desc);
+ ntls = min_t(unsigned int, GDT_ENTRY_TLS_ENTRIES, ntls);
+ err = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ infobuf, minpos, maxpos);
+ if (err)
+ return err;
+
+ for (i = 0; i < ntls; i++) {
+ if (!tls_desc_okay(&info[i]))
return -EINVAL;
+ }

- set_tls_desc(target,
- GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)),
- info, count / sizeof(struct user_desc));
+ set_tls_desc(target, index, info, ntls);

return 0;
}
diff --git a/arch/x86/kernel/tls.h b/arch/x86/kernel/tls.h
index 2f083a2fe216..936680e904d0 100644
--- a/arch/x86/kernel/tls.h
+++ b/arch/x86/kernel/tls.h
@@ -14,8 +14,12 @@

#include <linux/regset.h>

+#ifdef CONFIG_X86_TLS_AREA
extern user_regset_active_fn regset_tls_active;
-extern user_regset_get_fn regset_tls_get;
-extern user_regset_set_fn regset_tls_set;
+extern user_regset_set_fn regset_gdt_set;
+#else
+#define regset_tls_active NULL
+#define regset_gdt_set NULL
+#endif

#endif /* _ARCH_X86_KERNEL_TLS_H */
diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h
index 4e12c423b9fe..8c386906eba8 100644
--- a/include/uapi/linux/elf.h
+++ b/include/uapi/linux/elf.h
@@ -400,6 +400,7 @@ typedef struct elf64_shdr {
#define NT_386_TLS 0x200 /* i386 TLS slots (struct user_desc) */
#define NT_386_IOPERM 0x201 /* x86 io permission bitmap (1=deny) */
#define NT_X86_XSTATE 0x202 /* x86 extended state using xsave */
+#define NT_X86_GDT 0x203 /* x86 GDT content (user visible) */
#define NT_S390_HIGH_GPRS 0x300 /* s390 upper register halves */
#define NT_S390_TIMER 0x301 /* s390 timer register */
#define NT_S390_TODCMP 0x302 /* s390 TOD clock comparator register */
--
2.14.4