CVE-2014-9870 (C, published 2016-08-06T10:59Z)
Description: value: The Linux kernel before 3.11 on ARM platforms, as used in Android before 2016-08-05 on Nexus 5 and 7 (2013) devices, does not properly consider user-space access to the TPIDRURW register, which allows local users to gain privileges via a crafted application, aka Android internal bug 28749743 and Qualcomm internal bug CR561044.
Severity: HIGH (CVSS v3 base score: 7.8)
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/torvalds/linux
Files changed:
arch/arm/include/asm/thread_info.h
arch/arm/include/asm/tls.h
arch/arm/kernel/entry-armv.S
arch/arm/kernel/process.c
arch/arm/kernel/ptrace.c
arch/arm/kernel/traps.c
Commit message: ARM: 7735/2: Preserve the user r/w register TPIDRURW on context switch and fork

Since commit 6a1c53124aa1 the user writeable TLS register was zeroed to
prevent it from being used as a covert channel between two tasks.

There are more and more applications coming to Windows RT,
Wine could support them, but mostly they expect to have
the thread environment block (TEB) in TPIDRURW.

This patch preserves that register per thread instead of clearing it.
Unlike the TPIDRURO, which is already switched, the TPIDRURW
can be updated from userspace so needs careful treatment in the case that we
modify TPIDRURW and call fork(). To avoid this we must always read
TPIDRURW in copy_thread.

Signed-off-by: André Hentschel <nerv@dawncrow.de>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Jonathan Austin <jonathan.austin@arm.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>


Vulnerable code:
	unsigned long		tp_value;
	.macro set_tls_none, tp, tmp1, tmp2
	.macro set_tls_v6k, tp, tmp1, tmp2
	mov	\tmp1, #0
	mcr	p15, 0, \tmp1, c13, c0, 2	@ clear user r/w TLS register
	.macro set_tls_v6, tp, tmp1, tmp2
	mcrne	p15, 0, \tp, c13, c0, 3		@ yes, set TLS register
	movne	\tmp1, #0
	mcrne	p15, 0, \tmp1, c13, c0, 2	@ clear user r/w TLS register
	.macro set_tls_software, tp, tmp1, tmp2
#define set_tls		set_tls_none
#define set_tls		set_tls_v6
#define set_tls		set_tls_v6k
#define set_tls		set_tls_software
	ldr	r3, [r2, #TI_TP_VALUE]
	set_tls	r3, r4, r5
		thread->tp_value = childregs->ARM_r3;
			ret = put_user(task_thread_info(child)->tp_value,
		thread->tp_value = regs->ARM_r0;
	regs->uregs[reg] = current_thread_info()->tp_value;

Fixed code:
	unsigned long		tp_value[2];	/* TLS registers */
#include <asm/asm-offsets.h>
	.macro switch_tls_none, base, tp, tpuser, tmp1, tmp2
	.macro switch_tls_v6k, base, tp, tpuser, tmp1, tmp2
	mrc	p15, 0, \tmp2, c13, c0, 2	@ get the user r/w register
	mcr	p15, 0, \tpuser, c13, c0, 2	@ and the user r/w register
	str	\tmp2, [\base, #TI_TP_VALUE + 4] @ save it
	.macro switch_tls_v6, base, tp, tpuser, tmp1, tmp2
	mrcne	p15, 0, \tmp2, c13, c0, 2	@ get the user r/w register
	mcrne	p15, 0, \tp, c13, c0, 3		@ yes, set TLS register
	mcrne	p15, 0, \tpuser, c13, c0, 2	@ set user r/w register
	strne	\tmp2, [\base, #TI_TP_VALUE + 4] @ save it
	.macro switch_tls_software, base, tp, tpuser, tmp1, tmp2
#define switch_tls	switch_tls_none
#define switch_tls	switch_tls_v6
#define switch_tls	switch_tls_v6k
#define switch_tls	switch_tls_software
#ifndef __ASSEMBLY__
static inline unsigned long get_tpuser(void)
{
	unsigned long reg = 0;

	if (has_tls_reg && !tls_emu)
		__asm__("mrc p15, 0, %0, c13, c0, 2" : "=r" (reg));

	return reg;
}
#endif
	ldr	r4, [r2, #TI_TP_VALUE]
	ldr	r5, [r2, #TI_TP_VALUE + 4]
	switch_tls r1, r4, r5, r3, r7
#include <asm/tls.h>
		thread->tp_value[0] = childregs->ARM_r3;
	thread->tp_value[1] = get_tpuser();
			ret = put_user(task_thread_info(child)->tp_value[0],
		thread->tp_value[0] = regs->ARM_r0;
	regs->uregs[reg] = current_thread_info()->tp_value[0];