
كتابة عن ثغرة تجاوز سعة الكومة في برنامج split من GNU coreutils. CVE-2024-0684
ثغرة أمنية في برنامج split من مجموعة GNU coreutils تسمح بحدوث تجاوز سعة للمخزن المؤقت في الكومة (heap buffer overflow) باستخدام بيانات يتحكم بها المستخدم.
تم تقديمه في 40bf1591bb4362fa91e501bcec7c2029c5f65a43 بتاريخ 2023-03-04. تم إصدار إصلاح مع c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 بتاريخ 2024-01-17.
الإصدارات المتأثرة: GNU coreutils v9.4; v9.3; v9.2
إثبات المفهوم:
يمكن استخدام ملف split_me المثال في هذا المستودع لإحداث عطل في الإصدارات المتأثرة.
split -C 1024 ./split_me
سيؤدي هذا إلى تعطل split مع خطأ تجزئة ().
SIGABRTاكتشفت هذه الثغرة أثناء محاولة أتمتة استخراج البيانات من الأنظمة المعزولة (air-gapped) باستخدام رموز QR.
رموز QR المولدة باستخدام qrencode سعتها حوالي 4000 حرف، مما استدعى استخدامًا مكثفًا لـ split.
في إحدى حالات الاختبار المحددة، تعطل split مع خطأ تجزئة.
بما أن GNU coreutils مفتوحة المصدر، يمكننا استخدام المصدر لتحديد الخلل، بدلاً من الاضطرار إلى هندسة عكسية لملف ثنائي. في المشاريع مفتوحة المصدر، يجب أن تكون محددًا قدر الإمكان في تقرير الخلل، ويفضل تقديم الالتزام (commit) والسطر الدقيقين الذين أدخلا الخلل، بالإضافة إلى إصلاح مقترح. يتيح ذلك للمشرفين التحقق من تقريرك بسرعة ويقلل وقت الاستجابة.
أثناء التحقق من الخلل عبر أنظمة مختلفة،
لاحظت أن العطل يحدث فقط في الإصدارات الحديثة نسبيًا من split.
إذا كان لديك التزام جيد والتزام سيئ،
فيمكنك إجراء بحث ثنائي (binary search) على تاريخ الالتزامات،
من أجل العثور على الالتزام الذي أدخل الخلل بالفعل.
يوفر Git أداة محددة لهذه الحالة: git bisect.
ستقترح تلقائيًا التزامات لاختبارها وتسمح لك بتحديدها كجيدة أو سيئة.
في النهاية ستحصل على الالتزام الذي أدخل الخلل؛ في حالتنا:
commit 40bf1591bb4362fa91e501bcec7c2029c5f65a43
Author: Paul Eggert <[email protected]>
Date: Sat Mar 4 11:42:16 2023 -0800
split: prefer signed integers to size_t
This allows for better runtime checking with gcc
-fsanitize=undefined.
* src/split.c: Include idx.h.
(open_pipes_alloc, n_open_pipes, suffix_length)
(set_suffix_length, input_file_size, sufindex, outbase_length)
(outfile_length, addsuf_length, create, cwrite, bytes_split)
(lines_split, line_bytes_split, lines_chunk_split)
(bytes_chunk_extract, ofile_open, lines_rr, main):
Prefer signed integers (typically idx_t) to size_t.
src/split.c | 105 ++++++++++++++++++++++++++++++------------------------------
1 file changed, 52 insertions(+), 53 deletions(-)
يمكننا بعد ذلك ترجمة البرنامج (يفضل باستخدام أداة تعقب العناوين (address sanitizer))،
من أجل العثور على السطر الدقيق الذي يتعطل فيه.
بوجود حوالي 50 سطرًا فقط لفحصها،
يصبح من السهل تحديد الخلل.
في حالتنا، حدث العطل في استدعاء memcpy() بمؤشرات غير صحيحة.
وبالفعل، إذا تحققنا من المنطقة حول استدعاء memcpy()،
نجد تغييرًا (diff) يغير حسابات المؤشرات قبلها مباشرة:
@@ -816,15 +820,10 @@
/* Update hold if needed. */
if ((eoc && split_rest) || (!eoc && n_left))
{
- size_t n_buf = eoc ? split_rest : n_left;
+ idx_t n_buf = eoc ? split_rest : n_left;
if (hold_size - n_hold < n_buf)
- {
- if (hold_size <= SIZE_MAX - bufsize)
- hold_size += bufsize;
- else
- xalloc_die ();
- hold = xrealloc (hold, hold_size);
- }
+ hold = xpalloc (hold, &hold_size, n_buf - (hold_size - n_hold),
+ -1, sizeof *hold);
memcpy (hold + n_hold, sob, n_buf);
n_hold += n_buf;
n_left -= n_buf;
إذا تراجعنا عن هذه التغييرات وأعدنا الترجمة،
فإن split يعالج جميع حالات الاختبار لدينا دون خطأ.
كل ما تبقى هو مراجعة المنطق للتحقق من الخلل وتطوير إصلاح.