AF_UNIX MSG_OOB UAF & SKB 기반 커널 프리미티브
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
TL;DR
- Linux >=6.9에서 AF_UNIX
MSG_OOB처리를 위한manage_oob()리팩터(5aa57d9f2d53)에 결함이 도입되었습니다. 연속된 제로-길이 SKB가u->oob_skb를 초기화하는 로직을 우회하여, 일반적인recv()가 포인터가 여전히 살아있는 동안 OOB SKB를 free할 수 있었고 그 결과 CVE-2025-38236이 발생했습니다. recv(..., MSG_OOB)를 재실행하면 danglingstruct sk_buff를 역참조합니다.MSG_PEEK가 있을 경우, 경로unix_stream_recv_urg() -> __skb_datagram_iter() -> copy_to_user()가 안정적인 1바이트 임의 커널 읽기가 됩니다;MSG_PEEK가 없으면 이 프리미티브는 재할당된 객체의 offset0x40에 놓인 64비트 값의 상위 dword에 +4 GiB를 더하는, offset0x44의UNIXCB(oob_skb).consumed를 증가시킵니다.- order-0/1 unmovable pages를 소진(page-table spray)하고 SKB slab 페이지를 buddy allocator로 강제 free한 뒤 물리 페이지를 pipe buffer로 재사용함으로써, 익스플로잇은 제어된 메모리에 SKB 메타데이터를 위조해 dangling 페이지를 식별하고 read 프리미티브를
.data, vmemmap, per-CPU, 페이지-테이블 영역으로 피벗시킵니다 — usercopy hardening에도 불구하고. - 동일한 페이지는 이후 새로 클론된 스레드의 최상위 커널 스택 페이지로 재활용될 수 있습니다.
CONFIG_RANDOMIZE_KSTACK_OFFSET는 오라클이 됩니다:pipe_write()가 블록되는 동안 스택 레이아웃을 탐지하여 공격자는 spilledcopy_page_from_iter()길이(R14)가 offset0x40에 위치할 때까지 기다린 후 +4 GiB 증가를 발사해 스택 값을 손상시킵니다. - 자체 루프를 도는
skb_shinfo()->frag_list는 협력 스레드가 단일MADV_DONTNEED홀이 있는 VMA에 대해mprotect()로copy_from_iter()를 멈출 때까지 UAF syscall을 커널 공간에서 반복시키게 합니다. 루프를 깨면 증가가 스택 타깃이 활성화된 정확한 시점에 해제되어bytes인자를 부풀리고copy_page_from_iter()가 pipe buffer 페이지를 넘어 다음 물리 페이지에 쓰게 만듭니다. - read 프리미티브로 pipe-buffer PFN과 페이지 테이블을 모니터링함으로써, 공격자는 다음 페이지가 PTE 페이지임을 확인하고 OOB 복사를 임의의 PTE 쓰기로 변환하여 무제한 커널 R/W/X를 획득합니다. Chrome은 렌더러에서
MSG_OOB를 차단하여 접근 가능성을 완화했으며(6711812), Linux는 로직 결함을32ca245464e1에서 수정하고CONFIG_AF_UNIX_OOB를 도입해 해당 기능을 선택형으로 만들었습니다.
근본 원인: manage_oob()는 하나의 제로-길이 SKB만 가정한다
unix_stream_read_generic()는 manage_oob()가 반환하는 모든 SKB가 unix_skb_len() > 0임을 기대합니다. 93c99f21db36 이후 manage_oob()는 recv(MSG_OOB)가 남긴 제로-길이 SKB를 처음 제거할 때마다 skb == u->oob_skb 정리 경로를 건너뛰었습니다. 이후의 수정(5aa57d9f2d53)은 여전히 첫 번째 제로-길이 SKB에서 skb_peek_next()로 넘어가면서 길이를 재확인하지 않았습니다. 두 개의 연속된 제로-길이 SKB가 있으면 함수는 두 번째 빈 SKB를 반환했고, unix_stream_read_generic()는 다시 manage_oob()를 호출하지 않고 이를 건너뛰었기 때문에 실제 OOB SKB가 dequeue되어 free되었는데 u->oob_skb는 여전히 이를 가리키고 있었습니다.
최소 트리거 시퀀스
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가 공격자가 제어하는 메모리(또는 pipe 페이지 같은 제어 가능한 alias)로 재할당되면, 매번recv(MSG_OOB | MSG_PEEK)가__check_object_size()로 허용되는 임의의 커널 주소에서 1바이트를 유저 공간으로 복사합니다(프로세스가 죽지 않음).MSG_PEEK을 유지하면 dangling 포인터를 보존하여 무제한으로 읽을 수 있습니다. - Constrained write:
MSG_PEEK가 비활성화되어 있을 때UNIXCB(oob_skb).consumed += 1은 오프셋0x44의 32비트 필드를 증가시킵니다. 0x100 정렬된 SKB 할당에서 이것은 8바이트 정렬된 단어보다 4바이트 위에 위치하여, 원시 프리미티브를 오프셋0x40에 호스팅된 워드의 +4 GiB 증가로 변환합니다. 이를 커널 쓰기로 바꾸려면 해당 오프셋에 민감한 64비트 값을 배치해야 합니다.
Reallocating the SKB page for arbitrary read
- Drain order-0/1 unmovable freelists: 거대한 읽기 전용 anonymous VMA를 매핑하고 모든 페이지를 fault 시켜 페이지 테이블 할당을 강제합니다 (order-0 unmovable). 대략 RAM의 10% 정도를 페이지 테이블로 채우면 이후
skbuff_head_cache할당이 order-0 리스트가 고갈된 후 신규 buddy 페이지를 가져오게 됩니다. - Spray SKBs and isolate a slab page: 여러 개의 stream socketpair를 사용하고 소켓당 수백 개의 작은 메시지(~0x100 바이트 per SKB)를 큐잉하여
skbuff_head_cache를 채웁니다. 선택한 SKB들을 해제해 목표 슬랩 페이지를 공격자 제어 하에 완전히 놓고, 읽기 프리미티브로struct pagerefcount를 모니터링합니다. - Return the slab page to the buddy allocator: 페이지의 모든 객체를 해제한 뒤 추가적인 할당/해제를 충분히 수행해 SLUB per-CPU partial 리스트와 per-CPU 페이지 리스트에서 페이지가 밀려나도록 하여 buddy freelist의 order-1 페이지가 되게 합니다.
- Reallocate as pipe buffer: 수백 개의 pipe를 생성합니다; 각 pipe는 최소 두 개의 0x1000-바이트 데이터 페이지(
PIPE_MIN_DEF_BUFFERS)를 예약합니다. buddy allocator가 order-1 페이지를 분할할 때, 한 쪽 절반이 해제된 SKB 페이지를 재사용합니다. 어떤 pipe와 어떤 오프셋이oob_skb와 alias 되는지 찾기 위해 파이프 페이지 전역에 가짜 SKB에 고유 마커 바이트를 써넣고 반복적으로recv(MSG_OOB | MSG_PEEK)을 호출해 마커가 반환될 때까지 확인합니다. - Forge a stable SKB layout: alias된 pipe 페이지를 가짜
struct sk_buff로 채워 그data/head포인터와skb_shared_info구조가 관심 있는 임의의 커널 주소를 가리키도록 합니다. x86_64에서copy_to_user()내부로 SMAP가 비활성화되므로, 커널 포인터가 알려질 때까지 유저 모드 주소를 스테이징 버퍼로 사용할 수 있습니다. - Respect usercopy hardening: 이 복사는
.data/.bss, vmemmap 항목, per-CPU vmalloc 범위, 다른 스레드의 커널 스택 및 고차수 folio 경계를 넘지 않는 direct-map 페이지에 대해서는 성공합니다..text나__check_heap_object()가 거부하는 특수 캐시를 대상으로 하는 읽기는 프로세스를 죽이지 않고 단순히-EFAULT를 반환합니다.
Introspecting allocators with the read primitive
- Break KASLR:
CPU_ENTRY_AREA_RO_IDT_VADDR(0xfffffe0000000000)의 고정 매핑에서 어떤 IDT 디스크립터든 읽고 알려진 핸들러 오프셋을 빼면 커널 베이스를 복구할 수 있습니다. - SLUB/buddy state: 글로벌
.data심볼은kmem_cache베이스를 드러내고, vmemmap 항목은 각 페이지의 타입 플래그, freelist 포인터 및 소유 캐시를 노출합니다. per-CPU vmalloc 세그먼트를 스캔하면struct kmem_cache_cpu인스턴스를 찾아 주요 캐시(skbuff_head_cache,kmalloc-cg-192등)의 다음 할당 주소를 예측할 수 있습니다. - Page tables:
mm_struct를 직접 읽는 대신(유저카피로 차단됨) 글로벌pgd_list(struct ptdesc)를 따라 현재mm_struct를cpu_tlbstate.loaded_mm으로 매치합니다. 루트pgd를 알게 되면, 이 프리미티브로 모든 페이지 테이블을 순회하여 pipe 버퍼, 페이지 테이블, 커널 스택의 PFN을 매핑할 수 있습니다.
Recycling the SKB page as the top kernel-stack page
- 제어하던 pipe 페이지를 다시 해제하고 vmemmap으로 그 refcount가 0으로 돌아오는지 확인합니다.
- 즉시 네 개의 헬퍼 파이프 페이지를 할당한 뒤 역순으로 해제해 buddy allocator의 LIFO 동작을 결정론적으로 만듭니다.
clone()으로 헬퍼 스레드를 생성합니다; x86_64에서 스택은 네 페이지이므로 최근에 해제된 네 페이지가 그 스레드의 스택이 되며, 마지막으로 해제된 페이지(이전의 SKB 페이지)는 높은 주소에 위치합니다.- 페이지 테이블 워크로 헬퍼 스레드의 최상위 스택 PFN이 재활용된 SKB PFN과 같은지 확인합니다.
- arbitrary read로 스택 레이아웃을 관찰하면서 스레드를
pipe_write()로 유도합니다.CONFIG_RANDOMIZE_KSTACK_OFFSET는 syscall마다RSP에서 정렬된 0x0–0x3f0 범위의 랜덤 값을 빼므로, 다른 스레드의poll()/read()와 결합한 반복적인 쓰기로 작성자가 원하는 오프셋에서 블록될 때를 찾아냅니다. 운이 좋으면, 흘러나온copy_page_from_iter()의bytes인자(R14)가 재활용된 페이지 내 오프셋0x40에 위치합니다.
Placing fake SKB metadata on the stack
- AF_UNIX datagram 소켓에서
sendmsg()를 사용하면: 커널은 유저의sockaddr_un을 최대 108바이트까지 스택 상의sockaddr_storage로 복사하고, 부수 데이터(ancillary data)를 syscall이 큐 공간을 기다리며 블록되기 전에 또 다른 온-스택 버퍼로 복사합니다. 이를 통해 스택 메모리 안에 정밀한 가짜 SKB 구조를 심을 수 있습니다. - 복사가 끝난 시점을 감지하려면 unmapped 유저 페이지에 위치한 1바이트 컨트롤 메시지를 제공하세요;
____sys_sendmsg()가 이를 fault 시키므로 그 주소에 대해mincore()를 폴링하는 헬퍼 스레드는 목적지 페이지가 준비됐을 때를 알 수 있습니다. CONFIG_INIT_STACK_ALL_ZERO로 인한 0으로 초기화된 패딩은 사용되지 않은 필드를 채워 추가 쓰기 없이도 유효한 SKB 헤더를 완성시켜 줍니다.
Timing the +4 GiB increment with a self-looping frag list
skb_shinfo(fakeskb)->frag_list를 두 번째 가짜 SKB(공격자 제어 유저 메모리에 저장)로 가리키도록 위조하고, 그 SKB가len = 0및next = &self가 되게 합니다.__skb_datagram_iter()내부에서skb_walk_frags()가 이 리스트를 순회할 때, 이터레이터가 NULL에 도달하지 않으므로 실행은 영원히 회전하고 복사 루프는 진행하지 않습니다.- 두 번째 가짜 SKB가 self-loop하는 한 recv syscall을 커널 안에서 지속시킵니다. 증가를 발사할 시점에는 두 번째 SKB의
next포인터를 유저 공간에서NULL로 바꾸면 됩니다. 루프가 종료되고unix_stream_recv_urg()는 즉시UNIXCB(oob_skb).consumed += 1을 실행하여 재활용된 스택 페이지의 오프셋0x40에 현재 배치된 객체에 영향을 줍니다.
Stalling copy_from_iter() without userfaultfd
- 거대한 anonymous RW VMA를 매핑하고 전부 fault 시킵니다.
madvise(MADV_DONTNEED, hole, PAGE_SIZE)로 단일 페이지 홀을 만들고 그 주소를write(pipefd, user_buf, 0x3000)에 사용되는iov_iter에 넣습니다.- 병렬로 다른 스레드에서 VMA 전체에 대해
mprotect()를 호출하세요. 해당 syscall은 mmap 쓰기 락을 잡고 모든 PTE를 순회합니다. 파이프 작성자가 홀에 도달하면 페이지 폴트 핸들러는mprotect()가 잡고 있는 mmap 락을 기다리며 차단되므로copy_from_iter()는 결정론적인 시점에서 멈추고 흘러나온bytes값은 재활용된 SKB 페이지가 호스팅하는 스택 세그먼트에 남습니다.
Turning the increment into arbitrary PTE writes
- Fire the increment: frag 루프를 해제하여
copy_from_iter()가 멈춘 동안 +4 GiB 증가가bytes변수에 적용되게 합니다. - Overflow the copy: 결함이 재개되면
copy_page_from_iter()는 현재 파이프 페이지로 >4 GiB를 복사할 수 있다고 판단합니다. 합법적인 0x2000 바이트(두 파이프 버퍼)를 채운 뒤 추가 반복을 실행해 남은 유저 데이터를 파이프 버퍼 PFN 다음에 오는 물리 페이지에 씁니다. - Arrange adjacency: 할당자 텔레메트리를 이용해 buddy allocator가 프로세스 소유의 PTE 페이지를 대상 파이프 버퍼 페이지 바로 다음에 놓도록 강제합니다(예: 파이프 페이지를 번갈아 할당하고 새 가상 범위를 터치해 페이지-테이블 할당을 트리거해 PFN들이 같은 2 MiB 페이지블록에 정렬되도록 함).
- Overwrite page tables: OOB
copy_from_iter()가 이웃 페이지를 공격자가 선택한 엔트리로 채우게 하려면 추가 0x1000 바이트의 유저 데이터에 원하는 PTE 엔트리들을 인코딩하세요. 이를 통해 커널 물리 메모리에 대한 RW/RWX 유저 매핑을 부여하거나 기존 엔트리를 덮어써 SMEP/SMAP을 비활성화할 수 있습니다.
Mitigations / hardening ideas
- Kernel:
32ca245464e1479bfea8592b9db227fdc1641705적용( SKB를 올바르게 재검증) 및CONFIG_AF_UNIX_OOB(5155cbcdbf03)를 통해 필요하지 않다면 AF_UNIX OOB를 비활성화하는 것을 고려하세요.manage_oob()를 추가적인 무결성 검사(예:unix_skb_len() > 0가 될 때까지 루프)로 강화하고 유사한 가정이 존재하는 다른 소켓 프로토콜을 감사하세요. - Sandboxing: seccomp 프로필이나 상위 브로커 API에서
MSG_OOB/MSG_PEEK플래그를 필터링하세요(Chrome 변경6711812는 렌더러 측MSG_OOB를 차단합니다). - Allocator defenses: SLUB freelist 무작위화 강화나 캐시별 페이지 컬러링 강제는 결정론적 페이지 재활용을 복잡하게 합니다; 파이프 버퍼 수를 제한하는 것도 재할당 신뢰도를 줄입니다.
- Monitoring: 높은 비율의 페이지-테이블 할당이나 비정상적인 파이프 사용을 텔레메트리로 노출하세요—이 익스플로잇은 대량의 페이지 테이블과 파이프 버퍼를 소모합니다.
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 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.


