A few months ago while auditing the MSM Android kernel for bugs (MSM is the Linux kernel branch for the Qualcomm SoC on Android), I found an interesting issue. When I found some time, I reported it to the Android Security Team for responsible disclosure.

This issue was aknowledged by the Android Security Team and it got a High severity rating, id #198231 and label AndroidID-26404525.

In this blog post we will explore the issue, its limitations and the exploitation.

One of my old test devices is a Nexus 7 2013 which runs a Qualcomm SoC, so Android ships with a MSM kernel for this model.

You can find the kernel sources for the msm kernel of this device directly from Google Android repository.

The codename for Nexus 7 2013 device in AOSP is flo. The master branch of the kernel is empty, you will need to switch to your branch of interest to actually get the sources that interest you, for example checking out the android-msm-flo-3.4-marshmallow-mr1 branch.

A very interesting feature and attack surface of the Linux kernel, especially for Android devices, is the debug filesystem or debugfs for short. In another post, we discussed another Linux kernel attack surface which is Linux devices under /dev/.

Like with the kernel devices under /dev/, you can touch several interesting kernel code paths by interacting with the filesystem that debugfs creates.

debugfs:

Quote from the Linux kernel documentation under Documentation/filesystems/debugfs.txt, which I think it is very representative:

Debugfs exists as a simple way for kernel developers to make information
available to user space.  Unlike /proc, which is only meant for information
about a process, or sysfs, which has strict one-value-per-file rules,
debugfs has no rules at all.  Developers can put any information they want
there.

As you can imagine from this quote, debugfs is a nice place to hunt for infoleaks and memory corruption bugs. In addition to being able to read information produced by the kernel, debugfs has a lot of entries which give you the ability to pass data to the kernel — potentially allowing for memory corruption.

The debugfs is mounted in /sys/kernel/debug and usually on Android devices it’s symlinked to /d/.

Hunting for bugs:

This time instead of starting with searching the filesystem entries, I took the opposite approach; inspection of the kernel code. I started looking for dangerous functions and macros such as writel and backtracing the usage to see if they could be abused by debugfs or devices from userspace.

The writel macro/function is implementation dependant but it can be considered equivalent to a function void writel(unsigned value, address); which would write value at address.

As you can guess, if we can control address and value we have an arbitrary kernel write, which is a very powerful primitive for an attacker.

The issues:

After not too much looking into the code, I backtraced a writel call to a debugfs entry:

/sys/kernel/debug/mddi/reg -rw-r--r-- root root

As you can see this debugfs entry is writable by root (which lowers a lot the impact of this issue), and readable by any user.

Before going into the code, let me show how simple the PoC is, which is IMO very funny, running it as root:

echo "41414141 42424242" > /sys/kernel/debug/mddi/reg

The device will kernel panic and reboot, then you can get a summary of the kernel panic from /proc/last_kmsg.

Panic summary of arbitrary write:

[  277.320953] Unable to handle kernel paging request at virtual address 41414141
[  277.321411] pgd = e50fc000
[  277.321868] [41414141] *pgd=00000000
[  277.322387] Internal error: Oops: 805 [#1] PREEMPT SMP ARM
[  277.322662] CPU: 0    Tainted: G        W     (3.4.0-g8ba2631 #1)
[  277.323211] PC is at mddi_reg_write+0x40/0x70
[  277.323486] LR is at mddi_reg_write+0x2c/0x70
[  277.324005] pc : [<c02e04fc>]    lr : [<c02e04e8>]    psr: 60010013
[  277.324005] sp : e789bf30  ip : 0000000a  fp : b6d06196
[  277.324768] r10: 00000000  r9 : e789a000  r8 : 00000012
[  277.325042] r7 : 41414141  r6 : 41414141  r5 : 42424242  r4 : 00000000
[  277.325531] r3 : c113f7c0  r2 : 00000000  r1 : 00000000  r0 : 00000000
[  277.325836] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
[  277.326324] Control: 10c5787d  Table: a64fc06a  DAC: 00000015

As you can see the kernel paniced by trying to write 0x42424242 to 0x41414141, specified by the attacker.

Notice that you can panic the kernel also by reading from this as unprivileged user:

cat /sys/kernel/debug/mddi/reg

This may possibly lead to an infoleak as an unpriviledged user if we can avoid causing a kernel panic and getting the kernel to return us meaningful memory. I haven’t yet found to the to time to see if there is a feasible code path that would allow this behavior, but feel free to try.

The code:

We will go backward, starting from the code creating the /sys/kernel/debug/mddi/reg entry in the file drivers/video/msm/mdp_debugfs.c:

dent = debugfs_create_dir("mddi", NULL);

if (IS_ERR(dent)) {
    printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n",
        __FILE__, __LINE__, PTR_ERR(dent));
    return -1;
}

if (debugfs_create_file("reg", 0644, dent, 0, &pmdh_fops)
        == NULL) {
    printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n",
        __FILE__, __LINE__);
    return -1;
}

