26/08/2015

Android linux kernel privilege escalation (CVE-2014-4323)

In this blog post, we'll cover another Android linux kernel privilege escalation vulnerability I discovered, which could be used to achieve kernel code execution on Android devices.

This time we'll only go over the vulnerability, with no exploit, since I don't personally have any device which is vulnerable to this issue, and therefore couldn't write an exploit. However, we'll dream up an exploit together, which should be pretty simple to implement.

Before we start, I'd like to point out that this vulnerability has been responsibly disclosed to Qualcomm, and it has since been fixed (see "Timeline" below). It should be noted that this vulnerability was present in all Qualcomm-based devices based on the following chipsets:
  • APQ 8064 (Snapdragon S4 Pro)
  • MSM 8960 (Snapdragon S4)
  • MSM 8660 (Snapdragon S3)
  • MSM 8x30
  • MSM 7x30
So all devices based on these SoCs (such as the Nexus 4, Nexus 7, etc.), with kernels dated before December 2014, should be vulnerable (see "Timeline" below).

Let's get to it


Today we'll take a look at the "mdp" display driver. There are slight variations of this driver, depending on the SoC. However, both the MDP22 and MDP303 versions (which correspond to the SoCs listed above) are vulnerable.

Normally, users may access the display driver in order to modify the display's properties, and perhaps even in order to retrieve the current frame-buffer (that is, take a screenshot).

Since these operations are somewhat sensitive, they are usually restricted so that only processes with the "graphics" group-ID may perform them. This is facilitated by setting the permissions on the device files appropriately:


Naturally, the process in charge of compositing surfaces on Android (surfaceflinger) is a member of this group. However! The shell user is also a member of the graphics group - meaning, it can interact freely with the "mdp" driver (and therefore the vulnerability is also locally exploitable).

shell's user-ID and group-IDs

Diving into the code


The "mdp" driver is extremely complex, supporting a wide range of commands; from IOCTLs, to memory mapping the device, etc.  


This means we need a good strategy for mapping out the weak spots within the driver. Skimming over the code, going by the sheer amount of IOCTL commands supported (at least twenty different commands), it seems as though looking at the IOCTL commands in depth might be a lucrative venture.

Funnily, though, there was no need to go too deeply, since the second IOCTL command turned out to be vulnerable :)

MSMFB_SET_LUT

 
The "mdp" driver allows a user to change the colour map lookup table used by the display, by means of a special IOCTL called "MSMFB_SET_LUT". The actual implementation of this IOCTL is deferred to a simple call to an internal function pointer, which is initialized to point to the actual implementation based on the MDP platform which is compiled into the kernel.


The above "lut_update" function pointer is initialized to point to the "mdp_lut_update_lcdc" on the MDP22 system, and to "mdp_lut_update_nonlcdc" on the MDP303. Keep in mind that both of these functions receive the "fb_cmap" structure which is copied from the user directly, without any validations (as evidenced above).

Both of these functions call the "mdp_lut_hw_update" function directly in order to update the lookup-table, without performing any validations of their own on the user-controlled "fb_cmap" structure.

Let's take a good look at the "fb_cmap" structure:


Alarm bells should be ringing right about now:
  • This structure contains a large (32-bit) length field
  • There's a "start" field which is not only large (32-bit), but whose name indicates that it might actually be treated as a pointer, even though its type is an unsigned integer
  • All the pointers in the structures aren't marked as "tainted" (using "__user")!


Finally - let's keep our fingers crossed and take a look at the "mdp_lut_hw_update" function:


First, the function iterates "len" times. Then, for each iteration, the function reads the red, green and blue values from the "fb_cmap" (safely, using "copy_from_user"). But here comes the scary part:

On first glance - this might just be some innocuous piece of code. After all, who knows what MDP_OUTP means... But we've come this far, let's at least find out what it means:


Still non the wiser. What does "outpdw" do?


Oh.

For those who haven't come across it before, "writel" simply writes the value in "val" into the address at "port", using a memory write barrier beforehand. This is usually used in order to write to memory mapped registers, in order to make sure the write itself remains coherent.

Regardless, this means that the function above writes the concatenated value of the red, green and blue parameters (which are fully user controlled), into an address which is built from fully known, constant values and a fully controlled 32-bit value which is not validated in any way, since:
  • "MDP_BASE" is a macro which is defined to a constant memory mapped address (one for each SoC)
  • 0x93800 is a constant number and therefore also known in advance
  • "mdp_lut_i" is actually a flag which is set alternately to either 0 or 1, on each call to MDSSFB_SET_LUT. This means that the value of 0x400*mdp_lut_i is either 0 or 0x400
  • Since we can set cmap->len to 1, the index "i" will therefore be zero in the single iteration performed, meaning we can ignore i*4 (since it will equal zero)
  • cmap->start is fully user-controlled and never validated
Here's what it looks like:


Putting it together - this means that we can write any 24-bit value into any memory address - great! :)

Dreaming up an exploit


First, as with all exploits, we'd like to neatly package the write-what-where primitive into a single function. Let's imagine we've done that, and that it's called write_value, and it accepts a 24-bit value and a 32-bit address to which this value should be written.

In order to make exploitation fully reliable, we'd need to know the current value of "mdp_lut_i". This can be done by mapping a sterile buffer within user-space which is more than 0x400 bytes large. Then, we can simply trigger to overwrite vulnerability with a cmap->start value so that the destination address will either correspond to the beginning of this mapped buffer, or to its end:

After triggering the overwrite, we can check the sterile buffer to see where the overwrite occurred - allowing us to deduce the value of "mdp_lut_i".

Now that we know all the values from which the destination address is built, we can freely overwrite any address within the kernel's virtual address space. From here on, we can simply overwrite a kernel function pointer and redirect it to a function stub allocated within user-space.

This is actually identical to the exploitation method covered in a previous blog post - in which we overwrote a function pointer within "pppolac_proto_ops" and triggered it by closing a PPP_OLAC socket.



And there you have it. A fully imaginary exploit, just waiting to be written :) If you do happen to write this exploit, please let me know!

Timeline

  • 27.09.14 - Vulnerability disclosed
  • 29.09.14 - Initial response from QC
  • 02.10.14 - Issue confirmed by QC
  • 13.11.14 - QC publishes notification to customers
  • 27.11.14 - QC publishes notification to carriers
  • 11.12.14 - Issue closed, CAF advisory issued

5 comments:

  1. Will burn my phone as soon as I am back home.
    Thanks Lag!

    ReplyDelete
    Replies
    1. Good call, burning the phone should mitigate the vulnerability ;)
      Anyway, cheers and glad you enjoyed it.

      Delete
  2. Fully reviewed all of your write-ups, much more impressive of your bug hunting methodology and clearly descriptions. You indeed deserve the acknowledgements from vendors. Good job!

    ReplyDelete
    Replies
    1. Thank you! I've been bogged down with university work lately and haven't had the time to post new stuff, but hopefully I'll manage to get a new post up soon (new TrustZone saga!). Stay tuned :)

      Delete
  3. hey any chance you can show us how to use the motoboot extractor, im very confused as i do not know python very well

    ReplyDelete