yeahhbean/CVE-2026-45729

GitHub: yeahhbean/CVE-2026-45729

该仓库记录了通过 AFL++ 在 ThorVG SVG 解析器中发现并分析一个由畸形 SVG 触发的 NULL 指针解引用漏洞的完整安全研究报告。

Stars: 0 | Forks: 0

# CVE-2026-45729 — ThorVG 通过畸形 SVG 导致的 NULL 指针解引用 **严重程度:** CVSS 4.3 (Medium) — CWE-476 **公告:** [GHSA-f863-8ghq-7h64](https://github.com/advisories/GHSA-f863-8ghq-7h64) **修复:** ThorVG v1.0.5 **状态:** 已修复 / 已披露 ## 摘要 ThorVG 的 SVG 解析器在根 `` 节点内遇到被截断的子元素标签时,会解引用一个从未初始化的指针。 该漏洞可以通过正常渲染路径(`tvg::Picture::load` → 解析 → 渲染)触发,并可通过 **6 字节**的输入触发。 在标准 Linux 上,最坏的影响是进程崩溃(DoS)—— `mmap_min_addr` 阻止了映射 NULL 页,因此在该环境下,此故障无法直接用于代码执行。 在没有 MMU 且同样使用 ThorVG 的目标平台上(如 Tizen、基于 LVGL 的固件),其可利用性有所不同,值得进一步研究。 ## 目标选择理由 ThorVG 是一个用 C++17 编写的跨平台矢量图形引擎。它是 **Samsung Tizen OS** 中默认的 SVG/Lottie 渲染器,捆绑在 **LVGL**(广泛用于嵌入式/IoT UI)中,并作为独立库在多个平台上分发。 该库处理不受信任的 SVG/JSON 输入,并且通常在缺乏权限隔离的上下文中暴露。历史上,图形库中的解析器代码一直是内存安全漏洞的可靠来源——ThorVG 处于活跃开发中,并且在进行此项研究时,公共 bug 跟踪系统中没有任何关于其 fuzzing 覆盖率的记录。 ## Fuzzing 设置 ### 测试套件 该测试套件封装了 ThorVG 的内存加载 API,以便 AFL++ 可以直接驱动解析器,而无需进行磁盘 I/O。 ``` // fuzz_thorvg.cpp #include #include #include 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(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(``) - 带有基本形状的 SVG(``, ``, ``) - 带有嵌套 `` 组的 SVG - 带有 `` xlink 引用的 SVG - 一个小型 Lottie/JSON 动画(ThorVG 也处理此格式) 结构感知种子将我环境中首次发现有趣执行路径的时间从数小时缩短到了 20 分钟以内。 ### AFL++ 参数 ``` AFL_AUTORESUME=1 afl-fuzz \ -i corpus/ \ -o findings/ \ -x svg.dict \ -m none \ -- ./fuzz_thorvg @@ ``` `-x svg.dict` — 包含 SVG 关键字的 AFL++ token 字典,用于帮助变异出有效的标签名。 `-m none` — ASAN 影子内存需要禁用 AFL++ 的内存限制。 ## 崩溃发现 AFL++ 在单核 fuzzing 约 **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 个字节: ``` < ``` `` 开启了根节点。`<` 开启了一个子元素标签。解析器读取标签名,得到一个空字符串(输入在 `<` 之后立即结束),跳过元素创建,并在节点指针仍为 NULL 的情况下落入 `_parseStyle`。 确认这 6 字节的触发器可复现: ``` printf '<' | ./fuzz_thorvg /dev/stdin # 或者 echo -n '<' > 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` 时,于元素分发循环中添加了一个早期返回点,位置在任何属性/样式处理尝试之前。 ## 可利用性分析 ### 标准 Linux (CVSS 4.3 / Medium) 在现代发行版中,`/proc/sys/vm/mmap_min_addr` 通常设置为 `65536`。 NULL 页未被映射,因此 CPU 引发 SIGSEGV,内核将其转换为发送给进程的信号——结果是**崩溃**。没有受控写入,没有 PC 控制,无法直接用于代码执行。 ### 嵌入式 / 无 MMU 目标 ThorVG 是 **Tizen**(Samsung IoT/可穿戴操作系统)中的重要组件,并已集成到 **LVGL** 中,后者可在微控制器和无 MMU 的系统上运行。 在无 MMU 系统上,没有内存保护,且 `mmap_min_addr` 不适用。 如果映射了 NULL 页(这在裸机嵌入式环境中很常见),NULL 指针解引用可能会指向攻击者控制的内存。这在实际攻击中是否可行取决于攻击面——Tizen 设备上的 ThorVG 可能会解析来自不受信任的网络源或用户提供内容的 SVG。 这种情况证明了即使是 CVSS Medium 级别的发现,也应当进行负责任的披露。 ## 披露时间线 | 日期 | 事件 | |---|---| | 2026-xx-xx | 通过 AFL++ 发现崩溃 | | 2026-xx-xx | 在 ASAN 下确认根本原因;生成 6 字节 POC | | 2026-xx-xx | 通过 GitHub Security Advisory 向 ThorVG 维护者 发送了私密报告 | | 2026-xx-xx | 维护者确认并开启了私密 fork | | 2026-xx-xx | 补丁合并至 v1.0.5 | | 2026-xx-xx | GHSA-f863-8ghq-7h64 发布;CVE-2026-45729 被分配 | ## 补丁差异(摘要) 该补丁在 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 发布](https://github.com/thorvg/thorvg/releases/tag/v1.0.5) ## 复现(安全,仅限本地) ``` # 从源码构建(使用 ASAN) git clone https://github.com/thorvg/thorvg && cd thorvg git checkout meson setup build -Db_sanitize=address && ninja -C build # 针对已构建的库编译 harness clang++ -std=c++17 -fsanitize=address -O1 -g \ fuzz_thorvg.cpp -o fuzz_thorvg \ -Ibuild/src/include -Lbuild/src -lthorvg # 触发 printf '<' | ./fuzz_thorvg /dev/stdin ``` 预期输出:在 `tvg::SvgParser::_parseStyle` 中出现带有 `SEGV on unknown address 0x000000000000` 的 ASAN 报告。 ## 参考 - [GHSA-f863-8ghq-7h64](https://github.com/advisories/GHSA-f863-8ghq-7h64) - [ThorVG 项目](https://github.com/thorvg/thorvg) - [CWE-476: NULL 指针解引用](https://cwe.mitre.org/data/definitions/476.html) - [AFL++ 文档](https://aflplus.plus/) *由 yeahhbean (이예빈) 通过基于 AFL++ 覆盖率引导并使用结构感知 SVG 种子语料库的 fuzzing 发现。*
标签:C++, SVG解析, 图形库, 嵌入式, 数据擦除, 漏洞分析, 空指针解引用, 路径探测, 预握手