Vulnerability: arch/x86/kernel/tls.c in the Thread Local Storage (TLS) implementation in the Linux kernel through 3.18.1 allows local users to bypass the espfix protection mechanism, and consequently makes it easier for local users to bypass the ASLR protection mechanism, via a crafted application that makes a set_thread_area system call and later reads a 16-bit value.
Vulnerability ID: CVE-2014-8133
CWE: CWE-264
Severity score: 2.9
Project: https://github.com/torvalds/linux
Published: 2014-12-17
File: arch/x86/kernel/tls.c
Commit message: 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>
Commit author: Andy Lutomirski

Vulnerable version (before fix):
 static int get_free_idx(void) 	return -ESRCH; }  static void set_tls_desc(struct task_struct *p, int idx, 			 const struct user_desc *info, int n) { int do_set_thread_area(struct task_struct *p, int idx, 	if (copy_from_user(&info, u_info, sizeof(info))) 		return -EFAULT;  	if (idx == -1) 		idx = info.entry_number;  int regset_tls_set(struct task_struct *target, const struct user_regset *regset, { 	struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES]; 	const struct user_desc *info;  	if (pos >= GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) || 	    (pos % sizeof(struct user_desc)) != 0 || int regset_tls_set(struct task_struct *target, const struct user_regset *regset, 	else 		info = infobuf;  	set_tls_desc(target, 		     GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)), 		     info, count / sizeof(struct user_desc));

Fixed version (after fix):
 static int get_free_idx(void) 	return -ESRCH; }  static bool tls_desc_okay(const struct user_desc *info) { 	if (LDT_empty(info)) 		return true;  	/* 	 * espfix is required for 16-bit data segments, but espfix 	 * only works for LDT segments. 	 */ 	if (!info->seg_32bit) 		return false;  	return true; }  static void set_tls_desc(struct task_struct *p, int idx, 			 const struct user_desc *info, int n) { int do_set_thread_area(struct task_struct *p, int idx, 	if (copy_from_user(&info, u_info, sizeof(info))) 		return -EFAULT;  	if (!tls_desc_okay(&info)) 		return -EINVAL;  	if (idx == -1) 		idx = info.entry_number;  int regset_tls_set(struct task_struct *target, const struct user_regset *regset, { 	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 || int regset_tls_set(struct task_struct *target, const struct user_regset *regset, 	else 		info = infobuf;  	for (i = 0; i < count / sizeof(struct user_desc); 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));

Patch:
patch: @@ -27,6 +27,21 @@ static int get_free_idx(void)
 	return -ESRCH;
 }
 
+static bool tls_desc_okay(const struct user_desc *info)
+{
+	if (LDT_empty(info))
+		return true;
+
+	/*
+	 * espfix is required for 16-bit data segments, but espfix
+	 * only works for LDT segments.
+	 */
+	if (!info->seg_32bit)
+		return false;
+
+	return true;
+}
+
 static void set_tls_desc(struct task_struct *p, int idx,
 			 const struct user_desc *info, int n)
 {
@@ -66,6 +81,9 @@ int do_set_thread_area(struct task_struct *p, int idx,
 	if (copy_from_user(&info, u_info, sizeof(info)))
 		return -EFAULT;
 
+	if (!tls_desc_okay(&info))
+		return -EINVAL;
+
 	if (idx == -1)
 		idx = info.entry_number;
 
@@ -192,6 +210,7 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
 {
 	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 ||
@@ -205,6 +224,10 @@ int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
 	else
 		info = infobuf;
 
+	for (i = 0; i < count / sizeof(struct user_desc); 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));
path: arch/x86/kernel/tls.c