diff options
| author | Andy Lutomirski <luto@amacapital.net> | 2014-12-04 19:48:16 -0500 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2014-12-14 02:50:31 -0500 |
| commit | 41bdc78544b8a93a9c6814b8bbbfef966272abbe (patch) | |
| tree | 9c947f48aa4a6e357fc96a4a695271192252f480 | |
| parent | f0905c5a32ce6e9b743b4d9c70e53d1ce447852d (diff) | |
x86/tls: Validate TLS entries to protect espfix
Installing a 16-bit RW data segment into the GDT defeats espfix.
AFAICT this will not affect glibc, Wine, or dosemu at all.
Signed-off-by: Andy Lutomirski <luto@amacapital.net>
Acked-by: H. Peter Anvin <hpa@zytor.com>
Cc: stable@vger.kernel.org
Cc: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: security@kernel.org <security@kernel.org>
Cc: Willy Tarreau <w@1wt.eu>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
| -rw-r--r-- | arch/x86/kernel/tls.c | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/arch/x86/kernel/tls.c b/arch/x86/kernel/tls.c index f7fec09e3e3a..e7650bd71109 100644 --- a/arch/x86/kernel/tls.c +++ b/arch/x86/kernel/tls.c | |||
| @@ -27,6 +27,21 @@ 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 | return true; | ||
| 43 | } | ||
| 44 | |||
| 30 | static void set_tls_desc(struct task_struct *p, int idx, | 45 | static void set_tls_desc(struct task_struct *p, int idx, |
| 31 | const struct user_desc *info, int n) | 46 | const struct user_desc *info, int n) |
| 32 | { | 47 | { |
| @@ -66,6 +81,9 @@ int do_set_thread_area(struct task_struct *p, int idx, | |||
| 66 | if (copy_from_user(&info, u_info, sizeof(info))) | 81 | if (copy_from_user(&info, u_info, sizeof(info))) |
| 67 | return -EFAULT; | 82 | return -EFAULT; |
| 68 | 83 | ||
| 84 | if (!tls_desc_okay(&info)) | ||
| 85 | return -EINVAL; | ||
| 86 | |||
| 69 | if (idx == -1) | 87 | if (idx == -1) |
| 70 | idx = info.entry_number; | 88 | idx = info.entry_number; |
| 71 | 89 | ||
| @@ -192,6 +210,7 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset, | |||
| 192 | { | 210 | { |
| 193 | struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES]; | 211 | struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES]; |
| 194 | const struct user_desc *info; | 212 | const struct user_desc *info; |
| 213 | int i; | ||
| 195 | 214 | ||
| 196 | if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) || | 215 | if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) || |
| 197 | (pos % sizeof(struct user_desc)) != 0 || | 216 | (pos % sizeof(struct user_desc)) != 0 || |
| @@ -205,6 +224,10 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset, | |||
| 205 | else | 224 | else |
| 206 | info = infobuf; | 225 | info = infobuf; |
| 207 | 226 | ||
| 227 | for (i = 0; i < count / sizeof(struct user_desc); i++) | ||
| 228 | if (!tls_desc_okay(info + i)) | ||
| 229 | return -EINVAL; | ||
| 230 | |||
| 208 | set_tls_desc(target, | 231 | set_tls_desc(target, |
| 209 | GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)), | 232 | GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)), |
| 210 | info, count / sizeof(struct user_desc)); | 233 | info, count / sizeof(struct user_desc)); |
