Nexus 9
All Nexus 9 versions before the September 2016 patches.
Apply the September 2016 Android Security Patches.
The registers debugfs file node is initialized with the following write file operation:
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;
}
That is, on write system call, cl_register_write() securely copies a user space buffer and parses its contents as two numeric values: val, a value to be written, and offs, an offset from a constant address. Cl_dvfs_writel() is then fed with val and offs (cast into a four-byte aligned address).
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);
}
Eventually, __raw_writel() is used to write value val at offs+constant_address, which results in an arbitrary kernel write.