[CVE-2016-2443] Qualcomm MSM debug fs kernel arbitrary write (Nexus 5, Nexus 7 2013 and maybe other models)
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:
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:
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
:
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
):
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
):
Then mddi_reg_write
will call the dangerous writel
as we mentioned without proper bound checkings (still in drivers/video/msm/mdp_debugfs.c
):
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 isQualcomm
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.