AF_UNIX MSG_OOB UAF & SKB-based kernel primitives
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
TL;DR
- Linux >=6.9 wprowadził wadliwą refaktoryzację
manage_oob()(5aa57d9f2d53) dla obsługi AF_UNIXMSG_OOB. Ułożone po sobie zero-długościowe SKB ominęły logikę czyszczącąu->oob_skb, więc normalnerecv()mogło zwolnić out-of-band SKB podczas gdy wskaźnik pozostał żywy, prowadząc do CVE-2025-38236. - Ponowne wywołanie
recv(..., MSG_OOB)dereferencuje wiszącąstruct sk_buff. ZMSG_PEEKścieżkaunix_stream_recv_urg() -> __skb_datagram_iter() -> copy_to_user()staje się stabilnym 1-bajtowym arbitralnym odczytem jądra; bezMSG_PEEKprymityw inkrementujeUNIXCB(oob_skb).consumedna offsetcie0x44, czyli dodaje +4 GiB do górnego dwordu dowolnej 64-bitowej wartości umieszczonej na offsetcie0x40wewnątrz realokowanego obiektu. - Poprzez opróżnienie stron nieprzenoszalnych rzędu 0/1 (page-table spray), wymuszone zwolnienie strony składającej się z SKB do buddy allocator, i ponowne użycie fizycznej strony jako pipe buffer, exploit fałszuje metadane SKB w kontrolowanej pamięci, żeby zidentyfikować wiszącą stronę i przekierować prymityw odczytu do
.data, vmemmap, per-CPU oraz regionów page-table mimo usercopy hardening. - Ta sama strona może później zostać ponownie użyta jako górna strona stosu jądra świeżo sklonowanego wątku.
CONFIG_RANDOMIZE_KSTACK_OFFSETstaje się oraclem: sondując układ stosu podczas blokadypipe_write(), atakujący czeka aż rozlany przezcopy_page_from_iter()length (R14) wyląduje pod offsetem0x40, po czym odpala inkrementację +4 GiB by uszkodzić wartość na stosie. - Samoopierający się
skb_shinfo()->frag_listutrzymuje UAF syscall w pętli w przestrzeni jądra dopóki współpracujący wątek nie zablokujecopy_from_iter()(przezmprotect()nad VMA zawierającym pojedynczą dziuręMADV_DONTNEED). Złamanie pętli zwalnia inkrementację dokładnie wtedy, gdy cel na stosie jest aktywny, zwiększając argumentbytestak, żecopy_page_from_iter()zapisuje poza stroną pipe buffer do następnej fizycznej strony. - Monitorując PFN-y pipe-buffer i page table za pomocą prymitywu odczytu, atakujący zapewnia, że kolejna strona jest stroną PTE, konwertuje OOB copy w arbitralne zapisy PTE i uzyskuje nieograniczony kernel R/W/X. Chrome ograniczył dostępność przez zablokowanie
MSG_OOBz rendererów (6711812), a Linux naprawił błąd logiczny w32ca245464e1oraz wprowadziłCONFIG_AF_UNIX_OOB, by uczynić funkcję opcjonalną.
Root cause: manage_oob() assumes only one zero-length SKB
unix_stream_read_generic() oczekuje, że każde SKB zwrócone przez manage_oob() będzie miało unix_skb_len() > 0. Po 93c99f21db36, manage_oob() pominął ścieżkę czyszczenia skb == u->oob_skb za każdym razem, gdy najpierw usuwał zero-długościowe SKB pozostawione przez recv(MSG_OOB). Następna poprawka (5aa57d9f2d53) dalej przechodziła od pierwszego zero-długościowego SKB do skb_peek_next() bez ponownego sprawdzenia długości. Przy dwóch kolejnych zero-długościowych SKB funkcja zwracała drugi pusty SKB; unix_stream_read_generic() następnie go pominął bez ponownego wywołania manage_oob(), więc prawdziwe OOB SKB zostało zdequeue’owane i zwolnione podczas gdy u->oob_skb nadal na nie wskazywał.
Minimalna sekwencja wyzwalająca
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
Primitives exposed by unix_stream_recv_urg()
- 1-byte arbitrary read (repeatable):
state->recv_actor()ostatecznie wykonujecopy_to_user(user, skb_sourced_addr, 1). Jeśli dangling SKB zostanie realokowany w pamięć kontrolowaną przez atakującego (lub do kontrolowanego aliasu, np. pipe page), każderecv(MSG_OOB | MSG_PEEK)kopiuje bajt z dowolnego adresu jądra dopuszczonego przez__check_object_size()do przestrzeni użytkownika bez wywoływania crasha. UtrzymanieMSG_PEEKpozwala zachować dangling pointer dla nieograniczonych odczytów. - Constrained write: Gdy
MSG_PEEKjest wyczyszczony,UNIXCB(oob_skb).consumed += 1inkrementuje 32-bitowe pole pod offsetem0x44. Przy alokacjach SKB wyrównanych do 0x100 pole to znajduje się cztery bajty nad słowem wyrównanym do 8 bajtów, zamieniając prymityw w inkrementację +4 GiB słowa znajdującego się pod offsetem0x40. Aby przekształcić to w zapis jądrowy, trzeba umieścić wrażliwą 64-bitową wartość pod tym offsetem.
Reallocating the SKB page for arbitrary read
- Drain order-0/1 unmovable freelists: Zmapuj ogromne read-only anonymous VMA i faultuj każdą stronę, aby wymusić alokację page-table (order-0 unmovable). Wypełnienie ~10% RAMu tablicami stron gwarantuje, że późniejsze alokacje
skbuff_head_cachebędą pobierać świeże buddy pages po wyczerpaniu list order-0. - Spray SKBs and isolate a slab page: Użyj dziesiątek stream socketpairów i umieść setki małych wiadomości na każdym sockecie (~0x100 bytes na SKB), aby zapełnić
skbuff_head_cache. Zwolnij wybrane SKBy, aby doprowadzić do sytuacji, gdzie docelowa strona slab jest całkowicie pod kontrolą atakującego i monitoruj jejstruct pagerefcount za pomocą powstającego prymitywu odczytu. - Return the slab page to the buddy allocator: Zwolnij wszystkie obiekty na stronie, następnie wykonaj wystarczającą liczbę dodatkowych alokacji/zwolnień, aby wypchnąć stronę z SLUB per-CPU partial lists i per-CPU page lists, tak by stała się stroną order-1 na buddy freelist.
- Reallocate as pipe buffer: Utwórz setki pipe’ów; każdy pipe rezerwuje co najmniej dwie strony danych o rozmiarze 0x1000 (
PIPE_MIN_DEF_BUFFERS). Gdy buddy allocator rozdziela stronę order-1, jedna połowa może ponownie użyć zwolnionej strony SKB. Aby zlokalizować, który pipe i który offset aliasujeoob_skb, zapisz unikalne marker-byty w fake SKBach umieszczonych w stronach pipe i wywołuj powtarzalnierecv(MSG_OOB | MSG_PEEK)aż marker zostanie zwrócony. - Forge a stable SKB layout: Wypełnij aliasowaną stronę pipe fałszywym
struct sk_buff, którego poladata/headoraz strukturaskb_shared_infowskazują na dowolne interesujące adresy jądra. Ponieważ x86_64 wyłącza SMAP wewnątrzcopy_to_user(), adresy w trybie użytkownika mogą służyć jako staging buffers, dopóki nie poznasz dokładnych wskaźników jądrowych. - Respect usercopy hardening: Kopia powiedzie się względem
.data/.bss, wpisów vmemmap, zakresów per-CPU vmalloc, stosów jądra innych wątków oraz stron direct-map, które nie rozciągają się przez granice wyższych folio. Odczyty względem.textlub specjalizowanych cache’ów odrzucanych przez__check_heap_object()zwracają po prostu-EFAULTbez zabijania procesu.
Introspecting allocators with the read primitive
- Break KASLR: Odczytaj dowolny deskryptor IDT z fixed mapping pod
CPU_ENTRY_AREA_RO_IDT_VADDR(0xfffffe0000000000) i odejmij znany offset handlera, aby odzyskać kernel base. - SLUB/buddy state: Globalne symbole
.dataujawniają bazykmem_cache, podczas gdy wpisy vmemmap eksponują flagi typu każdej strony, pointer na freelist i owning cache. Skanowanie per-CPU vmalloc segmentów ujawnia instancjestruct kmem_cache_cpu, dzięki czemu następny adres alokacji kluczowych cache’y (np.skbuff_head_cache,kmalloc-cg-192) staje się przewidywalny. - Page tables: Zamiast czytać
mm_struct(blokowane przez usercopy), przejdź listępgd_list(struct ptdesc) i dopasuj bieżącemm_structprzezcpu_tlbstate.loaded_mm. Gdy rootpgdjest znany, prymityw może przemierzyć każdą tabelę stron, aby zmapować PFN-y dla pipe buffers, page tables i stosów jądra.
Recycling the SKB page as the top kernel-stack page
- Zwolnij kontrolowaną stronę pipe ponownie i potwierdź przez vmemmap, że jej refcount wraca do zera.
- Natychmiast alokuj cztery pomocnicze strony pipe, a potem zwolnij je w odwrotnej kolejności, tak aby zachowanie LIFO buddy allocator było deterministyczne.
- Wywołaj
clone()aby utworzyć pomocniczy wątek; stosy Linuxa są czterostronicowe na x86_64, więc cztery ostatnio zwolnione strony staną się jego stosem, przy czym ostatnio zwolniona strona (była strona SKB) znajdzie się pod najwyższymi adresami. - Zweryfikuj przez page-table walk, że top stack PFN pomocniczego wątku równa się zrecyklingowanemu SKB PFN.
- Użyj arbitralnego odczytu, aby obserwować układ stosu, jednocześnie sterując wątkiem do
pipe_write().CONFIG_RANDOMIZE_KSTACK_OFFSETodejmuje losowy 0x0–0x3f0 (wyrównany) odRSPprzy każdym syscall; powtarzane zapisy w połączeniu zpoll()/read()z innego wątku ujawniają, kiedy writer blokuje się z pożądanym offsetem. Gdy będzie szczęście, przelany argumentcopy_page_from_iter()bytes(R14) znajdzie się pod offsetem0x40wewnątrz zrecyklingowanej strony.
Placing fake SKB metadata on the stack
- Użyj
sendmsg()na AF_UNIX datagram socket: kernel kopiuje usersockaddr_undo stosowo-rezydentnegosockaddr_storage(do 108 bytes) oraz ancillary data do innego on-stack buffer przed zablokowaniem syscalla oczekującego na miejsce w kolejce. To pozwala zasadzić precyzyjną fałszywą strukturę SKB w pamięci stosu. - Wykryj, kiedy kopiowanie się skończyło, dostarczając 1-bajtowy control message umieszczony na niemapowanej stronie użytkownika;
____sys_sendmsg()wymusi jej fault, więc pomocniczy wątek pollingującymincore()na tym adresie dowie się, kiedy docelowa strona jest obecna. - Zerowe wypełnienie paddingu z
CONFIG_INIT_STACK_ALL_ZEROwygodnie uzupełnia nieużywane pola, kończąc poprawny nagłówek SKB bez dodatkowych zapisów.
Timing the +4 GiB increment with a self-looping frag list
- Sfałszuj
skb_shinfo(fakeskb)->frag_list, aby wskazywał na drugi fałszywy SKB (przechowywany w pamięci użytkownika kontrolowanej przez atakującego) zlen = 0inext = &self. Gdyskb_walk_frags()iteruje tę listę wewnątrz__skb_datagram_iter(), wykonanie zablokuje się w nieskończonej pętli, ponieważ iterator nigdy nie osiągaNULL, a pętla kopiująca nie robi postępu. - Utrzymaj recv syscall uruchomiony wewnątrz jądra, pozwalając drugiemu fałszywemu SKB na self-loop. Gdy nadejdzie czas na wystrzelenie inkrementacji, po prostu zmień
nextdrugiego SKB z przestrzeni użytkownika naNULL. Pętla wyjdzie, aunix_stream_recv_urg()natychmiast wykonaUNIXCB(oob_skb).consumed += 1raz, wpływając na dowolny obiekt aktualnie zajmujący zrecyklingowaną stronę stosu pod offsetem0x40.
Stalling copy_from_iter() without userfaultfd
- Zmapuj gigantyczne anonimowe RW VMA i wczytaj je w całości.
- Wykonaj jednodonicową dziurę przez
madvise(MADV_DONTNEED, hole, PAGE_SIZE)i umieść ten adres wiov_iterużytym dlawrite(pipefd, user_buf, 0x3000). - Równolegle wywołaj
mprotect()na całym VMA z innego wątku. Syscall ten zdobywa mmap write lock i przebiega przez każdy PTE. Gdy pipe writer osiągnie dziurę, handler page fault blokuje się na mmap lock trzymanym przezmprotect(), zatrzymująccopy_from_iter()w deterministycznym punkcie, podczas gdy przelanybytespozostaje na segmencie stosu hostowanym przez zrecyklingowaną stronę SKB.
Turning the increment into arbitrary PTE writes
- Fire the increment: Zwolnij frag loop, gdy
copy_from_iter()jest wstrzymany, tak aby inkrementacja +4 GiB uderzyła w zmiennąbytes. - Overflow the copy: Gdy fault zostanie wznowiony,
copy_page_from_iter()uwierzy, że może skopiować >4 GiB do bieżącej strony pipe. Po wypełnieniu legalnych 0x2000 bajtów (dwóch pipe buffers) wykona kolejną iterację i zapisze pozostałe dane użytkownika do fizycznej strony następującej po PFN-ie bufora pipe. - Arrange adjacency: Korzystając z telemetrii allocatorów, zmuszaj buddy allocator, aby umieścił stronę PTE należącą do procesu bezpośrednio po docelowej stronie bufora pipe (np. naprzemiennie alokując strony pipe i dotykając nowe zakresy wirtualne, by wymusić alokację page-table aż PFN-y zalignują się w tym samym 2 MiB pageblock).
- Overwrite page tables: Zakoduj pożądane wpisy PTE w dodatkowych 0x1000 bajtach danych użytkownika, tak aby OOB
copy_from_iter()wypełnił sąsiednią stronę wpisami wybranymi przez atakującego, dając RW/RWX mapowania pamięci fizycznej jądra w przestrzeni użytkownika lub nadpisując istniejące wpisy w celu wyłączenia SMEP/SMAP.
Mitigations / hardening ideas
- Kernel: Apply
32ca245464e1479bfea8592b9db227fdc1641705(properly revalidates SKBs) i rozważyć wyłączenie AF_UNIX OOB całkowicie, chyba że jest to absolutnie potrzebne, przezCONFIG_AF_UNIX_OOB(5155cbcdbf03). Wzmocnićmanage_oob()dodatkowymi sanity checks (np. pętlą ażunix_skb_len() > 0) i audytować inne protokoły socketów pod kątem podobnych założeń. - Sandboxing: Filtruj flagi
MSG_OOB/MSG_PEEKw profilach seccomp lub w wyższych-level broker API (zmiana w Chrome6711812teraz blokuje renderer-sideMSG_OOB). - Allocator defenses: Wzmocnienie randomizacji freelist w SLUB lub wymuszenie per-cache page coloring utrudniłoby deterministyczny recycling stron; ograniczenie maksymalnej liczby buforów pipe także zmniejsza niezawodność realokacji.
- Monitoring: Eksponuj wysoką częstotliwość alokacji page-table lub nienormalne użycie pipe przez telemetrię — ten exploit zużywa duże ilości page tables i pipe buffers.
References
- Project Zero – “From Chrome renderer code exec to kernel with MSG_OOB”
- Linux fix for CVE-2025-38236 (
manage_oobrevalidation) - Chromium CL 6711812 – block
MSG_OOBin renderers - Commit adding
CONFIG_AF_UNIX_OOBprompt
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


