
إثبات مفهوم (PoC) موثّق وتحليل لتجاوز فحص الأذونات في Linux perf/core يسرّب عناوين سلسلة استدعاءات النواة إلى مهام Android غير ذات امتيازات، مما يتيح استنتاج إزاحة KASLR.
English | 简体中文
تجاوز فحص الإذن في perf/core على Linux: يمكن لمهمة غير مميزة طلب سلاسل استدعاء النواة مع تعيين 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) والـ backports المستقرة المقابلة.
كان 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 مع الاستمرار في طلب سلاسل استدعاء النواة (PERF_SAMPLE_CALLCHAIN مع exclude_callchain_kernel=0)؛ حينها يجمع 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;
}
المقتطف أعلاه هو صيغة شجرة stable 6.12.113، حيث لم تعد perf_allow_kernel() تأخذ مؤشر سمة؛ يستخدم الـ commit upstream نفس الشرط مع perf_allow_kernel(&attr).
راجع docs/analysis.md للحصول على مسار الاستدعاء الكامل، وآلية skid، وأدلة تدقيق SELinux وقياسات الجهاز.
يشغّل PoC ثلاث حالات كعملية shell غير مميزة:
| الحالة | سمات perf_event | المتوقع على نواة مصابة |
|---|---|---|
| A | exclude_kernel=0 | EACCES (بوابة kernel-perf نشطة) |
| B | exclude_kernel=1، exclude_callchain_kernel=1 | الحدث مسموح، لا إطارات callchain للنواة |
| C | exclude_kernel=1، exclude_callchain_kernel=0 | الحدث مسموح، إطارات callchain للنواة مكشوفة |
على جهاز YLP-W00 الذي تم التحقق منه (kernel 6.12.38، SELinux Enforcing):
EACCES، مع
avc: denied { kernel } ... tclass=perf_event في سجل التدقيق.0xffffffde....على جهاز Redmi K70 الذي تم التحقق منه (kernel 5.15.178، ROM أندرويد 17 من طرف ثالث، SELinux Enforcing)، لوحظ النمط نفسه:
EACCES، نفس سطر تدقيق SELinux { kernel }.0xffffffdd....على الأجهزة التي تم التحقق منها، سترفض النواة المُصلحة الحالة C عند
perf_event_open() مع EACCES لأن طلب kernel-callchain يصل الآن
إلى perf_allow_kernel().
مع vmlinux مطابق، تصبح إطارات callchain المسربة أداة استنباط إزاحة 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) |
هذه إطارات نصية في نواة النواة (kernel text)، وليست وحدات (modules): يُسلَّم مقاطعة PMU المعلّقة
بالضبط عند تعليمة ret بعد msr DAIF, x0 في مسار دخول استدعاء النظام (syscall).
لا يمكن رمزنة العناوين الخام مباشرةً تحت KASLR؛
اطرح الإزاحة (slide) أولاً. إذا كان الإطار عنوان وحدة (module)، فلا توجد إزاحة تضع جميع
العناوين في [_stext, _etext) ويُبلّغ السكربت عن ذلك بدلاً من
التخمين. تتغيّر الإزاحة عند كل إقلاع ويجب عدم تخزينها مؤقتاً عبر
عمليات إعادة التشغيل.
تم التحقق أيضاً من Redmi K70 (5.15.178). وقد سُحبت صورته المطابقة
من /sdcard/boot.img (boot v4؛ سلسلة الإصدار المضمّنة تطابق
نواة التشغيل) واستعاد vmlinux-to-elf عدد 158,668 رمزاً. سبعة إطارات فريدة
من تشغيل واحد بدورة 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 |
| upstream v5.15 (تخطيط مختلف) | 0xffffffc010000000 |
اكتشف vmlinux-to-elf تلقائياً 0xffffffc008000000 لصورة K70، بما يطابق
AOSP android13-5.15. إذا اختلفت قاعدة ELF المكتشفة تلقائياً عن
KIMAGE_VADDR الخاصة بالفرع، فمرّر --link-base (أو SLIDE_LINK_BASE=): تبقى الرمزنة
في فضاء ELF، وتُصبح الإزاحة المُبلَّغ عنها نسبية إلى القاعدة الحقيقية،
ويكون _text في وقت التشغيل ثابتاً تجاه إزاحة قاعدة ثابتة. ويبقى تمرير تسريب K70 إلى
صورة 6.12.38 اختباراً سلبياً متعمّداً ولا يُنتج أي مرشّح.
المتطلبات:
/opt/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 | لا يوجد قيد kernel-perf لتجاوزه |
| 6 | خطأ في الإعداد (لا يوجد حدث PMU عتادي صالح للاستخدام، errno غير متوقع) |