In this code the kernel will create the debugfs entry, and assign the pmdh_fops file operation handler (same file: drivers/video/msm/mdp_debugfs.c):

static const struct file_operations pmdh_fops = {
    .open = mddi_reg_open,
    .release = mddi_reg_release,
    .read = pmdh_reg_read,
    .write = pmdh_reg_write,
};

And in pmdh_reg_write you will see the function that reads our data from the write to the filesystem (same file: drivers/video/msm/mdp_debugfs.c):

static ssize_t pmdh_reg_write(
    struct file *file,
    const char __user *buff,
    size_t count,
    loff_t *ppos)
{
    ...

    cnt = sscanf(debug_buf, "%x %x", &off, &data);

    mddi_reg_write(0, off, data);

    ...
}

Then mddi_reg_write will call the dangerous writel as we mentioned without proper bound checkings (still in drivers/video/msm/mdp_debugfs.c):

static void mddi_reg_write(int ndx, uint32 off, uint32 data)
{
    ...
    writel(data, base + off);
    ...
}

For the read operation, it is a very similar codepath: mddi_reg_read -> readl

You can check it out if you have time, I haven’t explored the possibility of memory leaks, which can potentially be used by unprivileged users.

The exploitation:

I didn’t code any PoC except the trigger. An arbitrary kernel memory write is probably the best primitive you can have for kernel exploitation, so it is very easy to use.

You can overwrite a function pointer for example and hijack the kernel to get kernel code execution.

Kernel code execution compared to userspace root only is starting to get significant on Android, if you want to try to exploit the TrustZone, you most likely need to execute code in kernel context.

You must be root to utilize this vulnerability. One example usecase of this may be: if you gain code execution to a root service, but its SELinux context is restricted, you could utilize a vulnerability such as this to completely disable SELinux to enjoy full, unrestricted access to the system.

Timeline:

  • 2016/1/5 The issues is reported to the Android Security Team with the bug reporting form.
  • 2016/1/14 The Android Security Team reviewed the issue and bumped the severity to High.
  • 2016/1/27 Suggested a fix to the Android Security Team. If nobody uses this interface the best solution would be to disable it in release kernels by adding a new configuration option to make it independant from the global debugfs, in this way it can still be used for debugging by recompiling the kernel with the new configuration enabled. An additional fix can be deployed by sanitizing and checking the input from userspace, making sure it’s inside the expected areas. Since this feature it’s heavily platform dependant I think the best candidate to make this kind of fix is Qualcomm itself since they know the internals.
  • 2016/3/1 Further discussion with Android Security about disclosure and deadlines. I suggested it would be nice to stick with their 90 days disclosure policy that Google applies at Project Zero for other vendors.
  • 2016/3/31 This issue got CVE-2016-2443 as identifier.
  • 2016/4/15 I got awarded a bug bounty from Google, more than I expected actually considering the issue.
  • 2016/5/2 The issue is fixed and credited in the May 2016 Nexus Security Bulletin

Take aways:

  • debugfs is a good attack surface, especially on Android devices.
  • OEMs should probably remember to disable this debug features in production, since I don’t see a real usage from them in a production build.
  • If they leave them enable, they should be more careful to what they expose, and do proper input checks.