diff options
Diffstat (limited to 'arch/x86/kernel/tls.c')
-rw-r--r-- | arch/x86/kernel/tls.c | 45 |
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 | ||
30 | static 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 | |||
30 | static void set_tls_desc(struct task_struct *p, int idx, | 67 | static 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)); |