
يقوم بتلقيح (Fuzzing) تطبيقات وحدات المعالجة المركزية (CPU) عبر توليد مدخلات اختبار من وكلاء برمجيين، ثم ينفّذها على عتاد حقيقي لكشف عيوب المعمارية الدقيقة والأخطاء الموثّقة (Errata).
SiliFuzz هو نظام يكتشف عيوب وحدة المعالجة المركزية (CPU) عبر اختبار الفازينج (fuzzing) للبروكسيات البرمجية، مثل محاكيات وحدة المعالجة المركزية أو مفككات التجميع (disassemblers)، ثم تنفيذ مدخلات الاختبار المتراكمة (المعروفة باسم corpus) على وحدات المعالجة المركزية الفعلية على نطاق واسع. SiliFuzz هو عمل قيد التطوير، يُرجى الرجوع إلى الورقة البحثية للحصول على التفاصيل.
الفازينج (Fuzzing) هو أسلوب لاختبار هدف (تطبيق أو واجهة برمجية API) بعدد كبير من مدخلات الاختبار المُولَّدة أثناء التنفيذ. الهدف هو جعل هذه المدخلات مثيرة للاهتمام ومتنوعة قدر الإمكان من أجل تحفيز الحالات الطرفية. بعبارة أخرى، يهدف الفازينج إلى تعظيم التغطية البرمجية المجمّعة. التغطية البرمجية قد يكون لها معانٍ مختلفة، مثل أي الكتل الأساسية يتم تنفيذها أو أي المسارات يتم اتخاذها في البرنامج.
لأغراض SiliFuzz، البروكسي هو أي نظام برمجي أو عتادي يتصرف بشكل مشابه لبعض جوانب وحدة المعالجة المركزية المستهدفة. على سبيل المثال، محاكي وحدة معالجة مركزية أو مفكك تجميع. نحتاج إلى بروكسي عندما لا نستطيع جمع معلومات التغطية مباشرة من الهدف.
من خلال تطبيق تقنيات الفازينج على بروكسي، يمكننا توليد سلسلة من مدخلات الاختبار (corpus) التي تُنتج سلوكًا مثيرًا للاهتمام في البروكسي. افتراضنا الأساسي هو أن هذا يُترجم إلى سلوك مثير للاهتمام بشكل مماثل في الهدف. اقرأ المزيد في .
مجموعة المدخلات المستخدمة لاختبار الهدف تُعرف باسم corpus.
يحتوي corpus كبير بشكل معقول على ملايين المدخلات وعادةً ما يتم تقسيمه إلى أجزاء غير متداخلة متعددة تسمى shards.
تصف لقطة SiliFuzz تسلسلًا قصيرًا من تعليمات وحدة المعالجة المركزية بالإضافة إلى حالة أولية لسجلات وحدة المعالجة المركزية والذاكرة لتنفيذ هذا التسلسل بشكل حتمي. تحتوي اللقطة النموذجية على أقل من 100 بايت من التعليمات البرمجية وتعمل في غضون ميكروثوانٍ، ولكنها يمكن أن تكون كبيرة بشكل تعسفي. يتم تخزين اللقطات كبروتوكولات مخازن silifuzz.proto.Snapshot.
يتم إنشاء اللقطات عادةً من المدخلات المُولَّدة بواسطة محرك فازينج. لأغراض اختبار وحدة المعالجة المركزية، يتم تصفية هذه المدخلات لإزالة جميع اللقطات غير الحتمية. اقرأ المزيد في المستندات.
تصف الحالة النهائية محتويات السجلات والذاكرة المتوقع وجودها في نهاية تنفيذ اللقطة. إذا تم تنفيذ اللقطة بشكل مختلف على ميكرومعماريات مختلفة لوحدة المعالجة المركزية، فسيكون لها حالات نهائية متوقعة متعددة.
Snap هو تمثيل في الذاكرة للقطة (Snapshot) يمكن تحميله وتنفيذه بسهولة بواسطة Runner. عادةً ما يتم تحميل Snaps من القرص بواسطة قارئ runner. تنسيق Snap على القرص هو في الأساس نفس تنسيق الذاكرة باستثناء أن المؤشرات الأصلية تُستبدل بالإزاحات. انظر هذا الملف الرأسي للحصول على التفاصيل. غالبًا ما يُشار إلى هذا التنسيق باسم relocatable. يحتوي كل Snap على حالة نهائية متوقعة واحدة بالضبط، أي أن Snaps خاصة بميكرومعمارية محددة. اقرأ المزيد في المستندات.
Runner هو ملف ثنائي لاختبار نواة معالج واحدة (CPU core). يستهلك runner جزءًا من corpus، وينفذ بشكل متكرر Snaps عشوائية فيه ويتحقق من الوصول إلى الحالة النهائية المتوقعة. Runner هو عملية أحادية الخيط.
المنسق هو عملية تقود عدة runners. في الإعداد النموذجي، سيقوم المنسق باستمرار بتنفيذ runner واحد لكل نواة معالج منطقية، وتجميع أي إخفاقات تُنتجها عمليات runner الفردية والإبلاغ عنها.
انظر هذا الملف للحصول على قائمة الميكرومعماريات المدعومة.
يعمل SiliFuzz على أنظمة Linux x86_64 و aarch64. تم اختباره مع إصدارات نواة Linux 5.x و 6.x. لا يوجد ضمان بأنه متوافق مع إصدارات النواة الأقدم. يجب إيقاف تشغيل ABI القديم vsyscall لتجنب النتائج الإيجابية الخاطئة.
قائمة غير شاملة للأخطاء والعيوب التي وجدها SiliFuzz.
الخطأ البرمجي المنطقي هو سلوك غير صالح لوحدة المعالجة المركزية متأصل في ميكرومعمارية أو خطوة إنتاج (stepping) معينة. حدد SiliFuzz الأخطاء البرمجية التالية:
العيب (الكهربائي) هو سلوك غير صالح لوحدة المعالجة المركزية يحدث فقط على شريحة واحدة أو عدة شرائح. وجد SiliFuzz العيوب التالية التي وصفناها في الورقة البحثية:
git clone https://github.com/google/silifuzz.git && cd silifuzz
SILIFUZZ_SRC_DIR=`pwd`
./install_build_dependencies.sh # يعمل حاليًا لأحدث إصدار من Ubuntu فقط.
bazel build -c opt @silifuzz//tools:{snap_corpus_tool,fuzz_filter_tool,snap_tool,silifuzz_platform_id,simple_fix_tool_main} \
@silifuzz//runner:reading_runner_main_nolibc \
@silifuzz//orchestrator:silifuzz_orchestrator_main
SILIFUZZ_BIN_DIR=`pwd`/bazel-bin
cd "${SILIFUZZ_BIN_DIR}"
ملاحظة: يمكنك استخدام حاوية Docker لتجنب تلويث النظام المضيف: docker run -it --tty --security-opt seccomp=unconfined --mount type=bind,source=${SILIFUZZ_SRC_DIR},target=/app ubuntu:noble /bin/bash -c "cd /app && ./install_build_dependencies.sh && bazel build ... && bazel test ..."
لـ Bazel، استخدم الأوامر التالية.
cd "${SILIFUZZ_SRC_DIR}"
COV_FLAGS_FILE="$(bazel info output_base)/external/fuzztest+/centipede/clang-flags.txt"
bazel build -c opt --copt=-UNDEBUG --dynamic_mode=off \
--per_file_copt=unicorn/.*@$(xargs < "${COV_FLAGS_FILE}" |sed -e 's/,/\\,/g' -e 's/ /,/g') @//proxies:unicorn_x86_64
bazel build -c opt @fuzztest//centipede:centipede
mkdir -p /tmp/wd
# قم بفازينج بروكسي Unicorn تحت Centipede 1000 مرة مع توازي 30.
"${SILIFUZZ_BIN_DIR}/external/fuzztest+/centipede/centipede" \
--binary="${SILIFUZZ_BIN_DIR}/proxies/unicorn_x86_64" \
--workdir=/tmp/wd \
-j=30 --num_runs=1000
ملاحظة: يُرجى الرجوع إلى وثائق Centipede حول كيفية تشغيل محرك الفازينج بكفاءة.
أداة المساعدة هذه للتحقق من أن الجهاز الذي تعمل عليه مدعوم.
$ ${SILIFUZZ_BIN_DIR}/tools/silifuzz_platform_id --short
intel-skylake
ملاحظة: لا يأخذ منطق اكتشاف وحدة المعالجة المركزية في SiliFuzz في الاعتبار بعض المتغيرات المكتبية (desktop) من وحدات المعالجة المركزية المدعومة بخلاف ذلك. ستبلغ الأداة بـ "Unsupported platform" في مثل هذه الحالات.
تقوم أداة fuzz_filter_tool بتحويل التعليمات الأولية إلى لقطات (Snapshots) متوافقة مع Snap. تُرجع 0 عندما يكون التحويل ممكنًا و1 بخلاف ذلك. هذه الواجهة متوافقة مع input_filter في Centipede.
fuzz_filter_tool raw_input_sequence
يحتوي ملف raw_input_sequence على تعليمات أولية سيتم تحويلها إلى تنسيق Snapshot باستخدام InstructionsToSnapshot
مثال على الاستخدام:
# INC EAX
echo -en '\xFF\xC0' > /tmp/inc_eax && ./tools/fuzz_filter_tool /tmp/inc_eax
echo $?
0
يقوم snap_tool بفحص ومعالجة ملفات Snapshot الثنائية (protos). يمكنه اختياريًا تحميل تعليمات أولية وتحويلها إلى Snapshot.
echo -en '\xFF\xC0' > /tmp/inc_eax
./tools/snap_tool --raw print /tmp/inc_eax
Metadata:
Id: inc_eax
Architecture: x86_64 Linux
Completeness: complete
Registers:
gregs (non-0 only)
rax = 0x20000000
....
تأخذ أداة الإصلاح البسيط نتائج الفازينج من Centipede، وتحول التعليمات الأولية إلى لقطات بدون حالات نهائية، وتضيف حالات نهائية إلى اللقطات، وأخيرًا تحزم اللقطات في corpus snap قابل للنقل ومجزأ إلى shards.
حاليًا تعمل هذه الأداة كعملية غير قابلة لإعادة التشغيل على مضيف واحد ويتم وضع كل شيء في الذاكرة، لذا فإن حجم corpus الذي يمكن التعامل معه محدود بالذاكرة المتاحة للمضيف. نظرًا لأن الحالات النهائية تُنشأ على المضيف، فإن corpus الناتج يكون لهندسة معمارية واحدة.
تجريبي: اختبارات التجزئة (hash tests) هي اختبارات منظمة عشوائية تضخ الإنتروبيا في تعليمات مولدة عشوائيًا وتلتقط المخرجات الناتجة بأكبر قدر ممكن من الكفاءة. يعتمد هذا النهج على ملاحظة أن نسبة غير تافهة من العيوب يمكن اكتشافها عن طريق استدعاء التعليمة الصحيحة بالمدخل الصحيح. تستهدف اختبارات التجزئة بقوة هذه الفئة البسيطة من العيوب لتوفير نقطة مقارنة تجريبية لـ SiliFuzz. حاليًا يتم دعم x86_64 فقط.
إذا كنت تريد، على سبيل المثال، توليد 30 ألف لقطة اختبار تجزئة تحتوي على تعليمات تدعمها معالجات Skylake في دليل /tmp/hashtest، يمكنك تشغيل هذا الأمر.
mkdir -p /tmp/hashtest && bazel run -c opt @silifuzz//fuzzer/hashtest:hashtest_generator -- --platform=intel-skylake -n 30000 --outdir /tmp/hashtest
تم تنظيم بقية المستند بطريقة "كيف أفعل" حيث يصف كل سؤال حالة استخدام نموذجية. يمثل تقدم الأسئلة التعقيد المتزايد للمهمة التي يحاول المرء إنجازها. تتطلب كل خطوة عادةً إما فهم أو القطع الأثرية (أحيانًا كلاهما) التي تم الحصول عليها في الخطوة السابقة.
ملاحظة: يفترض المستند مضيفًا/هدفًا بوحدة معالجة مركزية x86_64. قد يختلف المخرج الدقيق اعتمادًا على بائع وحدة المعالجة المركزية/الخطوة/إلخ والبيئة (مثل Docker/KVM).
تحذير: العديد من التعليمات أدناه تنفذ كودًا ثنائيًا عشوائيًا بصلاحيات المستخدم الجاري. تبذل الأداة قصارى جهدها لعزل الكود باستخدام seccomp(2). استخدم على مسؤوليتك الخاصة.
# INC EAX
$ echo -en '\xFF\xC0' > /tmp/inc_eax
$ ./tools/snap_tool --raw --out=/tmp/inc_eax.pb make /tmp/inc_eax
# CPUID
$ echo -en '\x0F\xA2' > /tmp/cpuid
$ ./tools/snap_tool --raw --out=/tmp/cpuid.pb make /tmp/cpuid
<error log omitted>
Could not load snapshot: INTERNAL: Tracing failed: Banned instruction: CPUID
ملاحظة: لتجنب النتائج غير الحتمية، تستبعد أجزاء مختلفة من SiliFuzz فئات معينة من التعليمات، مثل CPUID أعلاه.
$ ./tools/snap_tool print /tmp/inc_eax.pb
Metadata:
Id: inc_eax
Architecture: x86_64 Linux
Completeness: complete
Registers:
gregs (non-0 only):
rax = 0x20000000
rip = 0xeb85c12b000
<omitted>
End states (1):
Endpoint:
Instruction address: 0xeb85c12b002
Platforms:
intel-skylake
Registers (diff vs snapshot's initial values):
gregs (modified only):
rax = 0x20000001
rip = 0xeb85c12b002
<omitted>
لاحظ كيف أن قيمة الحالة النهائية لسجل RAX هي 0x20000001
(0x20000000+1 وهو بالضبط ما تفعله INC EAX). لاحظ أيضًا أن قيمة
RIP هي الأصلية +2 وهو حجم تعليمة INC EAX.
$ ./tools/snap_tool play /tmp/inc_eax.pb
Snapshot played successfully.
ملاحظة: ستحتاج إلى تحديد منصة مستهدفة لتوليد corpus. في هذا المثال، يستهدف corpus الناتج المنصة التي تم توليده عليها.
$ cd "${SILIFUZZ_BIN_DIR}"
$ PLATFORM_ID=$(./tools/silifuzz_platform_id --short)
$ ./tools/snap_tool generate_corpus /tmp/inc_eax.pb \
--target_platform="${PLATFORM_ID}" > /tmp/inc_eax.corpus
# سيتم تشغيل نفس لقطة "INC EAX" مليون مرة
$ ./runner/reading_runner_main_nolibc /tmp/inc_eax.corpus
يمكنك فحص العملية باستخدام gdb:
$ gdb ./runner/reading_runner_main_nolibc
(gdb) b RestoreUContextNoSyscalls
(gdb) run /tmp/inc_eax.corpus
Starting program: .../reading_runner_main_nolibc /tmp/inc_eax.corpus
Breakpoint 1, 0x0000456700010598 in RestoreUContextNoSyscalls ()
(gdb) x/i 0xeb85c12b000 # same as the rip value produced by snap_tool print above
0xeb85c12b000: inc %eax
ملاحظة: تعتمد هذه الخطوة على خطوة "فازينج هدف Unicorn" الموصوفة سابقًا.
قم بتحويل نتائج الفازينج corpus.* إلى corpus قابل للتشغيل مكون من 10 أجزاء (shards) للهندسة المعمارية الحالية.
cd "${SILIFUZZ_BIN_DIR}"
"${SILIFUZZ_BIN_DIR}/tools/simple_fix_tool_main" \
--num_output_shards=10 \
--output_path_prefix=/tmp/wd/runnable-corpus \
--runner="${SILIFUZZ_BIN_DIR}/runner/reading_runner_main_nolibc" \
/tmp/wd/corpus.*
ستكون أجزاء corpus في /tmp/wd/runnable-corpus.*
$ ./tools/snap_corpus_tool list_snaps /tmp/inc_eax.corpus
...
I0000 00:00:1661887744.019079 4074672 snap_corpus_tool.cc:155] inc_eax
ملاحظة: هذه أداة أساسية جدًا في الوقت الحالي تقدم بضعة أوامر فقط.
# سيتم تشغيل نفس لقطة "INC EAX" على CPU#1 10 آلاف مرة.
$ ./runner/reading_runner_main_nolibc \
--cpu=1 --num_iterations=10000 /tmp/inc_eax.corpus
سيتنقل المنسق عبر جميع الأجزاء المدرجة في الملف الذي تم تمريره في الوسيط --shard_list_file.
$ ls -1 /tmp/wd/runnable-corpus.* > /tmp/wd/shard_list
$ echo 'version: "local_corpus"' > /tmp/wd/corpus_metadata
# سيتم تشغيل corpus بشكل متكرر على جميع نوى وحدة المعالجة المركزية المتاحة لمدة 30 ثانية باستخدام
# /tmp/wd/runnable-corpus.* المختار عشوائيًا.
$ ${SILIFUZZ_BIN_DIR}/orchestrator/silifuzz_orchestrator_main --duration=30s \
--runner=${SILIFUZZ_BIN_DIR}/runner/reading_runner_main_nolibc \
--shard_list_file=/tmp/wd/shard_list \
--corpus_metadata_file=/tmp/wd/corpus_metadata
ملاحظة: يمكن للمنسق أيضًا تحميل أجزاء corpus مضغوطة بـ XZ من ملفات تنتهي بـ .xz.