Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-98937 — Verified PoC and analysis for a Linux perf/core permission-check bypass that leaks kernel callchain addresses to unprivileged Android tasks, enabling KASLR slide derivation. | Kitploit
Tools/GitHubGitHub/woshigenm/cve-2026-98937
Android SecurityPrivilege EscalationVulnerability AnalysisExploitationInformation GatheringPapers & Research
GitHubwoshigenm/cve-2026-98937

CVE-2026-98937

Verified PoC and analysis for a Linux perf/core permission-check bypass that leaks kernel callchain addresses to unprivileged Android tasks, enabling KASLR slide derivation.

View Repository
414 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-98937 — perf kernel callchain permission bypass

English | 简体中文

Linux perf/core permission-check bypass: an unprivileged task can request kernel callchains while setting exclude_kernel=1, skipping the perf_allow_kernel() gate and disclosing kernel stack frames through the perf ring buffer. This repository contains the analysis and a verified PoC; the bug gives an information leak, not a direct privilege-escalation primitive.

Status

ItemValue
CVECVE-2026-98937
ClassPermission bypass / information disclosure (CWE-284/CWE-269)
TargetAndroid arm64 (verified on HONOR YLP-W00 and Redmi K70)
KernelYLP-W00: 6.12.38-android16-5-gfde7767f6ef6-abogki481467632-4k; K70: 5.15.178-android13-8-00021-g6f2f96be86b9-ab13729987
SELinuxEnforcing (u:r:shell:s0)
PatchedNo; fixed upstream in 6.12.113 / 6.18.56 / 7.2.10 / 7.3+
StatusPoC verified; KASLR slide oracle validated on 6.12.38 and 5.15.178
Pre-checksrc/vuln_precheck.h

Affected versions

Kernels before the fix below are affected. Fixed by a4b016d0a29038a5b7c96bf62c054bf7832451d9 ("perf/core: Check kernel access when kernel callchains are requested", Dapeng Mi, merged 2026-06-30) and the corresponding stable backports.

Root cause

perf_event_open() used to gate kernel profiling only on !attr.exclude_kernel:

if (!attr.exclude_kernel) {
        err = perf_allow_kernel(&attr);
        if (err)
                return err;
}

perf_allow_kernel() contains both the perf_event_paranoid > 1 check and the SELinux perf_event { kernel } hook. A task can set exclude_kernel=1 while still requesting kernel callchains (PERF_SAMPLE_CALLCHAIN with exclude_callchain_kernel=0); perf_callchain() then collects the kernel part independently of the sampling filter. Hardware PMU events counting only user mode can take their overflow interrupt in kernel context (skid), so perf_callchain_kernel() unwinds the kernel stack and writes kernel text addresses into the ring buffer.

The fix calls perf_allow_kernel() when either kernel sampling or kernel callchains are requested:

if (!attr.exclude_kernel ||
    ((attr.sample_type & PERF_SAMPLE_CALLCHAIN) &&
     !attr.exclude_callchain_kernel)) {
        err = perf_allow_kernel();
        if (err)
                return err;
}

The snippet above is the 6.12.113 stable-tree form, in which perf_allow_kernel() no longer takes an attribute pointer; the upstream commit uses the same condition with perf_allow_kernel(&attr).

See docs/analysis.md for the full call path, skid mechanics, SELinux audit evidence and device measurements.

Verification design

The PoC runs three cases as an unprivileged shell process:

Caseperf_event attributesExpected on a vulnerable kernel
Aexclude_kernel=0EACCES (kernel-perf gate active)
Bexclude_kernel=1, exclude_callchain_kernel=1event allowed, no kernel callchain frames
Cexclude_kernel=1, exclude_callchain_kernel=0event allowed, kernel callchain frames disclosed

On the verified YLP-W00 (kernel 6.12.38, SELinux Enforcing):

  • Case A: EACCES, with avc: denied { kernel } ... tclass=perf_event in the audit log.
  • Case B: event opened, 0 kernel callchain entries.
  • Case C: event opened, ~179k samples with kernel context and ~537k kernel callchain addresses (3 frames per sample), all in the 0xffffffde... kernel range.

On the verified Redmi K70 (kernel 5.15.178, third-party Android 17 ROM, SELinux Enforcing), the same pattern was observed:

  • Case A: EACCES, same SELinux { kernel } audit line.
  • Case B: event opened, 0 kernel callchain entries.
  • Case C: event opened; 14,090 kernel callchain addresses in 2,432 samples at period 1000, and 5,049 in 876 samples at period 5000, in the 0xffffffdd... kernel range.

On the verified devices, a fixed kernel would reject case C at perf_event_open() with EACCES because the kernel-callchain request now reaches perf_allow_kernel().

KASLR slide derivation

With a matching vmlinux, the leaked callchain frames become a per-boot KASLR slide oracle. The arm64 virtual slide is 2 MiB aligned, and every leaked text address must satisfy _stext <= addr - slide < _etext; intersecting those intervals normally leaves exactly one slide.

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

On the verified YLP-W00 6.12.38 boot, three frames give the unique slide=0x1DCFC00000 (runtime _text=0xffffffde4fc00000):

Leaked addressLink-time addressSymbol
0xffffffde50748c780xffffffc080b48c78local_daif_restore+0x4 (msr DAIF, x0; ret)
0xffffffde50d406e00xffffffc0811406e0el0t_64_sync_handler+0x70 (after bl el0_svc)
0xffffffde4fc115880xffffffc080011588el0t_64_sync+0x1a8 (after bl el0t_64_sync_handler)

These are core kernel text frames, not modules: the pending PMU interrupt is delivered exactly at the ret after msr DAIF, x0 in the syscall entry path. Raw addresses cannot be symbolized directly under KASLR; subtract the slide first. If a frame is a module address, no slide places all addresses in [_stext, _etext) and the script reports that instead of guessing. The slide changes on every boot and must not be cached across reboots.

The Redmi K70 (5.15.178) has also been validated. Its matching image was pulled from /sdcard/boot.img (boot v4; the embedded version string matches the running kernel) and vmlinux-to-elf recovered 158,668 symbols. Seven unique frames from one period-1000 run give slide=0x1D93800000 (runtime _text=0xffffffdd9b800000), mapping to syscall_trace_enter, el0_svc_common, do_el0_svc, el0_svc, el0t_64_sync_handler, el0t_64_sync, and syscall_trace_exit.

The link-time base is branch-specific — do not reuse the 6.12 value on 5.15:

Kernel branchKIMAGE_VADDR
Android 13-5.15 (K70)0xffffffc008000000
Android 16-6.12 (YLP-W00)0xffffffc080000000
upstream v5.15 (different layout)0xffffffc010000000

vmlinux-to-elf auto-detected 0xffffffc008000000 for the K70 image, matching AOSP android13-5.15. If the auto-detected ELF base differs from the branch's KIMAGE_VADDR, pass --link-base (or SLIDE_LINK_BASE=): symbolization stays in ELF space, the reported slide is made relative to the true base, and the runtime _text is invariant to a constant base shift. Feeding the K70 leak to the 6.12.38 image remains a deliberate negative test and yields no candidate.

Build

Requirements:

  • Android NDK r29 at /opt/android-ndk-r29 (override with NDK=)
  • GNU make
  • adb
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

Artifacts are written to build/ and are git-ignored.

Usage

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

Options:

--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)

Exit codes:

Download Tool