أمان تطبيقات Rust: أهم 10 ثغرات عملية
يُعرف Rust كلغة نظام آمنة قادرة على منع فئات كاملة من الأخطاء الشائعة في C و C++. لكن هذا لا يعني أن تطبيقات Rust غير قابلة للاختراق. نظام الملكية (ownership) ومدقق الاقتراض (borrow checker) يحميان من العديد من المشاكل، لكن كود unsafe والأخطاء المنطقية والتبعيات الخارجية تفتح نواقل هجوم جديدة. في هذه المقالة، سنستعرض 10 من أكثر الثغرات خطورة التي يواجهها مطورو Rust - مع أمثلة كود حقيقية ونصائح عملية لإصلاحها.
ستكون المادة مفيدة لكل من المبتدئين والمهندسين ذوي الخبرة الراغبين في تعميق معرفتهم بالأمان في نظام Rust البيئي.
1. الاستخدام غير الآمن لـ unsafe
الكلمة المفتاحية unsafe هي المصدر الأكثر شيوعًا للثغرات في Rust. فهي تعطل فحوصات مدقق الاقتراض وتسمح بالعمل مع المؤشرات الأولية، لكن المسؤولية عن الصحة تقع بالكامل على عاتق المطور.
// كود ضعيف: إلغاء الإشارة إلى مؤشر فارغfn dangerous() { let ptr: *const i32 = std::ptr::null(); unsafe { println!("{}", *ptr); // سلوك غير محدد! تعطل }}الحل: قلل من استخدام unsafe. ضع العمليات الخطرة في وحدات منفصلة مع اختبار دقيق وتوثيق للثوابت. استخدم NonNull<T> بدلاً من المؤشرات الأولية.
2. حالات السباق (Data Races)
على الرغم من أن مدقق الاقتراض يمنع حالات السباق في الكود الآمن، إلا أن unsafe والاستخدام غير الصحيح لأدوات التزامن قد يؤديان إلى حالة سباق.
// كود ضعيف: حالة سباق عبر unsafestatic mut COUNTER: i32 = 0;
fn increment() { unsafe { COUNTER += 1; // عملية غير ذرية }}الحل: استخدم AtomicI32 أو Mutex أو RwLock. في الكود الآمن لـ Rust، حالات السباق مستحيلة - ثق بالمترجم.
3. تجاوز عدد صحيح (Integer Overflow)
في وضع التصحيح (debug)، يحدث ذعر (panic) في Rust عند التجاوز، لكن في وضع الإصدار (release) يحدث التفاف (wrapping) افتراضيًا، مما قد يؤدي إلى سلوك غير متوقع.
// ثغرة محتملة: منطق التجاوزfn allocate_buffer(size: usize) { let header_size = 64; let total = size + header_size; // قد يتجاوز let buf = vec![0u8; total]; // ذعر أو مخزن مؤقت صغير}الحل: استخدم دوال checked_add() أو saturating_add() أو تحققًا صريحًا. للحسابات الحرجة، استخدم crate overflow-checks.
4. تسرب الذاكرة في كود unsafe
يضمن Rust عدم وجود تسرب للذاكرة في الكود الآمن، لكن unsafe قد يخالف هذا المبدأ. على سبيل المثال، إدارة غير صحيحة للمؤشرات الأولية أو المراجع الدائرية مع Rc.
// تسرب ذاكرة: نسيان استدعاء dropfn leak_memory() { let ptr = Box::into_raw(Box::new(42)); // ptr لم يتم تحريره - تسرب}الحل: استخدم Box::from_raw() للتحرير الصحيح. للمراجع الدائرية، استخدم Weak أو Arc.
5. استدعاءات FFI غير آمنة
استدعاء دوال C عبر FFI (Foreign Function Interface) هو أحد المصادر الرئيسية للثغرات. لا يمكن لـ Rust التحكم في أمان الكود الخارجي.
// FFI خطير: تمرير مؤشر غير صحيحextern "C" { fn c_free(ptr: *mut c_void);}
fn free_wrapper(ptr: *mut c_void) { unsafe { c_free(ptr); } // تحرير مزدوج؟}الحل: استخدم أغلفة آمنة (wrappers)، وتحقق من حدود المخازن المؤقتة، واستخدم std::ffi::CStr للسلاسل النصية.