
وحدات ماكرو Rust وأمر فرعي لـ Cargo لأتمتة الاختبار بالتغذية العشوائية (fuzzing) باستخدام afl.rs، بما في ذلك توليد مجموعة المدخلات (corpus) وتنفيذ harness، ومدمج مع إطار الاختبار الخاص بـ Rust.
test-fuzz هي أمر فرعي لـ Cargo ومجموعة من ماكروات Rust لأتمتة مهام معينة تتعلق بـ fuzzing باستخدام [afl.rs]، وتشمل:
test-fuzz تحقق هذه الأمور (جزئيًا) باستخدام أدوات الاختبار في Rust. على سبيل المثال، لتوليد corpus للـ fuzzing، يسجّل test-fuzz وسائط الهدف في كل مرة يُستدعى فيها أثناء تشغيل cargo test. وبالمثل، ينفّذ test-fuzz harness للـ fuzzing كاختبار إضافي في ملف ثنائي مُولَّد بواسطة cargo-test. هذا التكامل الوثيق مع أدوات الاختبار في Rust هو ما يبرر الاسم test-fuzz.
المحتويات
test_fuzz]test_fuzz_impl]cargo test-fuzz]test-fuzz]ثبّت cargo-test-fuzz و[afl.rs] بالأمر التالي:```sh
cargo install cargo-test-fuzz cargo-afl
## نظرة عامة
التضمين باستخدام `test-fuzz` يتم أساسًا في ثلاث خطوات:\*
1. **حدد هدف التضمين**:
- أضف التبعيات `dependencies` التالية إلى ملف `Cargo.toml` الخاص بالكريت الهدف:
```toml
serde = "*"
test-fuzz = "*"
```
- سبق الدالة الهدف بماكرو [`test_fuzz`]:
```rust
#[test_fuzz::test_fuzz]
fn foo(...) {
...
}
```
2. **أنشئ مجموعة بيانات** بتشغيل `cargo test`: ```
cargo test
cargo test-fuzz]: ```
cargo test-fuzz foo
* قد تكون هناك حاجة إلى خطوة إضافية أولية بعد إعادة التشغيل:```sh cargo afl system-config
لاحظ أن الأمر أعلاه يشغّل `sudo` داخليًا. لذلك، قد يُطلب منك إدخال كلمة المرور.
## المكونات
### الماكرو `test_fuzz`
وضعُ الماكرو `test_fuzz` قبل دالة يشير إلى أن هذه الدالة هي هدف fuzzing.
الآثار الرئيسية للماكرو `test_fuzz` هي:
- إضافة أدوات قياس إلى الهدف لتسلسل وسائطه وكتابتها إلى ملف مجموعة (corpus file) في كل مرة يُستدعى فيها الهدف. الأدوات محمية بواسطة `#[cfg(test)]` بحيث يتم إنشاء ملفات المجموعة فقط عند تشغيل الاختبارات (ومع ذلك، انظر [`enable_in_production`] أدناه).
- إضافة اختبار لقراءة الوسائط وفك تسلسلها من الإدخال القياسي وتطبيق الهدف عليها. يفحص الاختبار متغير بيئة معيّنًا بواسطة [`cargo test-fuzz`]، بحيث لا يعلق الاختبار في محاولة القراءة من الإدخال القياسي أثناء استدعاء عادي لـ `cargo test`. الاختبار موضوع داخل وحدة (module) لتقليل احتمالية تعارض الأسماء. حاليًا، اسم الوحدة هو `target_fuzz`، حيث `target` هو اسم الهدف (ومع ذلك، انظر [`rename`] أدناه).
#### الوسائط
##### `bounds = "where_predicates"`
فرض `where_predicates` (مثل قيود الخصائص) على البنية المستخدمة لتسلسل/فك تسلسل الوسائط. قد يكون هذا ضروريًا، مثلًا، إذا كان نوع وسيط الهدف نوعًا مرتبطًا (associated type). لمثال، انظر [associated_type.rs] في هذا المستودع.
##### `generic_args = "parameters"`
استخدم `parameters` كمعاملات نوع الهدف عند fuzzing. مثال:```rust
#[test_fuzz(generic_args = "String")]
fn foo<T: Clone + Debug + Serialize>(x: &T) {
...
}
ملاحظة: يجب أن تكون وسائط الهدف قابلة للتسلسل (serializable) لكل instantiation من معاملات النوع الخاصة به. لكن وسائط الهدف مطلوبة لتكون قابلة لإلغاء التسلسل (deserializable) فقط عندما يتم إنشاء الهدف باستخدام parameters.
impl_generic_args = "parameters"استخدم parameters كمعاملات النوع Self الخاصة بالهدف عند الفازينج (fuzzing). مثال:```rust
#[test_fuzz_impl]
impl<T: Clone + Debug + Serialize> for Foo {
#[test_fuzz(impl_generic_args = "String")]
fn bar(&self, x: &T) {
...
}
}
ملاحظة: يجب أن تكون وسيطات الهدف قابلة للتسلسل (serializable) لكل **instantiation** (نسخة) من معاملات النوع `Self` الخاصة به. لكن وسيطات الهدف مطلوبة لتكون قابلة لإلغاء التسلسل (deserializable) فقط عندما يتم إنشاء نسخة `Self` الخاصة بالهدف باستخدام `parameters`.
##### `convert = "X, Y"`
عند تسلسل وسيطات الهدف، حوّل القيم من النوع `X` إلى النوع `Y` باستخدام تنفيذ `Y` لـ `From<X>`، أو من النوع `&X` إلى النوع `Y` باستخدام تنفيذ `Y` للخاصية غير القياسية `test_fuzz::FromRef<X>`. عند إلغاء التسلسل، حوّل تلك القيم مرة أخرى إلى النوع `X` باستخدام تنفيذ `Y` للخاصية غير القياسية `test_fuzz::Into<X>`.
أي أنّ استخدام `convert = "X, Y"` يجب أن يكون مصحوبًا بتنفيذات معيّنة. إذا كان `X` يطبّق [`Clone`]، فإن `Y` يمكن أن يطبّق ما يلي:```rust
impl From<X> for Y {
fn from(x: X) -> Self {
...
}
}
إذا كان X لا يطبّق [Clone]، فيجب أن يطبّق Y ما يلي:```rust
impl test_fuzz::FromRef for Y {
fn from_ref(x: &X) -> Self {
...
}
}
بالإضافة إلى ذلك، يجب أن يطبّق `Y` ما يلي (بغض النظر عما إذا كان `X` يطبّق [`Clone`]):```rust
impl test_fuzz::Into<X> for Y {
fn into(self) -> X {
...
}
}
تعريف test_fuzz::Into مطابق تمامًا لتعريف [std::convert::Into]. والسبب في استخدام trait غير قياسي هو تجنّب التعارضات التي قد تنشأ عن التطبيقات الشاملة (blanket implementations) للـ traits القياسية.
enable_in_productionقم بإنشاء ملفات corpus عند عدم تشغيل الاختبارات، بشرط أن يكون متغير البيئة [TEST_FUZZ_WRITE] مضبوطًا. السلوك الافتراضي هو إنشاء ملفات corpus فقط عند تشغيل الاختبارات، بغض النظر عن ضبط [TEST_FUZZ_WRITE] أو عدمه. عند تشغيل هدف من خارج مجلد الحزمة، اضبط [TEST_FUZZ_MANIFEST_PATH] على مسار ملف Cargo.toml الخاص بالحزمة.
تحذير: ضبط enable_in_production قد يُدخل ناقل رفض خدمة (denial-of-service). على سبيل المثال، ضبط هذا الخيار لدالة تُستدعى مرات عديدة بوسائط مختلفة قد يملأ القرص. ويأتي التحقق من [TEST_FUZZ_WRITE] ليوفر بعض الحماية ضد هذا الاحتمال. ومع ذلك، فكّر في هذا الخيار بعناية قبل استخدامه.
execute_with = "function"بدلاً من استدعاء الهدف مباشرة:
FnOnce() -> R، حيث R هو نوع القيمة المعادة للهدف، بحيث يكون استدعاء الإغلاق هو استدعاء الهدف؛function مع تمرير الإغلاق إليه.استدعاء الهدف بهذه الطريقة يتيح لـ function إعداد بيئة الاستدعاء. وقد يكون هذا مفيدًا، مثلًا، للـ fuzzing على [Substrate externalities].
no_auto_generateلا تحاول إنشاء [auto-generate corpus files] للهدف.
only_generic_argsسجّل الوسائط العامة (generic args) للهدف عند تشغيل الاختبارات، لكن لا تُنشئ ملفات corpus ولا تنفّذ أداة fuzzing harness. قد يكون هذا مفيدًا عندما يكون الهدف دالة عامة، ولكن من غير الواضح ما هي معاملات الأنواع (type parameters) التي ينبغي استخدامها في الـ fuzzing.
سير العمل المقصود هو: فعّل only_generic_args، ثم شغّل cargo test متبوعًا بـ cargo test-fuzz --display generic-args. قد تكون إحدى الوسائط العامة الناتجة صالحة للاستخدام كـ parameters لـ generic_args. وبالمثل، قد تكون الوسائط العامة الناتجة عن cargo test-fuzz --display impl-generic-args صالحة للاستخدام كـ parameters لـ impl_generic_args.
لاحظ، مع ذلك، أن مجرد استدعاء الهدف بمعاملات معينة أثناء الاختبارات لا يعني أن وسائط الهدف قابلة للـ serialization/deserialization عند استخدام تلك المعاملات. نتائج --display generic-args/--display impl-generic-args هي مجرد مؤشرات إرشادية.
rename = "name"تعامل مع الهدف كما لو كان اسمه name عند إضافة وحدة (module) إلى النطاق المحيط. توسيع الماكرو test_fuzz يضيف تعريف وحدة إلى النطاق المحيط. افتراضيًا، تُسمى الوحدة على النحو التالي:
impl، تكون الوحدة باسم target_fuzz__، حيث target هو اسم الهدف.impl، تكون الوحدة باسم path_target_fuzz__، حيث path هو الجزء الأخير من مسار نوع Self في impl.لكن استخدام هذا الخيار يجعل الوحدة تُسمى بدلًا من ذلك name_fuzz__. مثال:```rust
#[test_fuzz(rename = "bar")]
fn foo() {}
// Without the use of rename, a name collision and compile error would result.
mod foo_fuzz__ {}
#### سمات حقول Serde على وسائط الدوال
تسمح وحدة الماكرو `test_fuzz` بتطبيق [سمات حقول Serde] على وسائط الدوال. وهذا يوفر أداة إضافية للتعامل مع الأنواع الصعبة.