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.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
TL;DR
- Το Linux >=6.9 εισήγαγε μια ελαττωματική refactor του
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 byte στον kernel· χωρίςMSG_PEEKτο primitive αυξάνει τοUNIXCB(oob_skb).consumedστη μετατόπιση0x44, δηλαδή προσθέτει +4 GiB στο ανώτερο dword οποιασδήποτε 64-bit τιμής τοποθετημένης στη μετατόπιση0x40μέσα στο επανακατανεμημένο αντικείμενο. - Με το να εξαντλήσει τις unmovable σελίδες order-0/1 (page-table spray), να αναγκάσει την απελευθέρωση μιας σελίδας slab SKB στον buddy allocator, και να επαναχρησιμοποιήσει τη φυσική σελίδα ως pipe buffer, το exploit ψεύδει τα SKB metadata σε ελεγχόμενη μνήμη για να εντοπίσει τη κρεμασμένη σελίδα και να μεταστρέψει το read primitive στο
.data, το vmemmap, per-CPU και περιοχές page-table παρά την hardening του usercopy. - Η ίδια σελίδα μπορεί αργότερα να ανακυκλωθεί ως η κορυφαία σελίδα kernel-stack ενός πρόσφατα cloned thread. Το
CONFIG_RANDOMIZE_KSTACK_OFFSETγίνεται oracle: με διερεύνηση της διάταξης του stack ενώ τοpipe_write()μπλοκάρεται, ο επιτιθέμενος περιμένει μέχρι το spilled μήκος τουcopy_page_from_iter()(R14) να βρεθεί στη μετατόπιση0x40, και στη συνέχεια πυροδοτεί την αύξηση +4 GiB για να καταστρέψει την τιμή στο stack. - Μια αυτο-επαναλαμβανόμενη
skb_shinfo()->frag_listκρατάει το UAF syscall να περιστρέφεται στον kernel μέχρι ένα συνεργαζόμενο thread να σταματήσει τοcopy_from_iter()(μέσωmprotect()πάνω σε ένα VMA που περιέχει μια μονή τρύπαMADV_DONTNEED). Η διάρρηξη του βρόχου απελευθερώνει την αύξηση ακριβώς όταν ο stack στόχος είναι ενεργός, φουσκώνοντας το όρισμαbytesέτσι ώστε τοcopy_page_from_iter()να γράψει πέρα από τη σελίδα pipe buffer στην επόμενη φυσική σελίδα. - Παρακολουθώντας τα PFN του pipe-buffer και τους πίνακες σελίδων με το read primitive, ο επιτιθέμενος διασφαλίζει ότι η επόμενη σελίδα είναι σελίδα PTE, μετατρέπει το OOB copy σε αυθαίρετες εγγραφές PTE και αποκτά απεριόριστο kernel read/write/execute. Το Chrome μείωσε τη δυνατότητα εκμετάλλευσης εμποδίζοντας το
MSG_OOBαπό renderers (6711812), και το Linux διόρθωσε το λογικό σφάλμα στο32ca245464e1και εισήγαγε τοCONFIG_AF_UNIX_OOBγια να κάνει το feature προαιρετικό.
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
Βασικές λειτουργίες που εκτίθενται από unix_stream_recv_urg()
- 1-byte arbitrary read (repeatable): Το
state->recv_actor()τελικά εκτελείcopy_to_user(user, skb_sourced_addr, 1). Αν το dangling SKB επανακατανεμηθεί σε μνήμη ελεγχόμενη από τον επιτιθέμενο (ή σε ένα ελεγχόμενο alias όπως μια pipe page), κάθεrecv(MSG_OOB | MSG_PEEK)αντιγράφει ένα byte από μια αυθαίρετη διεύθυνση kernel που επιτρέπεται από__check_object_size()στον χώρο του χρήστη χωρίς crash. Κρατώντας τοMSG_PEEKενεργό διατηρείται ο dangling pointer για απεριόριστες αναγνώσεις. - Constrained write: Όταν το
MSG_PEEKείναι απενεργοποιημένο, τοUNIXCB(oob_skb).consumed += 1αυξάνει το 32-bit πεδίο στη μετατόπιση0x44. Σε 0x100-ευθυγραμμισμένες SKB allocations αυτό βρίσκεται τέσσερα bytes πάνω από μια 8-byte ευθυγραμμισμένη λέξη, μετατρέποντας το primitive σε μια αύξηση +4 GiB της λέξης που φιλοξενείται στη μετατόπιση0x40. Για να μετατραπεί αυτό σε kernel write απαιτείται η τοποθέτηση μιας ευαίσθητης 64-bit τιμής σε εκείνη τη μετατόπιση.
Reallocating the SKB page for arbitrary read
- Drain order-0/1 unmovable freelists: Mapάρετε ένα τεράστιο read-only anonymous VMA και faultάρετε κάθε σελίδα για να αναγκάσετε allocation page-tables (order-0 unmovable). Γεμίζοντας ~10% της RAM με page tables εξασφαλίζει ότι οι επόμενες
skbuff_head_cacheallocations θα πάρουν φρέσκες buddy σελίδες όταν εξαντληθούν οι order-0 λίστες. - Spray SKBs and isolate a slab page: Χρησιμοποιήστε δεκάδες stream socketpairs και ουράροντας εκατοντάδες μικρά μηνύματα ανά socket (~0x100 bytes ανά SKB) για να γεμίσετε το
skbuff_head_cache. Αποδεσμεύστε επιλεγμένα SKBs για να οδηγήσετε μια στοχευμένη slab σελίδα εξ ολοκλήρου υπό τον έλεγχο του επιτιθέμενου και παρακολουθήστε τοstruct pagerefcount της μέσω του αναδυόμενου read primitive. - Return the slab page to the buddy allocator: Ελευθερώστε κάθε αντικείμενο στη σελίδα, μετά εκτελέστε αρκετές επιπλέον allocations/frees ώστε να ωθήσετε τη σελίδα έξω από τις SLUB per-CPU partial λίστες και τις per-CPU page λίστες ώστε να γίνει μια order-1 σελίδα στη buddy freelist.
- Reallocate as pipe buffer: Δημιουργήστε εκατοντάδες pipes· κάθε pipe δεσμεύει τουλάχιστον δύο 0x1000-byte data pages (
PIPE_MIN_DEF_BUFFERS). Όταν ο buddy allocator σπάσει μια order-1 σελίδα, το ένα μισό επαναχρησιμοποιεί την ελευθερωμένη SKB σελίδα. Για να βρείτε ποιο pipe και ποιο offset aliased τοoob_skb, γράψτε μοναδικά marker bytes μέσα σε fake SKBs αποθηκευμένα σε όλη τη pipe page και εκτελέστε επαναλαμβανόμεναrecv(MSG_OOB | MSG_PEEK)μέχρι να επιστραφεί ο marker. - Forge a stable SKB layout: Γεμίστε την aliased pipe page με ένα fake
struct sk_buffτου οποίου ταdata/headpointers και η δομήskb_shared_infoδείχνουν σε αυθαίρετες διευθύνσεις kernel ενδιαφέροντος. Επειδή το x86_64 απενεργοποιεί SMAP μέσα σεcopy_to_user(), διευθύνσεις user-mode μπορούν να χρησιμεύσουν ως staging buffers μέχρι να γίνουν γνωστοί οι kernel pointers. - Respect usercopy hardening: Η copy επιτυγχάνει απέναντι σε
.data/.bss, vmemmap entries, per-CPU vmalloc ranges, stacks άλλων threads του kernel, και direct-map σελίδες που δεν διασχίζουν όρια υψηλότερης τάξης folio. Οι αναγνώσεις απέναντι σε.textή εξειδικευμένα caches που απορρίπτονται από__check_heap_object()απλώς επιστρέφουν-EFAULTχωρίς να σκοτώσουν τη διαδικασία.
Introspecting allocators with the read primitive
- Break KASLR: Διαβάστε οποιοδήποτε IDT descriptor από το fixed mapping στο
CPU_ENTRY_AREA_RO_IDT_VADDR(0xfffffe0000000000) και αφαιρέστε το γνωστό handler offset για να ανακτήσετε τη βάση του kernel. - SLUB/buddy state: Παγκόσμια
.dataσύμβολα αποκαλύπτουν τις βάσεις τωνkmem_cache, ενώ τα vmemmap entries εκθέτουν τα flags τύπου κάθε σελίδας, pointer freelist, και την owning cache. Σκανάροντας τα per-CPU vmalloc segments εντοπίζονταιstruct kmem_cache_cpuinstances ώστε η επόμενη διεύθυνση allocation των κλειδιών cache (π.χ.skbuff_head_cache,kmalloc-cg-192) να γίνει προβλέψιμη. - Page tables: Αντί να διαβάσετε
mm_struct(μπλοκάρεται από usercopy), περπατήστε το globalpgd_list(struct ptdesc) και ταιριάξτε το τρέχονmm_structμέσωcpu_tlbstate.loaded_mm. Μόλις γίνει γνωστό το rootpgd, το primitive μπορεί να διασχίσει κάθε page table για να αντιστοιχίσει PFNs για pipe buffers, page tables, και kernel stacks.
Recycling the SKB page as the top kernel-stack page
- Αποδεσμεύστε ξανά την ελεγχόμενη pipe page και επιβεβαιώστε μέσω vmemmap ότι το refcount της επιστρέφει στο μηδέν.
- Αμέσως allocate τέσσερις βοηθητικές pipe pages και μετά αποδεσμεύστε τες σε αντίστροφη σειρά ώστε η LIFO συμπεριφορά του buddy allocator να είναι ντετερμινιστική.
- Καλέστε
clone()για να δημιουργήσετε ένα helper thread· οι Linux stacks είναι τέσσερις σελίδες στο x86_64, οπότε οι τέσσερις πιο πρόσφατα ελευθερωμένες σελίδες γίνονται το stack του, με την τελευταία ελευθερωμένη σελίδα (η πρώην SKB page) στη μεγαλύτερη διεύθυνση. - Επαληθεύστε μέσω page-table walk ότι το top stack PFN του helper thread ισούται με το ανακυκλωμένο SKB PFN.
- Χρησιμοποιήστε το arbitrary read για να παρατηρήσετε τη διάταξη του stack ενώ κατευθύνετε το thread σε
pipe_write(). ΤοCONFIG_RANDOMIZE_KSTACK_OFFSETαφαιρεί ένα τυχαίο 0x0–0x3f0 (ευθυγραμμισμένο) από τοRSPανά syscall· επαναλαμβανόμενες εγγραφές συνδυασμένες μεpoll()/read()από άλλο thread αποκαλύπτουν πότε ο writer μπλοκάρει με το επιθυμητό offset. Όταν τύχει, το spilledcopy_page_from_iter()bytesόρισμα (R14) κάθεται στη μετατόπιση0x40μέσα στην ανακυκλωμένη σελίδα.
Placing fake SKB metadata on the stack
- Χρησιμοποιήστε
sendmsg()σε AF_UNIX datagram socket: ο kernel αντιγράφει το usersockaddr_unσε ένα stack-residentsockaddr_storage(έως 108 bytes) και τα ancillary data σε άλλο on-stack buffer πριν το syscall μπλοκάρει περιμένοντας χώρο στην ουρά. Αυτό επιτρέπει τη φύτευση μιας ακριβούς fake SKB δομής στη μνήμη του stack. - Εντοπίστε πότε ολοκληρώθηκε η αντιγραφή παρέχοντας ένα 1-byte control message τοποθετημένο σε μια unmapped user σελίδα· το
____sys_sendmsg()θα το faultάρει μέσα, οπότε ένα helper thread που κάνει polling μεmincore()σε εκείνη τη διεύθυνση μαθαίνει πότε η προοριζόμενη σελίδα είναι παρούσα. - Το μηδενικοποιημένο padding από το
CONFIG_INIT_STACK_ALL_ZEROσυμπληρώνει βολικά πεδία που δεν χρησιμοποιούνται, ολοκληρώνοντας ένα έγκυρο SKB header χωρίς επιπλέον εγγραφές.
Timing the +4 GiB increment with a self-looping frag list
- Forgeάρετε
skb_shinfo(fakeskb)->frag_listώστε να δείχνει σε ένα δεύτερο fake SKB (αποθηκευμένο σε μνήμη user που ελέγχεται από τον επιτιθέμενο) που έχειlen = 0καιnext = &self. Όταν τοskb_walk_frags()επαναλαμβάνει αυτή τη λίστα μέσα σε__skb_datagram_iter(), η εκτέλεση περιστρέφεται επ’ αόριστον γιατί ο iterator δεν φτάνει ποτέ σεNULLκαι το copy loop δεν προχωρά. - Κρατήστε το recv syscall να τρέχει μέσα στον kernel αφήνοντας το δεύτερο fake SKB να κάνει self-loop. Όταν είναι ώρα να πυροδοτήσετε την αύξηση, απλώς αλλάξτε τον
nextpointer του δεύτερου SKB από user space σεNULL. Το loop τερματίζει και τοunix_stream_recv_urg()εκτελεί αμέσωςUNIXCB(oob_skb).consumed += 1μία φορά, επηρεάζοντας όποιο αντικείμενο βρίσκεται αυτήν τη στιγμή στη ανακυκλωμένη stack page στη μετατόπιση0x40.
Stalling copy_from_iter() without userfaultfd
- Mapάρετε μια γιγάντια anonymous RW VMA και faultάρετε την πλήρως.
- Κάντε μια single-page τρύπα με
madvise(MADV_DONTNEED, hole, PAGE_SIZE)και τοποθετήστε αυτή τη διεύθυνση μέσα στοiov_iterπου χρησιμοποιείται γιαwrite(pipefd, user_buf, 0x3000). - Παράλληλα, καλέστε
mprotect()στην ολόκληρη VMA από άλλο thread. Το syscall παίρνει το mmap write lock και περπατά κάθε PTE. Όταν ο pipe writer φτάσει την τρύπα, ο page fault handler μπλοκάρει πάνω στο mmap lock που κρατείται από τοmprotect(), παγώνοντας τοcopy_from_iter()σε ένα ντετερμινιστικό σημείο ενώ το spilledbytesvalue βρίσκεται στο stack segment που φιλοξενείται από την ανακυκλωμένη SKB page.
Turning the increment into arbitrary PTE writes
- Fire the increment: Απελευθερώστε το frag loop ενώ το
copy_from_iter()είναι σταματημένο ώστε η +4 GiB αύξηση να χτυπήσει τη μεταβλητήbytes. - Overflow the copy: Μόλις το fault συνεχιστεί, το
copy_page_from_iter()πιστεύει ότι μπορεί να αντιγράψει >4 GiB μέσα στην τρέχουσα pipe page. Αφού γεμίσει τα νόμιμα 0x2000 bytes (δύο pipe buffers), εκτελεί άλλη μια επανάληψη και γράφει τα υπόλοιπα δεδομένα χρήστη στην φυσική σελίδα που ακολουθεί το PFN του pipe buffer. - Arrange adjacency: Χρησιμοποιώντας telemetry allocator, αναγκάστε τον buddy allocator να τοποθετήσει μια process-owned PTE page αμέσως μετά την στοχευόμενη pipe buffer page (π.χ. εναλλάσσοντας allocations pipe pages και αγγίζοντας νέα virtual ranges για να προκαλέσετε page-table allocation μέχρι να ευθυγραμμιστούν τα PFNs μέσα στο ίδιο 2 MiB pageblock).
- Overwrite page tables: Κωδικοποιήστε τις επιθυμητές εγγραφές PTE στα επιπλέον 0x1000 bytes των δεδομένων χρήστη ώστε το OOB
copy_from_iter()να γεμίσει τη διπλανή σελίδα με εγγραφές που επιλέγει ο επιτιθέμενος, παρέχοντας RW/RWX user mappings φυσικής μνήμης του kernel ή επανεγγράφοντας υπάρχουσες εγγραφές ώστε να απενεργοποιηθούν SMEP/SMAP.
Mitigations / hardening ideas
- Kernel: Εφαρμόστε το
32ca245464e1479bfea8592b9db227fdc1641705(επαληθεύει σωστά ξανά τα SKBs) και εξετάστε την απενεργοποίηση του AF_UNIX OOB εντελώς εκτός αν είναι απολύτως απαραίτητο μέσωCONFIG_AF_UNIX_OOB(5155cbcdbf03). Σκληρύνετε τοmanage_oob()με πρόσθετους ελέγχους ορθότητας (π.χ., loop μέχριunix_skb_len() > 0) και ελέγξτε άλλα socket πρωτόκολλα για παρόμοιες υποθέσεις. - Sandboxing: Φιλτράρετε τα flags
MSG_OOB/MSG_PEEKσε seccomp profiles ή σε υψηλότερου επιπέδου broker APIs (η αλλαγή του Chrome6711812τώρα μπλοκάρει το renderer-sideMSG_OOB). - Allocator defenses: Ενίσχυση της SLUB freelist randomization ή επιβολή per-cache page coloring θα περιέπλεκε την ντετερμινιστική ανακύκλωση σελίδων· ο περιορισμός του αριθμού των pipe buffers επίσης μειώνει την αξιοπιστία της επανακατανομής.
- Monitoring: Εκθέστε υψηλό ρυθμό page-table allocation ή ασυνήθιστη χρήση pipe μέσω telemetry—αυτό το exploit καταναλώνει μεγάλες ποσότητες page tables και 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.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


