CVE-2026-99127 in Linux
요약
\~에 의해 VulDB • 2026. 10. 28.
리눅스 커널에서 다음 취약점이 해결되었습니다:
page_pool: 홀수 크기의 요청에 대해 frag_offset 정렬 유지
page_pool_alloc_frag_netmem()은 다음과 같이 요청된 조각(fragment) 크기를 반올림합니다.
size = ALIGN(size, dma_get_cache_alignment());
dma_get_cache_alignment()는 아키텍처가 ARCH_DMA_MINALIGN을 정의하지 않는 한(예: DMA 일관성(DMA-coherent) 아키텍처인 x86 등), 1을 반환합니다. 이러한 경우 ALIGN()은 무작위 연산(no-op)이 되며, pool->frag_offset는 반올림되지 않은 원본 크기로 증가합니다.
홀수 크기를 요청하는 단일 호출자가 있는 경우, 이후 해당 페이지에서 잘려 나가는 모든 조각에 대해 frag_offset가 정렬되지 않게 됩니다. 풀(pool)은 공유되므로, 이로 인한 피해는 문제를 유발한 호출자에 국한되지 않습니다.
제너릭 XDP(generic XDP)에서 사용하는 per-cpu system_page_pool이 이 문제에 직면합니다. skb_pp_cow_data()는 원본 패킷 길이를 사용하여 조각을 할당합니다:
size = min_t(u32, len, PAGE_SIZE); truesize = size; page = page_pool_dev_alloc(pool, &page_off, &truesize);
이로 인해 해당 페이지에서 나중에 잘려 나가는 모든 항목에 대해 frag_offset가 홀수가 됩니다. 자체 헤드(head) 할당은 이미 정렬되어 있습니다(SKB_HEAD_ALIGN(size)와 호출자가 전달하는 XDP_PACKET_HEADROOM 합계). 따라서 공유 풀의 후속 사용자가 피해를 입습니다: page_pool_dev_alloc_va()는 정렬되지 않은 버퍼를 반환하고, napi_build_skb()는 이를 skb->head로 설치하며, skb_shinfo(skb) == skb->head + skb->end가 이와 함께 정렬되지 않게 됩니다.
skb_shinfo()->dataref은 오프셋 0x20에 위치한 4바이트 atomic_t이므로, __skb_clone() 내의 atomic_inc()는 캐시 라인(cache line)을 가로지릅니다(x86에서 split lock 감지가 활성화된 경우 기본값으로 커널 split lock이 치명적이므로 이 경우 시스템 패닉(panic)이 발생합니다):
Oops: Split lock detected RIP: 0010:skb_clone+0x154/0x1e0 Call Trace: raw_local_deliver+0x1ed/0x2c0 ip_protocol_deliver_rcu+0x54/0x1c0 ip_local_deliver_finish+0x85/0x100 ip_local_deliver+0x67/0x100 __netif_receive_skb_one_core+0x85/0xa0 process_backlog+0x87/0x130
루프백(loopback)에 제너릭 모드 XDP 프로그램을 연결하고 RAW IPPROTO_UDP 소켓을 열면 재현됩니다. 이는 로컬로 전달되는 모든 UDP 패킷을 raw_local_deliver()가 복제(clone)하게 만듭니다. 일반 DNS 트래픽은 약 2500번의 복제 중 한 번씩 이를 트리거합니다. 이 현상은 버전 6.12.101 및 7.1.8에서 관찰되었습니다.
이러한 실행 중 page_pool_alloc_frag_netmem()을 추적하면 증폭 효과를 확인할 수 있습니다(두 개의 홀수 크기 요청, 아홉 개의 정렬되지 않은 오프셋):
requested size & 7: 0: 17035 5: 1 7: 1 frag_offset & 7: 0: 17028 3: 1 4: 1 5: 1 6: 1 7: 5
그리고 skb_pp_cow_data()는 진입 시 정렬되었던 헤드(head)를 반환합니다:
head 0xffff8f4c86aeac00 -> 0xffff8f4c53a9a9c4 (&7=4) head 0xffff8f4d6a8a42c0 -> 0xffff8f4c4f7b7a45 (&7=5)
조각 크기를 skb_shared_info 구조체가 요구하는 정렬 이상으로 반올림하여, 호출자가 조각 위에 구축하는 객체에 대해 조각이 항상 적절하게 정렬되도록 합니다. 더 큰 DMA 정렬(DMA alignment)이 필요한 아키텍처는 해당 값을 유지합니다.
또한 page_pool_alloc_netmem()에서 계산되는 나머지 값도 다음과 같이 정렬됩니다:
*size = max_size - *offset;
max_size가 2의 거듭제곱(power of two)이기 때문에, 이는 skb->end의 일치하는 불일치(misalignment)를 수정합니다.
QEMU/KVM 하에서 통제된 A/B 테스트로 검증되었습니다: 동일한 트리와 구성, 동일한 컴파일러, 동일한 루트 파일시스템(rootfs) 및 동일하게 생성된 트래픽을 사용하되 이 패치만 다릅니다. lo 인터페이스에 SEC("xdp.frags") XDP_PASS 프로그램을 배치하고 max_head_size보다 큰 UDP 데이터그램을 전송하면 skb_pp_cow_data()의 조각 반복(loop)이 구동되며, 이는 원본 패킷 길이를 풀(pool)로 전달합니다. skb_pp_cow_data() 반환 시 측정된 결과는 다음과 같습니다:
unpatched patched skb_pp_cow_data calls 40800 40800 misaligned skb->head 1120 0 dataref at line offset >60 80 0
마지막 행은 실제로 페이지 폴트(page fault)를 발생시키는 접근 횟수를 세는 것입니다. skb_shinfo()->dataref는 head+end+0x20에 위치하며 4바이트 atomic이므로, `lock incl` 명령어는 해당 주소가 오프셋 61~63에 있을 때만 64바이트 캐시 라인을 분할(split)합니다. 발생했던 모든 80건의 사례는 오프셋 61에서 발생했으며, 위에서 보고된 패닉은 오프셋 62에서 발생했습니다. 정렬 불일치를 제거하면 이러한 문제가 모두 해결됩니다.
커밋 3bed3cc4156e("net: Do not allocate page fragments t ---truncated---
If you want to get best quality of vulnerability data, you may have to visit VulDB.