
English | 简体中文
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 イベントは、カーネルコンテキストでオーバーフロー割り込みを発生させることがあるため(スキッド)、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 安定ツリーの形式であり、そこでは
perf_allow_kernel() が属性ポインタを受け取らなくなっています。アップストリームの
コミットでは、同じ条件を perf_allow_kernel(&attr) で使用しています。
完全な呼び出しパス、skid の仕組み、SELinux 監査証跡、デバイスでの測定結果については
docs/analysis.md を参照してください。
PoC は非特権シェルプロセスとして 3 つのケースを実行します:
| ケース | perf_event 属性 | 脆弱なカーネルでの期待結果 |
|---|---|---|
| A | exclude_kernel=0 | EACCES(カーネル 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... のカーネル範囲に存在する。検証済みデバイスにおいて、修正済みカーネルではケース C が
perf_event_open() の時点で EACCES により拒否されます。これはカーネルコールチェーンの
要求が perf_allow_kernel() に到達するようになったためです。
一致する vmlinux があれば、漏洩したコールチェーンフレームは起動ごとの KASLR
スライドオラクルになります。arm64 の仮想スライドは 2 MiB 単位で整列され、漏洩した
すべてのテキストアドレスは _stext <= addr - slide < _etext を満たす必要があります。
これらの区間の交差を取ると、通常はちょうど 1 つのスライドが残ります。
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ブートでは、3つのフレームから一意に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下では生のアドレスを直接シンボル化することはできません。まずスライドを減算してください。フレームがモジュールアドレスの場合、すべてのアドレスを[_stext, _etext)に収めるスライドは存在せず、スクリプトは推測せずにその旨を報告します。スライドはブートごとに変化するため、再起動をまたいでキャッシュしてはなりません。
Redmi K70(5.15.178)も検証済みです。対応するイメージは/sdcard/boot.imgから取得され(boot v4、埋め込みバージョン文字列が実行中のカーネルと一致)、vmlinux-to-elfは158,668個のシンボルを復元しました。period-1000の1回の実行から得られた7つの一意なフレームにより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に対応付けられます。
リンク時ベースはブランチ固有です — 5.15で6.12の値を再利用しないでください:
| カーネルブランチ | KIMAGE_VADDR |
|---|---|
| Android 13-5.15 (K70) | 0xffffffc008000000 |
| Android 16-6.12 (YLP-W00) | 0xffffffc080000000 |
| upstream v5.15 (異なるレイアウト) | 0xffffffc010000000 |
vmlinux-to-elfはK70イメージに対して0xffffffc008000000を自動検出し、AOSP android13-5.15と一致しました。自動検出されたELFベースがブランチのKIMAGE_VADDRと異なる場合は、--link-base(またはSLIDE_LINK_BASE=)を渡してください。シンボル化はELF空間に留まり、報告されるスライドは真のベースに対して相対化され、実行時_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 | open 時に拒否 (修正済みまたは強制適用) |
| 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 リリースはバージョンによって事前チェックプロファイルに含まれていますが、デバイス側の実行時検証はまだ保留中です。