
استغلال إثبات المفهوم (PoC) لثغرة CVE-2022-22706: يستغل خللًا في الكتابة إلى ذاكرة التخزين المؤقت للصفحات (page cache) في برنامج تشغيل نواة وحدة معالجة الرسوميات Mali لتعديل ملف /etc/passwd في الذاكرة والحصول على صلاحيات الجذر (root shell).
يمنح برنامج تشغيل Mali GPU من Arm مساحة المستخدم تعيينًا قابلاً للكتابة عبر وحدة المعالجة المركزية لصفحات كانت مثبّتة للقراءة فقط؛ لذا تحصل عملية غير مميَّزة على اسم مستعار قابل للكتابة لمخبأ الصفحات (page cache) الذي يسند ملفًا لا يمكنها فتحه إلا بـ O_RDONLY.
يجعل exploit.c حقل كلمة مرور الجذر فارغًا في مخبأ صفحات /etc/passwd وينفّذ su root. لا يُعدَّل الملف الموجود على القرص أبدًا.
إثبات مفهوم لشجرة برنامج التشغيل وهدف QEMU في هذا المستودع (
mali_kbaser35p0-01eac0،CONFIG_MALI_NO_MALI=y، x86_64 GKI 5.15). شغّله داخل الجهاز الافتراضي.
هناك قراران متناقضان حول من يسمح له بكتابة الصفحات المستوردة.
تأتي قابلية كتابة تعيين وحدة المعالجة المركزية من KBASE_REG_CPU_WR، لكن التثبيت يطلب حق الوصول للكتابة بناءً على KBASE_REG_GPU_WR وحدها:
/* mali_kbase_mem.c */
pinned_pages = pin_user_pages_remote(
mm, address, alloc->imported.user_buf.nr_pages,
reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0, pages, NULL, NULL);
عند الاستيراد مع ضبط CPU_WR ومسح GPU_WR تحصل على الشقّين معًا: تعيين CPU قابل للكتابة للاستيراد، وتثبيت عبر get_user_pages دون FOLL_WRITE. وبدون FOLL_WRITE لا يكسر get_user_pages آلية النسخ عند الكتابة (COW) أبدًا في تعيين ملف للقراءة فقط، بل يُرجع صفحة مخبأ الصفحات نفسها. ثم يعيد برنامج التشغيل تعيين تلك الصفحات نفسها إلى مساحة المستخدم كصفحات قابلة للكتابة.
أُصلحت في 5381ff7 ("GPUCORE-32592 Fix userbuf imports to respect RO memory")، إذ تستمد حق الوصول للكتابة من KBASE_REG_CPU_WR | KBASE_REG_GPU_WR بدلاً من GPU_WR وحدها.
sequenceDiagram
participant U as unprivileged process
participant K as mali_kbase
participant PC as page cache
U->>U: mmap /etc/passwd O_RDONLY, PROT_READ
U->>K: MEM_IMPORT(anon page, CPU_RD|CPU_WR|GPU_RD)
Note over K: address recorded, nothing pinned yet
U->>U: munmap(anon) + mremap file mapping onto that VA
U->>K: mmap(import cookie) → writable CPU mapping
U->>K: JOB_SUBMIT(EXTERNAL_RESOURCES)
K->>PC: pin_user_pages_remote() without FOLL_WRITE
U->>PC: memcpy() through the writable mapping
U->>U: execl("/bin/su", "su", "root")MEM_IMPORT يسجّل عنوانًا فقط؛ أما التثبيت فيحدث لاحقًا عند JOB_SUBMIT. هذه الفجوة هي ما يسمح باستبدال الصفحة المجهولة بتعيين الملف بينهما.
التعديل يحافظ على الطول، لذا لا يتزحزح أي شيء بعد سطر الجذر:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
يعيد busybox su القيمة CHECKPASS_PW_HAS_EMPTY_PASSWORD قبل أن يطلب كلمة المرور عندما يكون حقل كلمة المرور فارغًا، ولا يقرأ /etc/shadow إلا عندما يكون الحقل x تمامًا.
gcc -static -o exploit exploit.c
انسخه إلى الجهاز الافتراضي وشغّله كمستخدم غير مميَّز:
$ ./exploit

مُسجَّل عبر SSH إلى هدف QEMU كمستخدم user (uid 1000). يرقّع ./exploit سطر الجذر في مخبأ صفحات /etc/passwd وينفّذ su root، التي تنقلك مباشرة إلى صدفة جذر دون طلب كلمة المرور.
حتى بعد مغادرة تلك الصدفة وتشغيل su مرة أخرى تظل تحصل على صلاحيات الجذر: تبقى الصفحة مخزنة في المخبأ، لذلك كل open()/read() لاحق لـ /etc/passwd يرى البايتات المرقّعة.
بعد أن يؤدي echo 1 > /proc/sys/vm/drop_caches إلى إخلاء الصفحة وإعادة قراءة الملف من التخزين، يطلب su كلمة المرور مجددًا. البايتات الموجودة على القرص لم تُمس أبدًا.