
ملاحظات هندسة عكسية وإثبات مفهوم (PoC) مكتفٍ ذاتيًا لسباق ذاكرة التخزين المؤقت للوصول في عميل NFS على macOS (CVE-2026-43687)، مع فرق تفكيك kext والتقاط السباق عبر dtrace.
هندسة عكسية مستقلة لسباق ذاكرة الوصول المؤقت (access-cache) في عميل NFS على macOS (CVE-2026-43687)، بالإضافة إلى PoC فعّال يُشغّل السباق ويلتقطه مباشرةً باستخدام dtrace.
الخلل هو قراءة غير متزامنة لمؤشر ذاكرة الوصول المؤقت nfsnode في _nfs_vnop_access. يمكن لخادم NFSv3 خبيث أن يدفع ذاكرة الوصول المؤقت إلى إعادة التخصيص بينما يقرأها خيط آخر، مما يُنتج كشفاً لذاكرة النواة يمكن لخادم معادٍ التأثير عليه. يُصلح macOS 26.7 هذا بإدراج lck_rw_t عند nfsnode+0x158 وأخذه مشتركاً حول قراءة الذاكرة المؤقتة.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-43687 |
| المكوّن | com.apple.filesystems.nfs (_nfs_vnop_access) |
| المتأثر | macOS Tahoe 26.6 وما قبله، iOS 26.x وما قبله |
| مُرقّع في | macOS Tahoe 26.7، macOS Golden Gate 27، iOS 26.7، iOS 27 |
| تأثير الإشعار | "قد يؤدي الاتصال بخادم NFS خبيث إلى كشف ذاكرة النواة." |
| CVSS v3.1 | 6.5 (متوسط) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N |
| أبلغ عنه | R4mbb من KRsecurity وPeter Malone (وفق إشعار Apple) |
يوثّق إشعار Apple الخاص بـ CVE-2026-43687 التأثير وإصدار الترقيع. لكنه لا يوثّق الآلية التقنية:
nfsnode يتعرض للسباقaccess(2) بدلاً من stat(2)لم يُعثر على أي تقرير تقني عام وقت الكتابة. يسدّ هذا المستودع تلك الفجوة بتحليل هندسة عكسية مستقل لـ NFS kext بين 26.6 و26.7، مع PoC فعّال يعيد إنتاج السباق على هدف حي.
هذا ليس ادعاء اكتشاف. أبلغ عن CVE كلٌّ من R4mbb وPeter Malone ورقّعته Apple. المساهمة هنا هي التحليل التقني وإعادة الإنتاج.
تقرأ _nfs_vnop_access في عميل NFS المؤشر nfsnode+0x158 — المؤشر إلى مصفوفة ذاكرة الوصول المؤقت لكل UID — مرتين داخل استدعاء واحد، دون حمل أي قفل:
; macOS 26.6, com.apple.filesystems.nfs
fffffe000b52def4 ldr w8, [x20, #0x160] ; count
fffffe000b52def8 cmp w23, w8
fffffe000b52defc b.ge ...
fffffe000b52df00 ldr x8, [x20, #0x158] ; cache ptr (read #1)
...
fffffe000b52df98 ldr x8, [x20, #0x158] ; cache ptr (read #2)
fffffe000b52dfb0 ldr w21, [x9] ; dereference
الكاتب، _nfs_nget، يعيد تخصيص المصفوفة بـ kalloc_data ويخزّن النتيجة عند +0x158 كلما رأى العميل UID جديداً من الخادم:
fffffe000b52100c bl 0xfffffe000b6a1dd8 ; kalloc
fffffe000b521010 str x0, [x22, #0x158] ; cache ptr
fffffe000b521018 str w20, [x22, #0x160] ; cache count
إذا وصل خيط آخر إلى _nfs_nget على نفس nfsnode بين تحميلَي القارئ، فإن التحميل الثاني يُعيد المؤشر الجديد بينما كانت القراءة الأولى — المستخدمة أصلاً لحساب إزاحة — مبنية على القديم. القراءة غير المباشرة اللاحقة تقرأ من كومة النواة المحرّرة.
إصلاح 26.7:
fffffe000b9e3064 str x0, [x22, #0x168] ; cache ptr moved
fffffe000b9e306c str w28, [x22, #0x170] ; cache count moved
fffffe000b9e307c add x0, x22, #0x158 ; lock slot
fffffe000b9e3084 bl _lck_rw_init ; init RW lock
وفي _nfs_vnop_access:
fffffe000b9f0200 add x0, x20, #0x158
fffffe000b9f0204 bl _lck_rw_lock_shared ; take the lock
fffffe000b9f0208 ldr x9, [x20, #0x168] ; cache pointer
...
fffffe000b9f0230 bl _lck_rw_unlock_shared ; release the lock
تغيّر تخطيط البنية:
_nfs_nget و_nfs_vnop_access بين NFS kext في 26.6 و26.7 — انظر
docs/PATCH_DIFF.mdnfsnode+0x158 في 26.6lck_rw_t عند +0x158، نقل مؤشر الذاكرة المؤقتة إلى +0x168، أخذ lck_rw_lock_shared حول القراءةaccess(2) يدخل _nfs_vnop_access؛ stat(2) يمر عبر _nfs_getattr ولا يصل أبداً إلى الدالة المعرّضةانظر docs/ANALYSIS.md للتقرير الكامل وdocs/ARTIFACTS.md للعناوين وعينات السجلات.
poc.sh — PoC بملف واحد مكتفٍ ذاتياً:
nfsd الخاص بـ Apple لتحرير المنفذ 2049127.0.0.1 يبدّل UID المُبلَّغ عنه في كل ردnoacaccess(2) عبر test -r / test -wnfs_vnop_access، مسجّلاً أي استدعاء يتغيّر فيه nfsnode+0x158 أثناء الاستدعاء_nfs_vnop_access التي تلاحظ تغيّر المصفوفة أثناء استدعاء واحدالسباق هو الشرط المسبق للكشف. لتحويل السباق إلى تسريب فعلي، سيحتاج المهاجم إلى ملاحظة القارئ وهو يستخدم المؤشر القديم ونشر تلك البايتات إلى مكان يمكنه قراءته. على arm64e، القيمة عند nfsnode+0x158 موقّعة بـ PAC ومفتاح كل إقلاع غير متاح من مساحة المستخدم، لذا يمكن لـ dtrace وحده ملاحظة السباق لكنه لا يستطيع فك تشفير المؤشر. انظر قسم "المسارات المُختبرة والمستبعدة" في docs/ANALYSIS.md.
التأثير المُوضَّح هو السباق نفسه — النافذة الدقيقة التي يُغلقها إصلاح القفل RW في 26.7.
dtrace متاح (قد يتطلب تعديل SIP في بعض التثبيتات)python3، dscl، mountيعمل PoC بالكامل على الهدف. يرتبط خادم NFS الخبيث بـ 127.0.0.1 والتركيب عبر loopback. هذا يُبقي PoC مكتفياً ذاتياً وقابلاً لإعادة الإنتاج دون إعداد شبكة.
chmod +x poc.sh
sudo ./poc.sh
ضبط اختياري عبر متغيرات البيئة:
sudo HAMMER_COUNT=30 RUN_SECONDS=600 ./poc.sh
يحدد HAMMER_COUNT عدد خيوط المطرقة لكل مستخدم (يستخدم أي حسابات nfsuserNNN موجودة، وينشئها حسب الحاجة). يحدد RUN_SECONDS نافذة dtrace.
[*] ensuring nfsuser accounts exist (UID 201..240)
nfsuser accounts available: 12
[*] starting evil NFS server on 127.0.0.1:2049
[*] mounting /Users/Shared/nfs_test (with noac)
mount check: hello.txt statable
[*] spawning up to 12 per-user threads + 1 root loop
started 12 user threads + 1 root loop
[*] running dtrace for 180s — looking for RACE lines
[*] stopping dtrace
[*] cleaning up
=================== SUMMARY ===================
RACE events caught: 16
Vulnerable interleaving observed — sample:
RACE nd=fffffe5f53cbb940 in=(0,0) out=(b0967e0023297878,0)
RACE nd=fffffe5f534db940 in=(0,0) out=(84dd7e0023297878,0)
RACE nd=fffffe5f53cbb940 in=(0,0) out=(c9a37e0023297878,0)
RACE nd=fffffe5f538ab940 in=(0,0) out=(1867e0023297878,0)
RACE nd=fffffe5f539db940 in=(0,0) out=(149bfe0023297878,0)
CVE-2026-43687 trigger SUCCESSFUL
===============================================
كل سطر RACE هو استدعاء واحد لـ nfs_vnop_access تغيّر فيه مؤشر ذاكرة الوصول المؤقت أثناء الاستدعاء.
على نظام مُرقّع (26.7 / 27)، يُنتج نفس عبء العمل صفر أحداث RACE. انظر docs/PATCH_DIFF.md لمقارنة التفكيك.
أُصلح خللان في خادم PoC أثناء التطوير وهما موثّقان هنا حتى لا يصطدم بهما من يبنون أدوات مشابهة:
ACCESS3resok يتطلب post_op_attr، وليس fattr3. يعرّف RFC 1813 الرد كـ post_op_attr obj_attributes; uint32 access;. يتضمن post_op_attr بادئة bool قبل fattr3. حذف ذلك الـ bool يجعل الرد أقصر بـ 4 بايت؛ يرفضه العميل بصمت ولا يصبح التركيب قابلاً للاستخدام أبداً.
يجب أن يُعيد LOOKUP لأسماء AppleDouble (._*) القيمة NFS3ERR_NOENT (2)، وليس NFS3ERR_STALE (70). يتحقق macOS من ملفات ._<name> الجانبية أثناء تحليل المسار العادي. إعادة STALE تسمّم التركيب.
كلاهما موثّق في docs/ANALYSIS.md.
قيم out في مخرجات RACE لها نمط ثابت في البتات الـ48 المنخفضة (...7e0023297878) عبر nfsnodes مختلفة، وتتفاوت فقط في البتات الـ16 العليا. هذا يؤكد أن الحقل موقّع بـ PAC أو مُموّه، وليس مؤشر نواة خاماً. يمكنك ملاحظة انتقال الحالة (NULL → مُعبّأ) من مساحة المستخدم، لكن لا يمكنك فك تشفير المؤشر أو إلغاء الإشارة إليه دون مفتاح PAC الخاص بكل إقلاع في النواة.
للسبب نفسه، فإن الرمزية مقابل KDK ليست مفيدة لهذه القيم — فهي ليست عناوين نسبية للنص.
يُقدَّم هذا المستودع لأبحاث الأمن الدفاعي والتعليم فقط.
MIT. انظر LICENSE.
| الإزاحة | macOS 26.6 | macOS 26.7 |
|---|
+0x158 | مؤشر مصفوفة الذاكرة المؤقتة | lck_rw_t |
+0x160 | عدد الذاكرة المؤقتة | (جزء من القفل) |
+0x168 | (أخرى) | مؤشر مصفوفة الذاكرة المؤقتة |
+0x170 | (أخرى) | عدد الذاكرة المؤقتة |