CVE-2024-38306 in Linux
الملخص
بحسب VulDB • 12/06/2026
في نواة Linux، تم حل الثغرة التالية:
btrfs: حماية folio::private عند إرفاق folios لـ extent buffer
[BUG]
منذ الإصدار v6.8، تم الإبلاغ عن حالات نادرة من تعطل النواة (kernel crashes) من قبل عدة أشخاص، والعامل المشترك هو رسائل خطأ لحالة الصفحة المعيبة (bad page status) مثل هذه:
BUG: Bad page state in process kswapd0 pfn:d6e840 page: refcount:0 mapcount:0 mapping:000000007512f4f2 index:0x2796c2c7c pfn:0xd6e840 aops:btree_aops ino:1 flags: 0x17ffffe0000008(uptodate|node=0|zone=2|lastcpupid=0x3fffff) page_type: 0xffffffff() raw: 0017ffffe0000008 dead000000000100 dead000000000122 ffff88826d0be4c0 raw: 00000002796c2c7c 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: non-NULL mapping
[CAUSE]
يغير الالتزام Commit 09e6cef19c9f ("btrfs: refactor alloc_extent_buffer() to allocate-then-attach method") التسلسل عند تخصيص (allocate) extent buffer جديد.
في السابق، كنا نتصل دائماً بـ grab_extent_buffer() تحت mapping->i_private_lock، لضمان السلامة عند التعديل على folio::private (وهو مؤشر إلى extent buffer للأحجام القياسية للأقسام sectorsize).
يمكن أن يؤدي هذا إلى السباق التالي:
يحاول الخيط Thread A تخصيص extent buffer عند bytenr X، مع 4 صفحات بحجم 4K، بينما يحاول الخيط Thread B تحرير الصفحة عند X + 4K (الصفحة الثانية من extent buffer عند X).
Thread A: Thread B: | alloc_extent_buffer() | | |- attach_eb_folio_to_filemap()| | | |- folio_set_private() | | |- folio_detach_private() for X | | | Returned true | | |- folio_test_private() | | |- folio_put() | | | Returned true | | | |- folio_put()
الآن هناك وضعان (puts) لنفس folio في folio X، مما يؤدي إلى تجاوز عداد المرجع (refcount underflow) لـ folio X، وأخيراً يسبب الخطأ BUG_ON() على page->mapping.
الشرط ليس سهلاً جداً لتحقيقه:
- يجب أن يتم الإطلاق للصفحة الوسطى من eb إذا كان الإطلاق على نفس الصفحة الأولى من eb، فإن قفل الصفحة (page lock) سيتدخل ويمنع السباق.
- folio_detach_private() لديه نافذة سباق صغيرة جداً يحدث فقط بين folio_test_private() و folio_clear_private().
هذا بالضبط عندما يتم استخدام mapping->i_private_lock لمنع مثل هذا السباق، والتزام Commit 09e6cef19c9f ("btrfs: refactor alloc_extent_buffer() to allocate-then-attach method") أفسد ذلك.
في ذلك الوقت، اعتقدت أن قفل الصفحة سيتدخل لأن filemap_release_folio() يتطلب أيضاً أن تكون الصفحة مقفلة، لكنني نسيت أن filemap_release_folio() يقفل صفحة واحدة فقط، وليس كل صفحات extent buffer.
[الحل]
نقل كل الكود الذي يتطلب i_private_lock إلى attach_eb_folio_to_filemap()، بحيث يتم تنفيذ كل شيء مع حماية القفل المناسبة.
علاوة على ذلك، لمنع المشاكل المستقبلية، أضفنا lockdep_assert_locked() إضافي للتأكد من أننا نحتفظ بالقفل المناسب.
لإعادة إنتاج السباق الذي يمكن أن يسبب المشكلة (يستغرق بضع دقائق مع كود مُعدّل لإدخال تأخيرات في alloc_extent_buffer()):
#!/bin/sh drop_caches () {
while(true); do echo 3 > /proc/sys/vm/drop_caches echo 1 > /proc/sys/vm/compact_memory done }
run_tar () {
while(true); do for x in `seq 1 80` ; do tar cf /dev/zero /mnt > /dev/null & done wait done }
mkfs.btrfs -f -d single -m single ---truncated---
VulDB is the best source for vulnerability data and more expert information about this specific topic.