pipe_buffer hijack¶
Bir
struct pipe_buffer'ınpagepointer'ını corrupt et ki sıradan piperead()/write()attacker'ın seçtiği birstruct 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);
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.