
CVE-2026-52910에 대한 레이스 재현 및 스트레스 툴킷으로, reuseport cBPF 선택기 프로그램의 Linux 커널 use-after-free 취약점을 다루며 dmesg 및 누수 검사를 포함합니다.
리눅스 커널의 classic BPF (cBPF) reuseport selector 프로그램 처리에서 발생하는 use-after-free (UAF)인 CVE-2026-52910에 대한 레이스 재현기 및 스트레스 툴킷으로, 업스트림에서 커밋 *"bpf: Free reuseport cBPF prog after RCU grace period"*로 수정되었습니다.
| CVE | CVE-2026-52910 |
| 유형 | Use-after-free / out-of-bounds read (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| 도입 버전 | v4.5 (reuseport cBPF 지원과 함께) |
| 수정 버전 | 5.10.259, 5.15.210, 6.1.176, 6.6.143, 6.12.94, 6.18.36, 7.0.13 (stable); mainline v7.1 |
| 업스트림 splat | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| 보고자 | Eulgyu Kim |
[!WARNING] 이것은 커널 스트레스 도구입니다. 취약한 커널에서는 use-after-free 레이스 윈도우를 의도적으로 넓히며, 적중 시 시스템이 크래시되거나 손상될 수 있습니다. 소유하고 있거나 명시적으로 테스트 권한을 받은 머신(테스트 VM, 일회용 CI 머신)에서만 실행하고, 프로덕션 시스템에서는 절대 실행하지 마십시오.
**SO_REUSEPORT**는 여러 소켓이 동일한 UDP 포트에 바인딩되도록 허용하며, 각 수신 패킷에 대해 커널은
reuseport_select_sock() (net/core/sock_reuseport.c)에서 그룹의 소켓 하나를 선택합니다. 그룹은
selector 프로그램 — setsockopt(SO_ATTACH_REUSEPORT_CBPF)로 연결된 classic BPF 프로그램 — 을
설치할 수 있으며, 이 프로그램이 패킷별로 그룹 내 어느 소켓이 패킷을 받을지 결정합니다. 이 프로그램은
RCU read-side critical section 내부의 RX softirq (네트워크 수신 처리)에서 실행됩니다.
버그: setsockopt() (reuseport_attach_prog() / reuseport_detach_prog())를 통해 프로그램이 교체되거나
분리될 때, 기존 cBPF 프로그램은 진행 중인 RCU 리더를 기다리지 않고 sk_reuseport_prog_free()에 의해
즉시 해제됩니다. 해제된 프로그램의 명령어를 아직 순회 중인 CPU는 해제된 vmalloc 메모리를 읽습니다:
sequenceDiagram
autonumber
participant C as CPU0 — churner thread
participant K as setsockopt() path
participant R as CPU1 — RX softirq
R->>R: rcu_read_lock()
R->>R: prog = rcu_dereference(reuse->prog)
C->>K: setsockopt(SO_ATTACH_REUSEPORT_CBPF, progB)
K->>K: swap progA → progB
K->>K: sk_reuseport_prog_free(progA)
Note right of K: unfixed kernels: bpf_prog_free()<br/>runs NOW — no RCU grace period
R->>R: execute progA->insns (run_bpf_filter)
Note right of R: progA was already freed<br/>KASAN: vmalloc-out-of-bounds
Note over K: fix: call_rcu(sk_reuseport_prog_free_rcu) —<br/>free deferred by one RCU grace periodeBPF selector 경로(SO_ATTACH_REUSEPORT_EBPF)는 영향을 받지 않습니다: 이미 지연된
bpf_prog_put() 단계를 통해 프로그램을 해제합니다. 수정 사항은 cBPF 경로에도 동일한 처리를
적용합니다 — 기존 프로그램이 해제되기 전에 RCU grace period 하나를 둡니다.
업스트림 KASAN 리포트 (7.0 디버그 커널에서):
BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock+0xedc/0x1220
Read of size 4 at addr ffffc9000051e004 by task slowme/10208
net/core/sock_reuseport.c:596
리눅스 테스트 박스에서:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
요구 사항:
gcc와 bash.dmesg, /proc/vmallocinfo, sysctl)를 위한 root;
hammer 자체는 비특권으로 실행됩니다 (업스트림 재현은 UID 1000으로 실행됨).예상 결과:
RC=2; 때때로 hammer의 무결성 검사가
먼저 발동 → RC=4.RC=0, 안정적인 bpf_prog vmalloc 카운트.레이스 윈도우는 매우 작으므로 (해제 vs. 진행 중인 RX 실행), 단일 깨끗한 실행은 결정적이지 않은 것으로 취급하십시오. 실제 테스트는 수 시간 실행하십시오, 예:
$ sudo ./run_hammer.sh 86400 512 8 16 4 127.0.0.1 0
reuseport_race_hammer$ ./reuseport_race_hammer [dur_sec] [insns] [nports] [nsocks] [nsenders] [ip] [ebpf]
각 그룹은 setsockopt()를 통해 cBPF selector를 타이트 루프에서 교체하고 분리하는
churner 스레드 하나와, 그룹에 64바이트 UDP 데이터그램을 플러딩하는 sender 스레드들을
실행하며, 수신 측은 소켓별 전달을 카운트합니다.
실행 단계 (T = dur_sec):
time ──────────────────────────────────────────────────────────────►
[0 ──────────── T-15s) [T-15s ── T-10s) [T-10s ─────────── T]
CHURN + FLOOD SETTLE MEASURE
churner swaps/detaches churn frozen, deterministic program
the selector prog at final program (selects the LAST
max rate under full attached socket): EVERY packet
UDP flood — THE (selects LAST must land on the LAST
race window open socket) socket; snapshot A →
run → snapshot B
무결성 검사: measure 단계 동안 최종 프로그램은 그룹의 마지막 소켓을 결정적으로 선택합니다. 해당 윈도우 동안 그룹이 수신한 패킷이 모두 그 소켓에 도착하지 않았다면, 선택이 잘못된 것입니다 (KASAN 없이도 가능한 UAF 효과) → 종료 코드 2.
hammer 종료 코드: 0 PASS · 1 설정/런타임 오류 · 2 무결성 WARN.
run_hammer.sh — 단일 실행 래퍼hammer를 실행하고 단일 실행을 의미 있게 만드는 검사를 추가합니다:
/proc/vmallocinfo에서 bpf_prog 할당 수를 스냅샷합니다;net.core.optmem_max를 높입니다;DRAIN (기본 30초) 동안 대기합니다;BUG:, Oops:, WARNING:, RIP:,
leaked, stuck)을 스캔합니다;bpf_prog vmalloc 카운트를 비교하고 (누수 검사),
/sys/kernel/debug/kmemleak이 존재하면 선택적으로 kmemleak을 스캔합니다.종료 코드: 0 깨끗함 · 1 설정 오류 (hammer 설정 실패 포함) ·
2 커널 splat 발견 · 3 가능한 bpf_prog 누수 · 4 hammer 무결성
WARN.
livepatch_cycle.sh — livepatch 라이프사이클 테스트수정 사항은 call_rcu() 콜백에서 기존 cBPF 프로그램을 해제합니다. 수정 사항을
livepatch (커널 라이브 패칭 — 실행 중인 커널에 코드를 패치)로 배포하면,
콜백 함수 자체가 패치 모듈에 존재합니다: 콜백이 아직 대기 중일 때 revert/unload하면
콜백의 발밑에서 모듈 텍스트가 해제됩니다. 이 스크립트는 hammer가 레이스 윈도우를
뜨겁게 유지하는 동안 apply/revert 사이클을 실행하고,
/sys/kernel/livepatch/*/transition과 dmesg를 감시합니다.
$ MODE=rcu ./livepatch_cycle.sh 20 120 # 20 cycles × 120s hammer each
| 모드 | 동작 |
|---|---|
cycle (기본) | apply → revert, 둘 다 지속적인 hammer 부하 상태에서 |
rcu | 최대 churn에서 즉시 revert — 위의 위험 사례 |
safe |
종료 코드: 0 깨끗함 · 1 명령 실패 · 2 splat 또는 멈춘 전환 ·
4 hammer의 무결성 WARN.
CI는 두 가지 플래그 세트로 hammer를 빌드하고 스크립트에 shellcheck를 실행합니다. 커널 런타임 테스트는 공유 CI 러너에서 의도적으로 실행하지 않습니다: 재현기는 러너의 커널 버전에 대한 제어가 필요하며 (취약한 커널에서는 러너를 oops시킬 수 있음), 실제 테스트 머신에서 실행하십시오.
GPL-2.0-only — LICENSE 참조.
| 파일 | 용도 |
|---|
reuseport_race_hammer.c | 재현기: 전체 UDP 부하 상태에서 cBPF selector를 계속 교체하는 멀티스레드 hammer로, 이후 전달 무결성을 검증합니다. |
run_hammer.sh | 단일 실행 래퍼: net.core.optmem_max를 높이고, hammer를 실행한 뒤, dmesg에서 splat, /proc/vmallocinfo에서 누수된 bpf_prog 할당, 선택적으로 kmemleak을 확인합니다. |
livepatch_cycle.sh | hammer가 실행되는 동안 수정 사항을 담은 livepatch를 적용/되돌립니다 — call_rcu() 기반 수정의 livepatch 라이프사이클 위험을 추적합니다. |
Makefile | hammer를 빌드합니다. |
.github/workflows/ci.yml | CI: 빌드 + shellcheck (런타임 커널 테스트 없음; CI 참조). |
| 인자 | 기본값 | 의미 |
|---|
dur_sec | 300 (최소 45) | 총 실행 시간(초) |
insns | 256 | 교체되는 cBPF 프로그램의 채움 명령어; 프로그램이 클수록 적중할 해제 영역이 커집니다. ENOMEM으로 attach가 실패하면 net.core.optmem_max를 높이십시오 (래퍼가 대신 처리합니다). |
nports | 4 (최대 64) | reuseport 그룹 (각각 21000부터 시작하는 UDP 포트 하나) |
nsocks | 8 (최대 512) | 그룹당 소켓 수 |
nsenders | 4 (최대 32) | 그룹당 UDP 송신 스레드 수 |
ip | 127.0.0.1 | 대상 주소; 물리 NIC IP를 사용하면 RX softirq를 CPU 전반에 분산(RSS)할 수 있습니다 |
ebpf | 0 | 1 = SO_ATTACH_REUSEPORT_EBPF attach/detach도 교체 (CAP_BPF/CAP_NET_ADMIN 필요); 해당 경로는 취약하지 않으며, 비교/커버리지용입니다 |
| 환경 변수 | 기본값 | 의미 |
|---|
HAMMER | ./reuseport_race_hammer | hammer 바이너리 |
DRAIN | 30 | 실행 후 검사 전 대기 시간(초) |
OPTMEM_MAX | 131072 | net.core.optmem_max 값; 0 = 건드리지 않음 |
hammer 중지 → GRACE (기본 30초, grace period 하나) 대기 → revert |
| 환경 변수 | 기본값 | 의미 |
|---|
APPLY_CMD / REVERT_CMD | kpatch load $PATCH / kpatch unload $PATCH | livepatch 명령 |
PATCH | ./livepatch-reuseport.ko | 패치 모듈 |
HAMMER | ./reuseport_race_hammer | hammer 바이너리 |
HAMMER_ARGS | 256 4 8 4 127.0.0.1 0 | hammer 인자 |
TRANSITION_TIMEOUT | 60 | livepatch 전환을 기다리는 최대 시간(초) |
GRACE | 30 | MODE=safe를 위한 grace-period 대기 |
FORCE | 0 | 1 = livepatch 전환이 감지되지 않아도 실행 |