aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/kernel/tls.c')
-rw-r--r--arch/x86/kernel/tls.c45
1 files changed, 45 insertions, 0 deletions
diff --git a/arch/x86/kernel/tls.c b/arch/x86/kernel/tls.c
index f7fec09e3e3a..3e551eee87b9 100644
--- a/arch/x86/kernel/tls.c
+++ b/arch/x86/kernel/tls.c
@@ -27,6 +27,43 @@ static int get_free_idx(void)
27 return -ESRCH; 27 return -ESRCH;
28} 28}
29 29
30static bool tls_desc_okay(const struct user_desc *info)
31{
32 if (LDT_empty(info))
33 return true;
34
35 /*
36 * espfix is required for 16-bit data segments, but espfix
37 * only works for LDT segments.
38 */
39 if (!info->seg_32bit)
40 return false;
41
42 /* Only allow data segments in the TLS array. */
43 if (info->contents > 1)
44 return false;
45
46 /*
47 * Non-present segments with DPL 3 present an interesting attack
48 * surface. The kernel should handle such segments correctly,
49 * but TLS is very difficult to protect in a sandbox, so prevent
50 * such segments from being created.
51 *
52 * If userspace needs to remove a TLS entry, it can still delete
53 * it outright.
54 */
55 if (info->seg_not_present)
56 return false;
57
58#ifdef CONFIG_X86_64
59 /* The L bit makes no sense for data. */
60 if (info->lm)
61 return false;
62#endif
63
64 return true;
65}
66
30static void set_tls_desc(struct task_struct *p, int idx, 67static void set_tls_desc(struct task_struct *p, int idx,
31 const struct user_desc *info, int n) 68 const struct user_desc *info, int n)
32{ 69{
@@ -66,6 +103,9 @@ int do_set_thread_area(struct task_struct *p, int idx,
66 if (copy_from_user(&info, u_info, sizeof(info))) 103 if (copy_from_user(&info, u_info, sizeof(info)))
67 return -EFAULT; 104 return -EFAULT;
68 105
106 if (!tls_desc_okay(&info))
107 return -EINVAL;
108
69 if (idx == -1) 109 if (idx == -1)
70 idx = info.entry_number; 110 idx = info.entry_number;
71 111
@@ -192,6 +232,7 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
192{ 232{
193 struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES]; 233 struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES];
194 const struct user_desc *info; 234 const struct user_desc *info;
235 int i;
195 236
196 if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) || 237 if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) ||
197 (pos % sizeof(struct user_desc)) != 0 || 238 (pos % sizeof(struct user_desc)) != 0 ||
@@ -205,6 +246,10 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
205 else 246 else
206 info = infobuf; 247 info = infobuf;
207 248
249 for (i = 0; i < count / sizeof(struct user_desc); i++)
250 if (!tls_desc_okay(info + i))
251 return -EINVAL;
252
208 set_tls_desc(target, 253 set_tls_desc(target,
209 GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)), 254 GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)),
210 info, count / sizeof(struct user_desc)); 255 info, count / sizeof(struct user_desc));