
Android 비특권 태스크에 커널 콜체인 주소를 유출하여 KASLR 슬라이드 도출을 가능하게 하는 Linux perf/core 권한 검사 우회에 대한 검증된 PoC 및 분석입니다.
한국어 | 简体中文
Linux perf/core 권한 검사 우회: 비특권 태스크가 exclude_kernel=1을 설정한 상태에서 커널 콜체인을 요청하여 perf_allow_kernel() 게이트를 건너뛰고 perf 링 버퍼를 통해 커널 스택 프레임을 노출할 수 있습니다. 이 저장소에는 분석 내용과 검증된 PoC가 포함되어 있으며, 이 버그는 정보 유출을 일으키는 것으로, 직접적인 권한 상승 프리미티브는 아닙니다.
| 항목 | 값 |
|---|---|
| CVE | CVE-2026-98937 |
| 분류 | 권한 우회 / 정보 노출 (CWE-284/CWE-269) |
| 대상 | Android arm64 (HONOR YLP-W00 및 Redmi K70에서 검증됨) |
| 커널 | YLP-W00: 6.12.38-android16-5-gfde7767f6ef6-abogki481467632-4k; K70: 5.15.178-android13-8-00021-g6f2f96be86b9-ab13729987 |
| SELinux | Enforcing (u:r:shell:s0) |
| 패치됨 | 아니오; 6.12.113 / 6.18.56 / 7.2.10 / 7.3+에서 업스트림 수정됨 |
| 상태 | PoC 검증됨; KASLR 슬라이드 오라클이 6.12.38 및 5.15.178에서 검증됨 |
| 사전 검사 | src/vuln_precheck.h |
아래 수정 이전의 커널이 영향을 받습니다. a4b016d0a29038a5b7c96bf62c054bf7832451d9 ("perf/core: Check kernel access when kernel callchains are requested", Dapeng Mi, 2026-06-30 병합) 및 해당 안정 브랜치 백포트로 수정되었습니다.
perf_event_open()은 이전에 !attr.exclude_kernel만을 기준으로 커널 프로파일링을 제한했습니다:
if (!attr.exclude_kernel) {
err = perf_allow_kernel(&attr);
if (err)
return err;
}
perf_allow_kernel()에는 perf_event_paranoid > 1 검사와
SELinux perf_event { kernel } 훅이 모두 포함되어 있습니다. 태스크는
exclude_kernel=1을 설정하면서도 여전히 커널 콜체인을 요청할 수 있으며
(exclude_callchain_kernel=0인 PERF_SAMPLE_CALLCHAIN), 이때
perf_callchain()은 샘플링 필터와 무관하게 커널 부분을 수집합니다. 사용자
모드만 카운트하는 하드웨어 PMU 이벤트는 커널 컨텍스트에서 오버플로 인터럽트를
발생시킬 수 있으므로(skid), perf_callchain_kernel()은 커널 스택을
언와인드하고 커널 텍스트 주소를 링 버퍼에 기록합니다.
수정 사항은 커널 샘플링 또는 커널 콜체인이 요청될 때
perf_allow_kernel()을 호출합니다:
if (!attr.exclude_kernel ||
((attr.sample_type & PERF_SAMPLE_CALLCHAIN) &&
!attr.exclude_callchain_kernel)) {
err = perf_allow_kernel();
if (err)
return err;
}
위 스니펫은 6.12.113 stable-tree 형태로, 여기서는
perf_allow_kernel()이 더 이상 속성 포인터를 받지 않습니다; 업스트림
커밋은 perf_allow_kernel(&attr)와 함께 동일한 조건을 사용합니다.
전체 호출 경로, skid 메커니즘, SELinux 감사 증거 및 디바이스 측정값은
docs/analysis.md를 참조하십시오.
PoC는 비권한 셸 프로세스로 세 가지 케이스를 실행합니다:
| 케이스 | perf_event 속성 | 취약한 커널에서의 예상 결과 |
|---|---|---|
| A | exclude_kernel=0 | EACCES (kernel-perf 게이트 활성) |
| B | exclude_kernel=1, exclude_callchain_kernel=1 | 이벤트 허용, 커널 콜체인 프레임 없음 |
| C | exclude_kernel=1, exclude_callchain_kernel=0 | 이벤트 허용, 커널 콜체인 프레임 노출 |
검증된 YLP-W00 (커널 6.12.38, SELinux Enforcing)에서:
EACCES, 감사 로그에
avc: denied { kernel } ... tclass=perf_event 기록.0xffffffde...
커널 범위 내.검증된 Redmi K70 (커널 5.15.178, 서드파티 Android 17 ROM, SELinux Enforcing)에서도 동일한 패턴이 관찰되었습니다:
EACCES, 동일한 SELinux { kernel } 감사 라인.0xffffffdd... 커널 범위 내.검증된 디바이스에서 수정된 커널은 커널 콜체인 요청이 이제
perf_allow_kernel()에 도달하므로 perf_event_open()에서 케이스 C를
EACCES로 거부할 것입니다.
일치하는 vmlinux가 있으면, 유출된 콜체인 프레임은 부팅별 KASLR
슬라이드 오라클이 됩니다. arm64 가상 슬라이드는 2 MiB 정렬이며, 유출된 모든
텍스트 주소는 _stext <= addr - slide < _etext를 만족해야 합니다; 이
구간들을 교집합하면 일반적으로 정확히 하나의 슬라이드만 남습니다.
make slide \
SLIDE_VMLINUX=/path/to/vmlinux-6.12.38.elf \
SLIDE_LINK_BASE=0xffffffc080000000 \
SLIDE_LOG=logs/poc-ylp-w00-20261027-020133-period1000.log
검증된 YLP-W00 6.12.38 부팅에서 세 개의 프레임이 고유한
slide=0x1DCFC00000(런타임 _text=0xffffffde4fc00000)을 제공합니다:
| 유출된 주소 | 링크 시점 주소 | 심볼 |
|---|---|---|
0xffffffde50748c78 | 0xffffffc080b48c78 | local_daif_restore+0x4 (msr DAIF, x0; ret) |
0xffffffde50d406e0 | 0xffffffc0811406e0 | el0t_64_sync_handler+0x70 (bl el0_svc 이후) |
0xffffffde4fc11588 | 0xffffffc080011588 | el0t_64_sync+0x1a8 (bl el0t_64_sync_handler 이후) |
이는 모듈이 아닌 코어 커널 텍스트 프레임입니다: 대기 중인 PMU 인터럽트가
시스템 콜 진입 경로의 msr DAIF, x0 이후 ret에서 정확히 전달됩니다.
KASLR 하에서는 원시 주소를 직접 심볼화할 수 없으며,
먼저 slide를 빼야 합니다. 프레임이 모듈 주소인 경우, 모든 주소를
[_stext, _etext)에 위치시키는 slide가 존재하지 않으므로 스크립트는
추측하는 대신 이를 보고합니다. slide는 부팅할 때마다 변경되며
재부팅 간에 캐시되어서는 안 됩니다.
Redmi K70(5.15.178)도 검증되었습니다. 일치하는 이미지는
/sdcard/boot.img(boot v4; 내장된 버전 문자열이 실행 중인 커널과 일치)에서
가져왔으며 vmlinux-to-elf가 158,668개의 심볼을 복구했습니다. period-1000
실행 한 번에서 나온 일곱 개의 고유 프레임이 slide=0x1D93800000(런타임
_text=0xffffffdd9b800000)을 제공하며, 이는 syscall_trace_enter,
el0_svc_common, do_el0_svc, el0_svc, el0t_64_sync_handler,
el0t_64_sync, syscall_trace_exit에 매핑됩니다.
링크 시점 베이스는 브랜치별로 다릅니다 — 6.12 값을 5.15에서 재사용하지 마십시오:
| 커널 브랜치 | KIMAGE_VADDR |
|---|---|
| Android 13-5.15 (K70) | 0xffffffc008000000 |
| Android 16-6.12 (YLP-W00) | 0xffffffc080000000 |
| 업스트림 v5.15 (다른 레이아웃) | 0xffffffc010000000 |
vmlinux-to-elf는 K70 이미지에 대해 0xffffffc008000000을 자동 감지했으며,
이는 AOSP android13-5.15와 일치합니다. 자동 감지된 ELF 베이스가 브랜치의
KIMAGE_VADDR와 다르면 --link-base(또는 SLIDE_LINK_BASE=)를 전달하십시오:
심볼화는 ELF 공간에 유지되고, 보고되는 slide는 실제 베이스에 상대적으로
계산되며, 런타임 _text는 일정한 베이스 이동에 대해 불변입니다. K70 유출을
6.12.38 이미지에 입력하는 것은 의도적인 부정 테스트로 남아 있으며
어떤 후보도 산출하지 않습니다.
요구 사항:
/opt/android-ndk-r29에 위치한 Android NDK r29 (NDK=로 재정의)make # PoC, arm64, API 24
make ARCH=arm API=29 # other ABI / API level
make host # static build for qemu-aarch64
make emu # build and run the static PoC locally
make help
아티팩트는 build/에 기록되며 git에서 무시됩니다.
make check # push + read-only environment pre-check
make run # push + full three-case verification
# directly
adb push build/poc /data/local/tmp/poc
adb shell chmod 755 /data/local/tmp/poc
adb shell /data/local/tmp/poc --check
adb shell /data/local/tmp/poc --duration 2 --period 1000
옵션:
--check pre-check only, no perf event is opened
--force run on an unlisted kernel release
--duration N sampling seconds per case (default 2)
--period N sample period in cycles (default 1000)
종료 코드:
| 코드 | 의미 |
|---|---|
| 0 | 취약: 게이트 우회 및 커널 호출 체인 프레임 관찰됨 |
| 2 | 사전 검사 거부됨 (알 수 없는 커널 릴리스 또는 환경 게이트 실패) |
| 3 | 판정 불가: 이벤트는 열렸으나 커널 프레임이 샘플링되지 않음 |
| 4 | 열기 단계에서 거부됨 (수정됨 또는 강제 적용됨) |
| 5 | 우회할 커널 perf 제한 없음 |
| 6 | 설정 오류 (사용 가능한 하드웨어 PMU 이벤트 없음, 예기치 않은 errno) |
--check 사용 시, 종료 코드 0은 프로파일 게이트가 진행됨을 의미하고 2는 거부됨을 의미하며, perf 이벤트는 열리지 않습니다.
PoC는 하드웨어 샘플링 데이터만 읽으며, 커널 상태를 쓰거나, sysctl을 수정하거나, 기기를 재부팅하지 않습니다.
cycles/instructions)가 필요합니다. exclude_kernel=1인 소프트웨어 이벤트는 커널 컨텍스트에서 필터링되어 skid 경로에 도달하지 않습니다.shell 도메인(uid 2000)에 대해서만 검증되었습니다. untrusted_app에 perf_event { open }이 허용되는지는 벤더 정책에 따라 다릅니다. 이 테스트를 위해 APK는 빌드되지 않았습니다.kptr_restrict=2이므로 기기 측 심볼화가 불가능합니다. 유출된 주소는 심볼화 대신 범위와 커널 컨텍스트 플래그로 검증됩니다.perf_event_paranoid를 수정하지 않습니다. 검증된 두 기기 모두에서 실효 게이트는 SELinux입니다. 건너뛴 함수에는 paranoid 검사도 포함되어 있으므로, perf_event_paranoid >= 2인 경우에도 동일한 우회가 적용됩니다.python3, nm, 정확히 일치하는 vmlinux, 그리고 브랜치의 실제 KIMAGE_VADDR가 필요합니다. 슬라이드는 부팅마다 다릅니다. 6.12.38과 5.15.178 모두 검증되었습니다. 6.12.58 릴리스는 버전 기준으로 사전 검사 프로파일에 포함되어 있으나, 기기 측 런타임 검증은 아직 대기 중입니다.