AF_UNIX MSG_OOB UAF & SKB-based kernel primitives

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Sažetak

  • Linux >=6.9 je uveo problematičan refaktor manage_oob() (5aa57d9f2d53) za AF_UNIX MSG_OOB handling. Složeni zero-length SKB-ovi su zaobišli logiku koja briše u->oob_skb, pa je običan recv() mogao osloboditi out-of-band SKB dok je pokazivač ostajao živ, što je dovelo do CVE-2025-38236.
  • Ponovno pokretanje recv(..., MSG_OOB) dereferencira dangling struct sk_buff. Sa MSG_PEEK, putanja unix_stream_recv_urg() -> __skb_datagram_iter() -> copy_to_user() postaje stabilno 1-bajtno arbitrarno kernel čitanje; bez MSG_PEEK primitiv povećava UNIXCB(oob_skb).consumed na offsetu 0x44, tj. dodaje +4 GiB gornjem dword-u bilo koje 64-bit vrednosti smeštene na offsetu 0x40 unutar ponovo alociranog objekta.
  • Ispraznivši order-0/1 unmovable stranice (page-table spray), prisilnim oslobađanjem SKB slab stranice u buddy allocator i ponovnim korišćenjem fizičke stranice kao pipe buffer, exploit falsifikuje SKB metadata u kontrolisanoj memoriji da identifikuje dangling stranicu i pivotira read primitiv u .data, vmemmap, per-CPU i page-table regione uprkos usercopy hardening-u.
  • Ista stranica kasnije se može reciklirati kao gornja kernel-stack stranica sveže kloniranog threada. CONFIG_RANDOMIZE_KSTACK_OFFSET postaje orakl: ispitujući layout steka dok pipe_write() blokira, napadač čeka dok prosuta dužina copy_page_from_iter() (R14) ne dospe na offset 0x40, pa onda pusti +4 GiB inkrement da korumpira vrednost na steku.
  • Self-looping skb_shinfo()->frag_list drži UAF syscall u petlji u kernel prostoru dok saradnički thread ne zaustavi copy_from_iter() (putem mprotect() nad VMA koja sadrži jednu MADV_DONTNEED rupu). Prekidanje petlje oslobađa inkrement tačno kada je ciljani stack lokalan, uvećavajući argument bytes tako da copy_page_from_iter() piše preko pipe buffer stranice u sledeću fizičku stranicu.
  • Praćenjem PFN-ova pipe-buffer-a i page table-a pomoću read primitiva, napadač osigurava da je sledeća stranica PTE stranica, konvertuje OOB copy u arbitrarne PTE write-ove i dobija neograničeno kernel read/write/execute. Chrome je smanjio dostupnost blokiranjem MSG_OOB iz renderera (6711812), a Linux je ispravio logičku grešku u 32ca245464e1 i uveo CONFIG_AF_UNIX_OOB da bi opcija postala izborna.

Root cause: manage_oob() assumes only one zero-length SKB

unix_stream_read_generic() očekuje da svaki SKB koji vrati manage_oob() ima unix_skb_len() > 0. Posle 93c99f21db36, manage_oob() je preskakao cleanup putanju skb == u->oob_skb kad god bi prvo uklonio zero-length SKB koji je ostao nakon recv(MSG_OOB). Naknadno rešenje (5aa57d9f2d53) i dalje je napredovalo sa prvog zero-length SKB-a na skb_peek_next() bez ponovnog provere dužine. Sa dva uzastopna zero-length SKB-a, funkcija je vratila drugi prazan SKB; unix_stream_read_generic() ga je onda preskočio bez ponovnog poziva manage_oob(), tako da je pravi OOB SKB bio dequeued i oslobođen dok je u->oob_skb i dalje pokazivao na njega.

Minimal trigger sequence

