AF_UNIX MSG_OOB UAF & SKB-based kernel primitives
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Коротко
- Linux >=6.9 ввів некоректний рефакторинг
manage_oob()(5aa57d9f2d53) для обробки AF_UNIXMSG_OOB. Послідовні нуль-байтові SKB обійшли логіку, яка очищаєu->oob_skb, тож звичайнийrecv()міг звільнити out-of-band SKB, поки вказівник залишався живим, що призвело до CVE-2025-38236. - Повторний виклик
recv(..., MSG_OOB)дереференсує підвислийstruct sk_buff. ЗMSG_PEEKшляхunix_stream_recv_urg() -> __skb_datagram_iter() -> copy_to_user()стає стабільним 1-байтовим довільним читанням ядра; безMSG_PEEKпримітив інкрементуєUNIXCB(oob_skb).consumedна зсуві0x44, тобто додає +4 GiB до верхнього dword будь-якого 64-бітного значення, розміщеного на зсуві0x40всередині переалокованого об’єкта. - Через спорожнення сторінок незрушуваних порядків 0/1 (page-table spray), примусове звільнення SKB slab-сторінки в buddy allocator і повторне використання фізичної сторінки як pipe buffer, експлойт підробляє метадані SKB у керованій пам’яті, щоб ідентифікувати підвислу сторінку і повести примітив читання у
.data, vmemmap, per-CPU та області таблиць сторінок незважаючи на usercopy hardening. - Та сама сторінка пізніше може бути перероблена як верхня сторінка ядрового стека щойно клонованого потоку.
CONFIG_RANDOMIZE_KSTACK_OFFSETстає ореклом: досліджуючи структуру стека покиpipe_write()блокується, атакуючий чекає, поки розлитийcopy_page_from_iter()length (R14) опиниться на зсуві0x40, а потім запускає інкремент +4 GiB, щоб пошкодити значення стека. - Самопетлюючий
skb_shinfo()->frag_listтримає UAF-системний виклик у петлі в просторі ядра, поки співпрацюючий потік не затримаєcopy_from_iter()(черезmprotect()над VMA, що містить єдину діруMADV_DONTNEED). Розрив петлі звільняє інкремент точно тоді, коли ціль у стеку жива, збільшуючи аргументbytesтак, щоcopy_page_from_iter()записує поза сторінкою pipe buffer у наступну фізичну сторінку. - Моніторячи PFN-и pipe-buffer і таблиці сторінок за допомогою примітива читання, атакуючий впевнюється, що наступна сторінка є PTE-сторінкою, перетворює OOB copy у довільні PTE-записи і отримує необмежений kernel read/write/execute. Chrome знизив доступність, блокуючи
MSG_OOBз рендерерів (6711812), а Linux виправив логічну помилку в32ca245464e1та додавCONFIG_AF_UNIX_OOB, щоб зробити цю можливість опційною.
Root cause: manage_oob() assumes only one zero-length SKB
unix_stream_read_generic() очікує, що кожен SKB, повернений manage_oob(), має unix_skb_len() > 0. Після 93c99f21db36 manage_oob() пропускав шлях очищення skb == u->oob_skb щоразу, коли вперше видаляв нуль-байтовий SKB, залишений recv(MSG_OOB). Наступне виправлення (5aa57d9f2d53) все ще просувалося від першого нуль-байтового SKB до skb_peek_next() без повторної перевірки довжини. За наявності двох підряд нуль-байтових SKB функція повертала другий порожній SKB; unix_stream_read_generic() тоді пропускала його без повторного виклику manage_oob(), тож справжній OOB SKB був витягнутий з черги і звільнений, поки u->oob_skb іще вказував на нього.
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
Primitives exposed by unix_stream_recv_urg()
- 1-byte arbitrary read (repeatable):
state->recv_actor()ultimately performscopy_to_user(user, skb_sourced_addr, 1). If the dangling SKB is reallocated into attacker-controlled memory (or into a controlled alias such as a pipe page), everyrecv(MSG_OOB | MSG_PEEK)copies a byte from an arbitrary kernel address allowed by__check_object_size()to user space without crashing. KeepingMSG_PEEKset preserves the dangling pointer for unlimited reads. - Constrained write: When
MSG_PEEKis clear,UNIXCB(oob_skb).consumed += 1increments the 32-bit field at offset0x44. On 0x100-aligned SKB allocations this sits four bytes above an 8-byte aligned word, converting the primitive into a +4 GiB increment of the word hosted at offset0x40. Turning this into a kernel write requires positioning a sensitive 64-bit value at that offset.
Reallocating the SKB page for arbitrary read
- Drain order-0/1 unmovable freelists: Map a huge read-only anonymous VMA and fault every page to force page-table allocation (order-0 unmovable). Filling ~10% of RAM with page tables ensures subsequent
skbuff_head_cacheallocations pull fresh buddy pages once order-0 lists exhaust. - Spray SKBs and isolate a slab page: Use dozens of stream socketpairs and queue hundreds of small messages per socket (~0x100 bytes per SKB) to populate
skbuff_head_cache. Free chosen SKBs to drive a target slab page entirely under attacker control and monitor itsstruct pagerefcount via the emerging read primitive. - Return the slab page to the buddy allocator: Free every object on the page, then perform enough additional allocations/frees to push the page out of SLUB per-CPU partial lists and per-CPU page lists so it becomes an order-1 page on the buddy freelist.
- Reallocate as pipe buffer: Create hundreds of pipes; each pipe reserves at least two 0x1000-byte data pages (
PIPE_MIN_DEF_BUFFERS). When the buddy allocator splits an order-1 page, one half reuses the freed SKB page. To locate which pipe and which offset aliasesoob_skb, write unique marker bytes into fake SKBs stored throughout pipe pages and issue repeatedrecv(MSG_OOB | MSG_PEEK)calls until the marker is returned. - Forge a stable SKB layout: Populate the aliased pipe page with a fake
struct sk_buffwhosedata/headpointers andskb_shared_infostructure point to arbitrary kernel addresses of interest. Because x86_64 disables SMAP insidecopy_to_user(), user-mode addresses can serve as staging buffers until kernel pointers are known. - Respect usercopy hardening: The copy succeeds against
.data/.bss, vmemmap entries, per-CPU vmalloc ranges, other threads’ kernel stacks, and direct-map pages that do not straddle higher-order folio boundaries. Reads against.textor specialized caches rejected by__check_heap_object()simply return-EFAULTwithout killing the process.
Introspecting allocators with the read primitive
- Break KASLR: Read any IDT descriptor from the fixed mapping at
CPU_ENTRY_AREA_RO_IDT_VADDR(0xfffffe0000000000) and subtract the known handler offset to recover the kernel base. - SLUB/buddy state: Global
.datasymbols revealkmem_cachebases, while vmemmap entries expose each page’s type flags, freelist pointer, and owning cache. Scanning per-CPU vmalloc segments uncoversstruct kmem_cache_cpuinstances so the next allocation address of key caches (e.g.,skbuff_head_cache,kmalloc-cg-192) becomes predictable. - Page tables: Instead of reading
mm_struct(blocked by usercopy), walk the globalpgd_list(struct ptdesc) and match the currentmm_structviacpu_tlbstate.loaded_mm. Once the rootpgdis known, the primitive can traverse every page table to map PFNs for pipe buffers, page tables, and kernel stacks.
Recycling the SKB page as the top kernel-stack page
- Free the controlled pipe page again and confirm via vmemmap that its refcount returns to zero.
- Immediately allocate four helper pipe pages and then free them in reverse order so the buddy allocator’s LIFO behavior is deterministic.
- Call
clone()to spawn a helper thread; Linux stacks are four pages on x86_64, so the four most recently freed pages become its stack, with the last freed page (the former SKB page) at the highest addresses. - Verify via page-table walk that the helper thread’s top stack PFN equals the recycled SKB PFN.
- Use the arbitrary read to observe the stack layout while steering the thread into
pipe_write().CONFIG_RANDOMIZE_KSTACK_OFFSETsubtracts a random 0x0–0x3f0 (aligned) fromRSPper syscall; repeated writes combined withpoll()/read()from another thread reveal when the writer blocks with the desired offset. When lucky, the spilledcopy_page_from_iter()bytesargument (R14) sits at offset0x40inside the recycled page.
Placing fake SKB metadata on the stack
- Use
sendmsg()on an AF_UNIX datagram socket: the kernel copies the usersockaddr_uninto a stack-residentsockaddr_storage(up to 108 bytes) and the ancillary data into another on-stack buffer before the syscall blocks waiting for queue space. This allows planting a precise fake SKB structure in stack memory. - Detect when the copy finished by supplying a 1-byte control message located in an unmapped user page;
____sys_sendmsg()faults it in, so a helper thread pollingmincore()on that address learns when the destination page is present. - Zero-initialized padding from
CONFIG_INIT_STACK_ALL_ZEROconveniently fills unused fields, completing a valid SKB header without extra writes.
Timing the +4 GiB increment with a self-looping frag list
- Forge
skb_shinfo(fakeskb)->frag_listto point to a second fake SKB (stored in attacker-controlled user memory) that haslen = 0andnext = &self. Whenskb_walk_frags()iterates this list inside__skb_datagram_iter(), execution spins indefinitely because the iterator never reachesNULLand the copy loop makes no progress. - Keep the recv syscall running inside the kernel by letting the second fake SKB self-loop. When it’s time to fire the increment, simply change the second SKB’s
nextpointer from user space toNULL. The loop exits andunix_stream_recv_urg()immediately executesUNIXCB(oob_skb).consumed += 1once, affecting whatever object currently occupies the recycled stack page at offset0x40.
Stalling copy_from_iter() without userfaultfd
- Map a giant anonymous RW VMA and fault it in fully.
- Punch a single-page hole with
madvise(MADV_DONTNEED, hole, PAGE_SIZE)and place that address inside theiov_iterused forwrite(pipefd, user_buf, 0x3000). - In parallel, call
mprotect()on the entire VMA from another thread. The syscall grabs the mmap write lock and walks every PTE. When the pipe writer reaches the hole, the page fault handler blocks on the mmap lock held bymprotect(), pausingcopy_from_iter()at a deterministic point while the spilledbytesvalue resides on the stack segment hosted by the recycled SKB page.
Turning the increment into arbitrary PTE writes
- Fire the increment: Release the frag loop while
copy_from_iter()is stalled so the +4 GiB increment hits thebytesvariable. - Overflow the copy: Once the fault resumes,
copy_page_from_iter()believes it can copy >4 GiB into the current pipe page. After filling the legitimate 0x2000 bytes (two pipe buffers), it executes another iteration and writes the remaining user data into whatever physical page follows the pipe buffer PFN. - Arrange adjacency: Using allocator telemetry, force the buddy allocator to place a process-owned PTE page immediately after the target pipe buffer page (e.g., alternate between allocating pipe pages and touching new virtual ranges to trigger page-table allocation until the PFNs align inside the same 2 MiB pageblock).
- Overwrite page tables: Encode desired PTE entries in the extra 0x1000 bytes of user data so the OOB
copy_from_iter()fills the neighbouring page with attacker-chosen entries, granting RW/RWX user mappings of kernel physical memory or rewriting existing entries to disable SMEP/SMAP.
Mitigations / hardening ideas
- Kernel: Apply
32ca245464e1479bfea8592b9db227fdc1641705(properly revalidates SKBs) and consider disabling AF_UNIX OOB entirely unless strictly needed viaCONFIG_AF_UNIX_OOB(5155cbcdbf03). Hardenmanage_oob()with additional sanity checks (e.g., loop untilunix_skb_len() > 0) and audit other socket protocols for similar assumptions. - Sandboxing: Filter
MSG_OOB/MSG_PEEKflags in seccomp profiles or higher-level broker APIs (Chrome change6711812now blocks renderer-sideMSG_OOB). - Allocator defenses: Strengthening SLUB freelist randomization or enforcing per-cache page coloring would complicate deterministic page recycling; pipeline-limiting of pipe buffer counts also reduces reallocation reliability.
- Monitoring: Expose high-rate page-table allocation or abnormal pipe usage via telemetry—this exploit burns large amounts of page tables and 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
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


