
Linux qdisc rate-table 경쟁 조건을 악용하는 개념 증명(PoC) 로컬 권한 상승 익스플로잇으로, BPF 힙 그루밍과 파이프 누수를 이용해 루트 권한을 획득합니다.

이 저장소에는 CVE-2026-68138에 대한 개념 증명(PoC) 로컬 권한 상승 익스플로잇이 포함되어 있습니다. 이 취약점은 Linux 트래픽 제어 rate-table 코드의 경합(race)입니다. 테스트한 QEMU 환경에서 이 PoC는 외부 UID 1000을 가진 일반 프로세스에서 초기 사용자 네임스페이스의 UID 0 셸로 권한을 상승시킵니다.
경고
이 코드는 의도적으로 커널 힙 상태를 손상시킵니다. 소유한 격리된 일회용 VM에서만 사용하십시오. 경합 실패나 조기 정리는 게스트를 패닉 상태로 만들 수 있습니다. 워크스테이션, 서버 또는 제3자 시스템에서 실행하지 마십시오.
qdisc_get_rtab()와 qdisc_put_rtab()는 프로세스 전역 단일 연결 리스트 qdisc_rtab_list와 일반적인 비원자적 int refcnt를 관리합니다. 역사적으로 호출자는 RTNL 뮤텍스를 보유하여 리스트와 참조 횟수에 대한 접근을 직렬화했습니다.
flower 분류기는 TCF_PROTO_OPS_DOIT_UNLOCKED를 설정합니다. flower 규칙에 대한 RTM_NEWTFILTER 요청은 결과적으로 police 액션에 도달하여 RTNL 없이 qdisc rate-table 헬퍼를 호출할 수 있습니다:
tc_new_tfilter()
-> fl_change()
-> tcf_exts_validate_ex()
-> tcf_action_init()
-> tcf_police_init()
-> qdisc_get_rtab()/qdisc_put_rtab()
동일한 rate-table을 사용하는 동시 요청은 전역 리스트와 그 참조 횟수에서 경합할 수 있습니다. 그 결과 테스트한 x86-64 커널에서 kmalloc-2k로부터 할당된 1056바이트 객체인 struct qdisc_rate_table의 use-after-free 또는 이중 해제(double-free)가 발생합니다.
리스트가 네트워크 네임스페이스별이 아니라 전역이므로, 서로 다른 네트워크 네임스페이스의 요청도 동일한 객체에서 경합합니다.
Linux CNA 기록은 Linux 5.1에서 릴리스된 도입 커밋 470502de5bdb을 식별합니다.
배포판 커널은 위에 표시된 업스트림 버전으로 변경하지 않고 수정 사항을 백포트하는 경우가 많습니다. 시스템에서 사용하는 정확한 커널 소스에 두 수정 커밋 중 하나(또는 이에 상응하는 qdisc rate-table 스핀락 변경)가 있는지 확인하십시오.
익스플로잇은 메인라인 수정의 직접적인 부모 커밋인 취약한 커밋 92d3817649df2b0b6a008a686c8275c88d7ef594를 대상으로 개발 및 검증되었습니다. 수정된 커널 대조군에서는 f43ee0c0730d6191629b5ee1ceae27b1ebfdc047를 사용했습니다.
2026-08-12 기준으로 Ubuntu CVE 추적기 검색은 이 CVE에 대한 항목을 반환하지 않았습니다. 따라서 아래 표는 Canonical의 보안 상태 판정이 아닌 직접적인 소스 검사 결과입니다. 링크된 각 Ubuntu 태그는 여전히 잠금 해제된 qdisc_rtab_list를 가지고 있으며 수정용 qdisc_rtab_lock이 없습니다.
해당 검사 시점에 수정된 Ubuntu 패키지는 확인되지 않았습니다. 향후 Ubuntu 패키지는 버전 번호만으로 판단하지 말고 링크된 업스트림 수정과 동등한 백포트가 있는지 확인해야 합니다.
Ubuntu 22.04 빌드 전용 익스플로잇, QEMU 랩, 정확한 이미지 체크섬 및 조건은 ubuntu/README.md에 문서화되어 있습니다. 메모리 cgroup 회계가 활성화된 공식 5.15.0-187-generic #197-Ubuntu 커널을 대상으로 검증되었습니다.
기본 버그와 이 특정 익스플로잇 체인은 서로 다른 요구 사항을 가집니다. PoC는 다음 조건에서 검증되었습니다:
CONFIG_USER_NS=y 및 CONFIG_NET_NS=y;CONFIG_NET_CLS=y, CONFIG_NET_CLS_FLOWER=y,
CONFIG_NET_CLS_ACT=y, CONFIG_NET_ACT_POLICE=y;simple_xattr 힙 스프레이를 위한 CONFIG_TMPFS_XATTR=y;CONFIG_MODULES=y 및 사용 가능한 /sbin/modprobe;CONFIG_MEMCG=n - 이 체인이 사용하는 qdisc/BPF/pipe/xattr 할당이 예상되는
kmalloc-2k 캐시를 공유하도록 함;성공적인 테스트 커널에서는 CONFIG_SLAB_BUCKETS=y, 프리리스트 무작위화 및 프리리스트 강화가 활성화되어 있었습니다. KASLR은 하드코딩된 주소로 본질적으로 우회되지 않습니다. PoC는 파이프 누출에서 필요한 페이지 및 연산(ops) 포인터를 얻습니다. 제공된 랩은 디버깅을 단순화하기 위해 nokaslr를 사용했습니다.
메모리 cgroup 회계가 활성화된 커널이나 다른 할당자/캐시 레이아웃은 다른 재확보(reclaim) 전략이 필요합니다. PoC는 임의의 배포 구성에 대한 이식성을 주장하지 않도록 의도적으로 설계되었습니다.
별도의 Ubuntu 변형은 그 다른 재확보 전략을 구현합니다. 그 요구 사항은 의도적으로 더 좁으며 ubuntu/README.md에 나열되어 있습니다.
4개의 워커 스레드가 별도의 네트워크 네임스페이스에 진입하여 police 액션이 있는 flower 필터를 생성합니다. 3개의 워커는 성공적인 액션 경로를 취하고, 1개는 두 rate-table 참조를 획득한 후 의도적으로 잘못된 추정기(estimator)를 제공하여 정리 경로를 강제합니다. 이 조합은 워커 간에 flower 분류기 상태를 공유하지 않고도 동시 참조 횟수 및 리스트 조작을 재현 가능하게 만듭니다.
qdisc_rate_table 재확보모든 넷링크 요청 후, 동일한 CPU는 즉시 133개 명령어의 클래식 BPF 필터를 연결합니다. 그 1064바이트 명령어 배열은 kmalloc-2k에서 할당되며, qdisc_rate_table.next 및 qdisc_rate_table.refcnt와 겹치는 바이트가 처음에는 유효하게 유지되도록 형태가 만들어집니다.
마지막 명령어에는 소켓별 마커가 포함되어 있습니다. SO_GET_FILTER를 통해 PoC는 한 소켓의 orig_prog->filter 포인터가 다른 살아있는 BPF 할당으로 리다이렉션된 것을 감지할 수 있습니다. 이로써 동일한 명령어 버퍼를 참조하는 두 개의 소켓 객체가 생성됩니다.
한 소유자를 닫으면 sk_filter_release_rcu()를 통해 실제 해제가 지연됩니다. 유예 기간(grace period) 후 PoC는 32슬롯 파이프 링을 할당합니다:
32 * sizeof(struct pipe_buffer) = 32 * 40 = 1280 bytes -> kmalloc-2k
한 링이 에일리어싱된 BPF 버퍼를 재확보합니다. SO_GET_FILTER로 다른 소켓을 읽으면 살아있는 pipe_buffer가 공개되는데, 여기에는 page 및 ops 포인터와 /sbin/modprobe 페이지 캐시 항목의 오프셋이 포함됩니다.
두 번째 소켓을 닫으면 또 다른 RCU 유예 기간 후 살아있는 파이프 링이 해제되는 반면, 해당 pipe_inode_info는 여전히 이를 참조합니다. simple_xattr 스프레이가 1280바이트 링을 재확보하고 누출된 pipe_buffer를 PIPE_BUF_FLAG_CAN_MERGE가 설정된 상태로 예상 슬롯에 배치합니다.
각 후보 파이프에 쓰면 결국 공격자가 제어하는 바이트가 /sbin/modprobe의 ELF 엔트리 포인트를 뒷받침하는 페이지 캐시 페이지에 추가됩니다. PoC는 파일을 다시 읽고 정확한 덮어쓰기를 확인한 후에만 진행합니다.
최신 커널은 더 이상 search_binary_handler()에서 역사적인 잘못된 binfmt 모듈 자동 로드 폴백을 사용하지 않습니다. 이 PoC는 대신 지원되지 않는 프로토콜 253을 가진 AF_INET 데이터그램 소켓을 생성합니다. inet_create()는 누락된 net-pf-2-proto-253-type-2 모듈을 요청하여, 커널이 덮어쓴 /sbin/modprobe를 초기 네임스페이스 루트로 실행하게 합니다.
주입된 엔트리 포인트 페이로드는 증명 파일을 쓰고 QEMU 직렬 콘솔에서 루트 셸을 엽니다.
게스트 바이너리는 정적으로 링크됩니다:
./build.sh
동일한 명령:
gcc -O2 -static -pthread -Wall -Wextra -Werror \
-o build/exploit exploit.c
결과 바이너리는 게스트 내부에서 /exploit/CVE-2026-68138로 노출되어야 합니다. 테스트한 랩은 QEMU의 읽기/쓰기 9p 공유를 사용했습니다. 루트 헬퍼는 초기 마운트 네임스페이스의 /chroot/exploit에서 해당 공유를 기대합니다. 랩이 다른 레이아웃을 사용하는 경우 exploit.c의 helper_command를 조정하십시오.
관련 테스트 커널 옵션은 config.fragment에 기록되어 있습니다. 이는 완전한 프로덕션 커널 구성이 아니라 일부(fragment)입니다.
비특권 게스트 셸에서:
id
/exploit/CVE-2026-68138
경합은 확률적입니다. 실패 시 PoC는 다음을 출력합니다:
[-] no BPF alias found in this attempt
재시도 전에 VM을 재부팅하십시오. 동일한 게스트에서 다시 실행하지 마십시오. 전역 qdisc 리스트가 이미 손상되었을 수 있습니다.
성공적인 실행은 다음에 도달합니다:
[+] confirmed dangling orig_prog->filter pointer
[+] pipe leak: page=<kernel pointer> ops=<kernel pointer> off=0x60f len=1 flags=0
[+] page-cache overwrite via dangling pipe
[+] controlled page-cache write confirmed; triggering helper
[+] CVE-2026-68138 initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
[+] ROOT PROOF: uid=0(root) gid=0(root) groups=0(root)
비식별화된 전체 실행 기록은 docs/example-output.txt에 있습니다.
전체 체인은 새 취약 VM 부팅 상태에서 세 번 완료되었습니다. 최종 바이너리는 다음을 생성했습니다:
outer identity: uid=1000(user)
alias: classic-BPF orig_prog->filter overlap
leak: live pipe_buffer page and ops pointers
write: verified /sbin/modprobe page-cache modification
final identity: uid=0(root) gid=0(root) groups=0(root)
동일한 바이너리가 동일한 4-vCPU 토폴로지로 수정된 커밋에 대해 실행되었습니다. 약 126,000개의 경합 요청이 BPF 에일리어스 없이 완료되었으며, 파이프 또는 페이지 캐시 단계에 도달하지 않았습니다.
Ubuntu 전용 변형도 정확한 공식 5.15.0-187.197 제네릭 커널에 대해 실행되었습니다. 게스트는 4개의 vCPU, 일반 메모리 cgroup 회계, 그리고 익스플로잇 전용 커널 인자로서의 nokaslr로 부팅되었습니다.
Ubuntu 커널은 재컴파일되지 않았습니다. 테스트는 배포판의 변경되지 않은 vmlinuz-5.15.0-187-generic과 일치하는 공식 모듈을 사용했습니다. Ubuntu 사전 요구 사항 표는 스톡 커널 기능과 런타임 및 QEMU 랩 조정을 구분합니다.
시작 계정에는 sudo 액세스나 보조 그룹이 없었습니다:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
새 스냅샷에서 익스플로잇은 BPF 에일리어스를 획득하고, cgroup-v2 simple_xattr 객체와 차단 select() 비트맵을 통해 이를 재확보하고, /proc/sys/kernel/modprobe에 /tmp/x가 포함되어 있는지 확인한 후 다음에 도달했습니다:
[+] CVE-2026-68138 Ubuntu initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
root@cve-2026-68138-jammy:/# id
uid=0(root) gid=0(root) groups=0(root)
실제 QEMU 실행 기록 및 Ubuntu 랩 안내를 참조하십시오. 이 변형은 이 패키지에 대해 modprobe_path 주소를 하드코딩하므로 nokaslr가 필요합니다. KASLR 우회나 다른 Ubuntu 빌드와의 호환성을 주장하지 않습니다.
패치는 이미 Linux 커널의 일부이므로 이 저장소에 중복으로 포함되지 않습니다. 표준 안정(stable) 및 메인라인 수정 커밋은 아래 참조에 링크되어 있습니다.
연구 및 익스플로잇 구현: A. Ramos <[email protected]> (Twitter: @aramosf).
| 라인 | 상태 | 커밋/버전 |
|---|
| Linux 5.1 이전 | 영향 없음 | 도입 변경 사항 없음 |
| Linux 5.1 ~ 7.1.5 | 벤더 백포트가 없으면 영향 있음 | 470502de5bdb부터 안정 수정 이전 커밋까지 |
| Linux 7.1.y | 수정됨 | 7.1.6, fb29e1b41052 |
| Linux 7.2 개발 시리즈 | rc5 이전에는 영향 있음 | rc1 ~ rc4 |
| 메인라인 | 수정됨 | 7.2-rc5, f43ee0c0730d |
| Ubuntu 라인 | 검사한 패키지/태그 | 소스 검사 결과 |
|---|
| Ubuntu 22.04 GA | 5.15.0-187.197 | 취약한 코드 존재; QEMU에서 전체 익스플로잇 재현됨 |
| Ubuntu 22.04 HWE | 6.8.0-136.136~22.04.1 | 취약한 코드 존재; 익스플로잇 체인 테스트되지 않음 |
| Ubuntu 24.04 HWE | 7.0.0-28.28~24.04.1 | 취약한 코드 존재; 이 익스플로잇 체인은 해당 할당자 강화와 호환되지 않음 |
| Ubuntu 26.04 | 7.0.0-28.28 | 취약한 코드 존재; 이 익스플로잇 체인은 해당 할당자 강화와 호환되지 않음 |