char byte;
int socks[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
for (int i = 0; i < 2; ++i) {
send(socks[1], "A", 1, MSG_OOB);
recv(socks[0], &byte, 1, MSG_OOB);
}
send(socks[1], "A", 1, MSG_OOB);   // SKB3, u->oob_skb = SKB3
recv(socks[0], &byte, 1, 0);         // normal recv frees SKB3
recv(socks[0], &byte, 1, MSG_OOB);   // dangling u->oob_skb

Primitivi koje izlaže unix_stream_recv_urg()

  1. 1-byte arbitrary read (repeatable): state->recv_actor() na kraju izvodi copy_to_user(user, skb_sourced_addr, 1). Ako se dangling SKB ponovo alocira u memoriju pod kontrolom napadača (ili u kontrolisani alias kao što je pipe page), svaki recv(MSG_OOB | MSG_PEEK) kopira bajt sa proizvoljne kernel adrese koju dozvoljava __check_object_size() u korisnički prostor bez rušenja procesa. Ostaviti MSG_PEEK omogućava neograničeno čitanje jer sačuva dangling pointer.
  2. Constrained write: Kada je MSG_PEEK isključen, UNIXCB(oob_skb).consumed += 1 uvećava 32-bitno polje na offsetu 0x44. Na 0x100-poravnatim SKB alokacijama ovo leži četiri bajta iznad 8-bajt-poravnatog word-a, pretvarajući primitiv u +4 GiB inkrement word-a koji je na offsetu 0x40. Da bi se ovo pretvorilo u kernel write, potrebno je pozicionirati osetljivu 64-bit vrednost na taj offset.

Realokacija SKB stranice za proizvoljno čitanje

  1. Isprazniti order-0/1 unmovable freeliste: Mapirati veliki read-only anonymous VMA i izazvati fault za svaku stranicu da bi se forsirala alokacija page-table-a (order-0 unmovable). Popunjavanje ~10% RAM-a page tabelama osigurava da će naredne skbuff_head_cache alokacije uzimati sveže buddy stranice kada se order-0 liste iscrpe.
  2. Spray SKB-ova i izolovati slab stranicu: Koristiti desetine stream socketpair-ova i staviti stotine malih poruka po socketu (~0x100 bajtova po SKB) da se popuni skbuff_head_cache. Otpustiti izabrane SKB-ove da se ciljna slab stranica potpuno dovede pod kontrolu napadača i pratiti njen struct page refcount putem nastalog read primitiva.
  3. Vratiti slab stranicu u buddy allocator: Otpustiti svaki objekat na stranici, zatim izvršiti dovoljno dodatnih alokacija/oslobađanja da se stranica izbaci iz SLUB per-CPU partial listi i per-CPU page listi tako da postane order-1 stranica na buddy freelistu.
  4. Ponovo alocirati kao pipe buffer: Napraviti stotine pipe-ova; svaki pipe rezerviše najmanje dve 0x1000-bajt data stranice (PIPE_MIN_DEF_BUFFERS). Kada buddy allocator split-uje order-1 stranicu, jedna polovina može ponovo koristiti oslobođenu SKB stranicu. Da se locira koji pipe i koji offset alias-uje oob_skb, upisati jedinstvene marker bajtove u lažne SKB-ove smeštene širom pipe stranica i izdavati ponovljene recv(MSG_OOB | MSG_PEEK) pozive dok marker ne bude vraćen.
  5. Forgovati stabilan SKB layout: Popuniti alias-ovanu pipe stranicu lažnim struct sk_buff čiji data/head pokazivači i skb_shared_info struktura pokazuju na proizvoljne kernel adrese od interesa. Pošto x86_64 onemogućava SMAP unutar copy_to_user(), user-mode adrese mogu služiti kao staging baferi dok kernel pokazivači ne postanu poznati.
  6. Poštovati usercopy hardening: Kopija uspeva protiv .data/.bss, vmemmap unosa, per-CPU vmalloc opsega, kernel stack-ova drugih thread-ova i direct-map stranica koje ne prelaze granice višeg-order folio-a. Čitanja prema .text ili specijalizovanim keševima koja __check_heap_object() odbije jednostavno vraćaju -EFAULT bez ubijanja procesa.

Introspekcija allocator-a pomoću read primitiva

  • Break KASLR: Pročitati bilo koji IDT deskriptor iz fixed mapping-a na CPU_ENTRY_AREA_RO_IDT_VADDR (0xfffffe0000000000) i oduzeti poznati handler offset da se obnovi kernel base.
  • SLUB/buddy stanje: Globalni .data simboli otkrivaju kmem_cache baze, dok vmemmap unosi izlažu tip flag-ove svake stranice, freelist pokazivač i owning cache. Skeniranje per-CPU vmalloc segmenata otkriva struct kmem_cache_cpu instance tako da sledeća adresa alokacije ključnih keševa (npr. skbuff_head_cache, kmalloc-cg-192) postane predvidljiva.
  • Page tables: Umesto čitanja mm_struct (blokirano od usercopy), proći kroz globalni pgd_list (struct ptdesc) i uskladiti trenutni mm_struct putem cpu_tlbstate.loaded_mm. Kada je root pgd poznat, primitiv može traversirati sve page table-ove da mapira PFN-ove za pipe buffer-e, page table-ove i kernel stack-ove.

Recikliranje SKB stranice kao gornja kernel-stack stranica

  1. Otpustiti kontrolisanu pipe stranicu ponovo i potvrditi preko vmemmap da se njen refcount vrati na nulu.
  2. Odmah alocirati četiri pomoćne pipe stranice i zatim ih otpustiti u obrnutom redosledu da bi LIFO ponašanje buddy allocator-a bilo determinističko.
  3. Pozvati clone() da se pokrene pomoćni thread; Linux stack-ovi su četiri stranice na x86_64, tako da četiri najrecentnije oslobođene stranice postaju njegov stack, pri čemu je poslednja oslobođena stranica (bivša SKB stranica) na najvišim adresama.
  4. Verifikovati preko page-table walka da li PFN helper thread-ovog top stack-a odgovara recikliranom SKB PFN-u.
  5. Upotrebiti arbitrary read da se posmatra layout stack-a dok se thread usmerava u pipe_write(). CONFIG_RANDOMIZE_KSTACK_OFFSET oduzima nasumičan 0x0–0x3f0 (poravnat) od RSP po syscall-u; ponovljeni write-ovi u kombinaciji sa poll()/read() iz drugog threada otkrivaju kada writer blokira sa željenim offsetom. Kada se poklopi, prosuta copy_page_from_iter() bytes argument (R14) se nalazi na offsetu 0x40 unutar reciklirane stranice.

Postavljanje lažnih SKB metadata na stack

  • Koristiti sendmsg() na AF_UNIX datagram socketu: kernel kopira korisnički sockaddr_un u stack-resident sockaddr_storage (do 108 bajtova) i ancillary podatke u drugi on-stack bafer pre nego što syscall blokira čekajući prostor u queue-u. Ovo omogućava postavljanje precizne lažne SKB strukture u memoriju stack-a.
  • Detektovati kada je kopija završena snabdevanjem 1-bajt control message koji se nalazi u nemapiranoj korisničkoj strani; ____sys_sendmsg() izaziva fault prilikom toga, tako da pomoćni thread koji poll-uje mincore() na toj adresi saznaje kada destinaciona stranica postane prisutna.
  • Zero-inicijalizovani padding iz CONFIG_INIT_STACK_ALL_ZERO zgodno popunjava neiskorišćena polja, dovršavajući validan SKB header bez dodatnih upisa.

C time+ing the +4 GiB increment sa self-looping frag list-om

  • Forgovati skb_shinfo(fakeskb)->frag_list da pokazuje na drugi lažni SKB (smešten u memoriji pod kontrolom napadača) koji ima len = 0 i next = &self. Kada skb_walk_frags() iterira ovu listu unutar __skb_datagram_iter(), izvršavanje se zavrti u nedogled jer iterator nikad ne dostigne NULL i copy petlja ne napreduje.
  • Ostaviti recv syscall da trči unutar kernela dozvoljavajući drugom lažnom SKB-u da se self-loop-uje. Kada dođe vreme da se ispali inkrement, jednostavno promeniti next pokazivač drugog SKB-a iz korisničkog prostora u NULL. Petlja izlazi i unix_stream_recv_urg() odmah izvršava UNIXCB(oob_skb).consumed += 1 jednom, utičući na bilo koji objekat koji trenutno zauzima recikliranu stack stranicu na offsetu 0x40.

Zastavljanje copy_from_iter() bez userfaultfd

  • Mapirati ogromnu anonymous RW VMA i fault-ovati je u potpunosti.
  • Napraviti jednodržan hole sa madvise(MADV_DONTNEED, hole, PAGE_SIZE) i staviti tu adresu unutar iov_iter koji se koristi za write(pipefd, user_buf, 0x3000).
  • Paralelno pozvati mprotect() na celoj VMA iz drugog threada. Syscall zahvata mmap write lock i prolazi kroz svaki PTE. Kada pipe writer stigne do hole-a, page fault handler blokira na mmap lock koji drži mprotect(), pauzirajući copy_from_iter() u determinističkoj tački dok se prosuti bytes vrednost nalazi na segmentu stack-a hostovanom od strane reciklirane SKB stranice.

Pretvaranje inkrementa u proizvoljne PTE write-ove

  1. Ispaliti inkrement: Otpustiti frag loop dok je copy_from_iter() zaustavljen tako da +4 GiB inkrement pogodi bytes varijablu.
  2. Overflow copy-a: Kada se fault nastavi, copy_page_from_iter() veruje da može kopirati >4 GiB u trenutnu pipe stranicu. Nakon popunjavanja legitimnih 0x2000 bajtova (dva pipe buffera), izvršava još jednu iteraciju i upisuje preostale korisničke podatke u fizičku stranicu koja sledi PFN pipe buffera.
  3. Rasporediti susedstvo: Koristeći telemetry allocator-a, forsirati buddy allocator da postavi process-owned PTE stranicu odmah posle ciljane pipe buffer stranice (npr. naizmenično alocirati pipe stranice i dirati nove virtuelne opsege da bi se izazvala alokacija page-table-a dok se PFN-ovi ne poravnaju unutar istog 2 MiB pageblock-a).
  4. Overwrite-ovati page table-ove: Kodirati željene PTE unose u dodatnih 0x1000 bajtova korisničkih podataka tako da OOB copy_from_iter() popuni susednu stranicu sa unesenim entry-ima koje je izabrao napadač, dajući RW/RWX korisničke mape kernel fizičke memorije ili prepisujući postojeće unose da onemoguće SMEP/SMAP.

Mitigations / hardening ideas

  • Kernel: Apply 32ca245464e1479bfea8592b9db227fdc1641705 (properly revalidates SKBs) i razmotriti onemogućavanje AF_UNIX OOB potpuno osim ako nije striktno potrebno preko CONFIG_AF_UNIX_OOB (5155cbcdbf03). Harden-ovati manage_oob() dodatnim sanity check-ovima (npr. loop dok unix_skb_len() > 0) i audit-ovati druge socket protokole za slična pretpostavljanja.
  • Sandboxing: Filtrirati MSG_OOB/MSG_PEEK flag-ove u seccomp profilima ili višem nivou broker API-ja (Chrome change 6711812 sada blokira renderer-side MSG_OOB).
  • Allocator defenses: Jačanje SLUB freelist randomizacije ili forsiranje per-cache page coloring bi otežalo determinističko recikliranje stranica; ograničavanje broja pipe buffera takođe smanjuje pouzdanost rerne alokacije.
  • Monitoring: Izložiti visoke stope page-table alokacija ili abnormalno korišćenje pipe-ova putem telemetrije — ovaj exploit troši velike količine page tabela i pipe buffera.

References

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks