
深刻度: CVSS 4.3(Medium)— 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ページのマッピングを防ぐため、その環境では障害がコード実行に直接悪用可能になることはありません。ThorVGが使用されるMMUなしのターゲット(Tizen、LVGLベースのファームウェア)では、悪用可能性の状況は異なり、より詳細な調査が必要です。
ThorVGはC++17で書かれたクロスプラットフォームのベクターグラフィックスエンジンです。Samsung Tizen OSのデフォルトのSVG/Lottieレンダラーであり、LVGL(組み込み/IoT UIで広く使用されています)にバンドルされ、複数のプラットフォームでスタンドアロンライブラリとして配布されています。
このライブラリは信頼できないSVG/JSON入力を処理し、特権分離がないコンテキストで公開されることがよくあります。グラフィックスライブラリのパーサーコードは、歴史的にメモリ安全性バグの確実な発生源となってきました — 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 — 有効なタグ名に向けた変異を支援する、SVGキーワードを含むAFL++トークン辞書。
-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の修正では、属性/スタイル処理が試行される前に、_createElementがnullptrを返した場合に要素ディスパッチループで早期リターンを追加しています。
/proc/sys/vm/mmap_min_addrは、最新のディストリビューションでは通常65536に設定されています。NULLページはマッピングされていないため、CPUはSIGSEGVを発生させ、カーネルがそれをプロセスへのシグナルに変換します — 結果は**クラッシュ(DoS)**です。制御された書き込みもPC制御もなく、コード実行に直接悪用可能ではありません。
ThorVGはTizen(SamsungのIoT/ウェアラブルOS)でファーストクラスにサポートされており、MMUを持たないマイクロコントローラやシステム上で動作するLVGLにも統合されています。
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(이예빈)により、構造を考慮したSVGシードコーパスを使用したAFL++カバレッジガイド付きファジングで発見されました。
| 日付 | イベント |
|---|
| 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を採番 |