
تحليل تقني متعمق لثغرة CVE-2026-31431 (Copy Fail) في نواة لينكس، وهي تصعيد صلاحيات محلي عبر خلل في قائمة التشتت (scatterlist) الموضعية في AF_ALG، بما في ذلك السبب الجذري، ومسار الاستغلال، ودليل تقديم التصحيح.
الخطورة: CVSS 7.8 (عالية)
الفئة: تصعيد امتيازات محلي
أُدخلت في: Linux 4.14 (2017)
أُصلحت في: الالتزام a664bf3d603d
CVE-2026-31431 هي ثغرة تصعيد امتيازات محلية في وحدة algif_aead في نواة لينكس — واجهة مقبس AEAD لواجهة برمجة التشفير الخاصة بالنواة الموجهة لمستخدمي الفضاء (AF_ALG).
تتيح الثغرة لمستخدم غير مميز استخدام عائلة العناوين AF_ALG في واجهة برمجة التشفير الخاصة بنواة لينكس لتنفيذ عمليات كتابة محكومة بحجم 4 بايت في ذاكرة التخزين المؤقت للصفحات (page cache)، التي تدعم النسخ في الذاكرة من الملفات. من خلال استبدال الكود في النسخة الموجودة في الذاكرة من ملف تنفيذي قابل للقراءة والمخزّن في ذاكرة التخزين المؤقت للصفحات، يمكن للمهاجم تصعيد امتيازات المستخدم عندما تقوم أي عملية مميزة لاحقًا بتشغيل تلك النسخة التالفة من الملف.
على عكس Dirty Cow (CVE-2016-5195)، التي تطلبت الفوز بسباق (race condition)، فإن Copy Fail هي عيب منطقي مباشر. فهي تُفعَّل دون سباقات أو محاولات إعادة أو نوافذ توقيتية هشّة قابلة للانهيار.
قدّم الالتزام 72548b093ee3 ("crypto: algif_aead - switch to in-place processing") تحسينًا جعل req->src == req->dst داخل aead_recvmsg()، مما ألغى الحاجة إلى تخصيص مخزن إخراج منفصل.
تسبب هذا التحسين في جعل req->src و req->dst يشيران إلى قائمة تشتت (scatterlist) مدمجة. ونتيجة لذلك، تم ربط صفحات ذاكرة التخزين المؤقت للصفحات من استدعاء splice() بشكل غير صحيح مباشرةً في قائمة التشتت الوجهة القابلة للكتابة.
تعرّف واجهة برمجة AEAD الخاصة بالنواة عقد إخراج واضحًا: يستقبل المخزن الوجهة AAD || plaintext، أي بالضبط assoclen + (cryptlen - authsize) بايت. لا شيء في الواجهة يفرض هذا العقد، ولا شيء يوثّقه كمتطلب. يفترض التصميم أن كل خوارزمية AEAD ستقيّد عمليات كتابتها في الوجهة المقصودة. خوارزمية واحدة لم تفعل ذلك.
crypto/
├── algif_aead.c ← خطأ قائمة التشتت "في المكان" (أساسي)
└── authencesn.c ← كتابة خدش خارج الحدود (المُفعِّل)
crypto/algif_aead.c — الإسناد "في المكان"/* VULNERABLE — post-2017 in-place path */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* When userspace calls splice(2) on a file, areq->tsgl contains
* page cache pages of that file. Setting dst = src hands those
* page cache pages to the crypto engine as a writable output
* buffer.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst — identical pointer; root cause */
areq->cryptlen, iv);
}
crypto/authencesn.c — كتابة الخدش خارج الحدودقالب authencesn هو غلاف AEAD تستخدمه IPsec لدعم أرقام التسلسل الموسعة (ESN). تستخدم IPsec أرقام تسلسل 64-بت مقسمة إلى نصف مرتفع (seqno_hi، البايتات 0–3 من AAD) ونصف منخفض (seqno_lo، البايتات 4–7). يحمل تنسيق الإرسال seqno_lo فقط؛ أما seqno_hi فهو ضمني.
/* crypto/authencesn.c — scratch write past output boundary */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn temporarily stores seqno_hi at offset
* (assoclen + cryptlen) in req->dst — one word past the
* legitimate output region.
*
* Under normal operation: hits kernel-owned memory.
* With in-place scatterlist: hits a page cache page.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* out-of-bounds offset */
sizeof(seqno_hi), /* 4 bytes */
1 /* write */);
/* authencesn does not restore these bytes on the failing path */
}
الإصلاح الرسمي: الالتزام a664bf3d603d — يتراجع عن تحسين 2017.
crypto/algif_aead.c — قوائم تشتت منفصلة/* FIXED — separate src and dst */
aead_request_set_crypt(req,
areq->tsgl, /* src — may contain page cache pages; read-only */
areq->rsgl, /* dst — freshly allocated kernel buffer; writable */
areq->cryptlen, iv);
التأثير: تبقى صفحات ذاكرة التخزين المؤقت للصفحات في src، التي يقرؤها محرك التشفير فقط. كتابة الخدش الخاصة بـ authencesn عند assoclen + cryptlen تهبط الآن في مخزن مخصص من النواة تملكه العملية. لا يمكن الوصول إلى أي صفحة من صفحات ذاكرة التخزين المؤقت للصفحات من مسار الكتابة.
يصف ما يلي كيف تحقق الكتابة بحجم 4 بايت تصعيد امتيازات كامل.
| الخطوة | الإجراء |
|---|---|
| 1 | افتح مقبس AF_ALG مهيأ لـ authencesn(hmac(sha256),cbc(aes)) |
| 2 | استخدم splice() على ملف ثنائي setuid قابل للقراءة (مثل /usr/bin/sudo) في المقبس — وهذا يضع صفحات ذاكرة التخزين المؤقت للصفحات الخاصة به في areq->tsgl |
| 3 | صمّم assoclen + cryptlen بحيث يتماشى إزاحة كتابة الخدش مع بايت مستهدف داخل الصورة المخزنة مؤقتًا للثنائي |
| 4 | استدعِ recvmsg() — يُفعّل مسار فك التشفير، ويكتب authencesn 4 بايت يتحكم بها المهاجم في ذاكرة التخزين المؤقت للصفحات |
| 5 | الملف على القرص لم يتغير؛ أدوات فحص التكامل وinotify وطوابع inode الزمنية غير متأثرة |
| 6 | أي تنفيذ لاحق للثنائي يعيّن صفحات ذاكرة التخزين المؤقت للصفحات المعدلة بالفعل — يعمل الكود المُصحح مع euid=0 |
تنتهي الكتابة داخل البيانات المخزنة مؤقتًا للملف المُدمج في الذاكرة، متجاوزةً صلاحيات الملف تمامًا.
./scripts/get_maintainer.pl crypto/algif_aead.c
# Herbert Xu <[email protected]>
# [email protected]
./scripts/checkpatch.pl 0001-crypto-algif_aead-revert-inplace.patch
crypto: algif_aead - restore separate src/dst scatterlists in recvmsg
Commit 72548b093ee3 ("crypto: algif_aead - switch to in-place
processing") set req->src == req->dst inside aead_recvmsg(). When
the caller uses splice(2), the source scatterlist contains page
cache pages of the spliced file. With the in-place optimization,
those same pages become the writable destination passed to the
AEAD transform.
authencesn writes 4 bytes at offset (assoclen + cryptlen) into
req->dst as scratch space for Extended Sequence Number
rearrangement and does not restore them on the error path. With
page cache pages present in dst, this write bypasses the VFS
permission layer and corrupts the in-memory image of the spliced
file without modifying the on-disk inode.
An unprivileged local user can exploit this to overwrite
arbitrary bytes in the page cache of any readable setuid binary
and escalate privileges to root.
Restore separate src and dst scatterlists so that page cache
pages are confined to the read-only source path.
Fixes: 72548b093ee3 ("crypto: algif_aead - switch to in-place processing")
Cc: [email protected] # 4.14+
Reported-by: Taeyang Lee <[email protected]>
Signed-off-by: Your Name <[email protected]>
git send-emailgit send-email \
[email protected] \
[email protected] \
[email protected] \
0001-crypto-algif_aead-revert-inplace.patch
| العنصر | الغرض |
|---|---|
موضوع subsystem: file - verb phrase | التنسيق المطلوب؛ أقل من 72 حرفًا |
Fixes: <sha> ("original title") | يُفعّل أدوات النقل الخلفي التلقائي للمستقر |
Cc: [email protected] | يعلّم التصحيح للنقل الخلفي لفروع LTS |
Reported-by: | إسناد الفضل للمكتشف |
Signed-off-by: | شهادة المنشأ للمطور — إلزامية |
تشمل النوى القابلة للاستغلال لينكس 4.14 حتى 7.0-rc، وجميع إصدارات 6.18.x قبل 6.18.22، و6.19.x قبل 6.19.12.
تخفيف مؤقت (لا يتطلب إعادة تشغيل على معظم الأنظمة):
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
لا يؤثر هذا الحل البديل على dm-crypt/LUKS أو kTLS أو IPsec/XFRM أو OpenSSL أو GnuTLS أو NSS أو SSH.
xint.io/blog/copy-fail-linux-distributionsnvd.nist.gov/vuln/detail/CVE-2026-31431a664bf3d603d (الخط الرئيسي)72548b093ee3 (لينكس 4.14، 2017)