From: "H. Peter Anvin" <h...@linux.intel.com>

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) <h...@zytor.com>
Cc: Ingo Molnar <mi...@kernel.org>
Cc: Thomas Gleixner <t...@linutronix.de>
Cc: Andy Lutomirski <l...@kernel.org>
Cc: Chang S. Bae <chang.seok....@intel.com>
Cc: Markus T. Metzger <markus.t.metz...@intel.com>
---
 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

Reply via email to