
توليد تلقائي لمشغلات الفاز (fuzz drivers) استنادًا إلى اختبارات الوحدة (UT)
UTopia هي أداة لتوليد محركات التضمين تلقائيًا من اختبارات الوحدة.
UTopia ستتيح للمطورين إجراء اختبار التضمين دون معرفة خاصة بكتابة أدوات التضمين. حتى المطورين الملمّين بالتضمين يمكنهم توفير وقت كبير عبر توليد محركات التضمين تلقائيًا.
UTopia يدعم مكتبات C/C++ التي لديها اختبارات وحدة باستخدام GoogleTest أو Boost.Test أو Tizen TCT.
للاطلاع على الأخطاء التي عثرت عليها UTopia، قم بزيارة صفحة Trophy. يمكنك أيضًا مشاهدة بعض أدوات التضمين المبنية على UTopia هناك.
للسهولة في الإعداد، نوفر صورة دوكر لتشغيل UTopia. يمكنك بناء UTopia وتوليد/تشغيل أدوات التضمين داخل حاوية دوكر.
ابني صورة دوكر باستخدام الأمر التالي.
docker buildx build -f docker/Dockerfile -t utopia . #llvm-10
docker buildx build --build-arg LLVM_VERSION=12 -f docker/Dockerfile -t utopia . #llvm-12
UTopia مصمم للعمل بشكل أفضل مع إصدار LLVM 10. كما تم التأكد من اجتيازه لاختبارات الوحدة على إصدار LLVM 12. لذلك ننصحك باستخدام LLVM 10، ولكن يمكنك تجربة إصدار LLVM 12 إذا أردت.
UTopia يعتمد على LLVM وProtobuf وGoogleTest. يمكنك تثبيت التبعيات يدويًا، لكننا ننصح باستخدام صورة الدوكر المعطاة.
بعد استنساخ المستودع، قم بتهيئة وتحديث جميع الوحدات الفرعية:
git submodule update --init --recursive
لبناء UTopia، اتبع عملية cmake التالية.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
بالنسبة لبعض المشاريع المختارة، يمكنك استخدام السكربت المساعد لتشغيل أداتنا دون جهد إضافي. يرجى الرجوع إلى helper/README.md. أما بالنسبة للمشاريع الأخرى، فيرجى الاطلاع على الدليل التالي.
يقوم Target Analyzer بتحليل كود المكتبة الهدف وينشئ نتيجة كملف json. الخيارات الإلزامية لسطر الأوامر هي التالية.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
قاعدة بيانات البناء هي ملف json يحتوي على مسارات ملفات AST وIR لكود المكتبة الهدف. تنسيقها يبدو كالتالي.
{
"bc": "/root/fuzz-test-generation/exp/sample/output/bc/libcommon.a.bc",
"ast": [
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast1.o.ast",
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast2.o.ast"
],
"project_dir": "/root/fuzz-test-generation/exp/sample"
}
يجب تحديد مسار ملف LLVM bitcode لمكتبة معينة باستخدام الكلمة الأساسية "bc".
نقبل حاليًا ملف bitcode واحدًا فقط، لذا يمكنك استخدام llvm-link لربط عدة ملفات bitcode معًا في ملف bitcode واحد.
يجب تحديد مسارات ملفات AST لمكتبة معينة باستخدام الكلمة الأساسية "ast". لاحظ أن ملف bitcode المحدد وملفات AST يتم توليدهما من نفس الأكواد المصدرية لمكتبة معينة.
يستقبل Target Analyzer تقارير المحلل الهدف للمكتبات الأخرى للحصول على نتيجة دقيقة. يجب أن يكون هذا المسار مسار دليل تُخزَّن فيه التقارير الأخرى، مما يعني أنه يُسمح بأكثر من تقرير لمكتبات متعددة.
أسماء دوال API المراد تحليلها. يجب أن تكون ملف json بتنسيق كما يلي.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
يمكنك الحصول على قائمة API من مكتبة معينة باستخدام الأمر التالي.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
خاصية الاتجاه هي معلمة أساسية يستخدمها Target Analyzer. وهي تشير إلى ما إذا كان المعامل يُستخدم للقراءة (Dir_In) أو الكتابة (Dir_Out) أو للقراءة والكتابة معًا (Dir_In | Dir_Out) داخل دالة. يوضح Target Analyzer هذه الخاصية عبر نوع تعداد يتضمن العناصر التالية:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
على سبيل المثال، في مقتطف ملف JSON المعروض:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
يساعد هذا الترميز في فهم كيفية استخدام كل معامل داخل دالة، سواء كان للإدخال أو الإخراج أو كليهما، مما يوفر رؤى واضحة حول تدفق البيانات والعمليات التي تنفذها الدالة.
يقوم UT Analyzer بتحليل كود اختبار الوحدة لمكتبة هدف وينشئ نتيجة كملف json. الخيارات الإلزامية لسطر الأوامر هي التالية.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
معظم الخيارات مماثلة لخيارات target_analyzer. لاحظ أن entry_path يجب أن يحدد ملفات AST/IR لملف تنفيذي لاختبار الوحدة، وليس للمكتبة.
الإطار المستخدم في المشروع الهدف، يمكن أن يكون tct أو gtest أو boost.
يقوم fuzz_generator بتوليد محركات التضمين باستخدام ملفات التقارير الخاصة بـ target_anlayzer وut_analyzer. الخيارات الإلزامية لسطر الأوامر هي التالية.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path هو مسار الدليل الذي تُخزَّن فيه أكواد مصدر اختبار الوحدة. يقوم fuzz_generator بنسخ هذا الدليل ويولّد محرك تضمين عبر تعديل هذه الملفات المنسوخة.
الخيارات الأخرى مماثلة لخيارات target_analyzer.
يصف هذا القسم وظيفة وتفاصيل تنفيذ محرك التضمين المولّد، وهو أمر بالغ الأهمية لفهم كيفية دمج اختبار التضمين مع الكود المصدري الهدف.
fuzz_entry.cc (ملف مولّد تلقائيًا)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
هذه الدالة تعمل كنقطة دخول لمحرك التضمين المولّد. فهي تأخذ القيم المولّدة بواسطة أداة التضمين وتعيّنها إلى متغيرات تُستخدم لاحقًا لاستدعاء دوال المكتبة. في النهاية، تستدعي الدالة enterAutofuzz(); للمتابعة في عملية اختبار التضمين.
الكود المصدري الذي يعرّف حالة الاختبار الهدف
#ifdef __cplusplus
extern "C" {
#endif
void enterAutofuzz() {
class AutofuzzTest : public ::Parser_TestArray_Test {
public:
void runTest() {
try {
SetUpTestCase();
} catch (std::exception &E) {}
try {
SetUp();
} catch (std::exception &E) {}
try {
TestBody();
} catch (std::exception &E) {}
try {
TearDown();
} catch (std::exception &E) {}
try {
TearDownTestCase();
} catch (std::exception &E) {}
}
};
AutofuzzTest Fuzzer;
Fuzzer.runTest();
}
#ifdef __cplusplus
}
#endif
في الجزء الأخير من الكود المصدري الذي يعرّف حالة الاختبار الهدف، تحقن UTopia الدالة enterAutofuzz. داخل هذه الدالة، يتم تعريف الفئة AutofuzzTest، الموروثة من ::Parser_TestArray_Test. هذه الفئة الأم معرفة بواسطة إطار GoogleTest وتكون خاصة بحالة الاختبار المعالجة، كما هو موضح أدناه:
TEST(Parser, TestArray)
{
...
}
تنفذ طريقة runTest() حالة الاختبار بشكل مستقل عن GoogleTest، حيث تستدعي خمس دوال يستدعيها GoogleTest عادةً لكل حالة اختبار. يتيح هذا الأسلوب التنفيذ المباشر للاختبار دون الاعتماد على إطار GoogleTest.
يتم توليد محركات التضمين في ${output_dir} الذي يُمرَّر إلى fuzz_generator كخيار من خيارات سطر الأوامر.
يمكنك بناؤها باستخدام نفس أمر المترجم المستخدم لملف اختبار الوحدة التنفيذي.
لاحظ أنه يجب تضمين ملفي fuzz_entry.cc وFuzzArgsProto.pb.cc اللذين يولّدهما fuzz_generator.
يمكنك العثور على هذه الملفات في ${output_dir}.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
يمكنك العثور على جميع المخرجات الناتجة عن خط أنابيب 'UTopia' بأكمله في الدليلين التاليين:
exp/{library name}/output,result/test/{library name}