Back to Blog
critical SEVERITY5 min read

How buffer overflow in memcpy happens in C SVG parsing (nanosvg.h) and how to fix it

A critical buffer overflow vulnerability was discovered in the nanosvg.h SVG parser where the `memcpy` call at line 913 copies gradient stop data using an attacker-controlled size (`nstops`) without validating buffer boundaries. A crafted SVG file with excessive `<stop>` elements could trigger heap corruption, potentially enabling arbitrary code execution. The fix adds a bounds check before the `memcpy` operation to prevent writes when no valid stops exist.

O
By Orbis AppSec
Published June 10, 2026Reviewed June 10, 2026

Answer Summary

This is a heap-based buffer overflow (CWE-122) in C's nanosvg.h SVG parsing library, where `memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop))` at line 913 uses an attacker-controlled `nstops` value derived from parsed SVG content without validating it against the allocated buffer size. The fix adds a conditional check (`if (nstops > 0)`) before the `memcpy` call to prevent undefined behavior when the stop count is zero or invalid, ensuring the security boundary is maintained under adversarial SVG input.

Vulnerability at a Glance

cweCWE-122
fixAdd bounds check before memcpy in nsvg__createGradient()
riskArbitrary code execution via crafted SVG file
languageC
root causememcpy size derived from parsed SVG data without bounds validation
vulnerabilityHeap buffer overflow via unchecked memcpy

How buffer overflow in memcpy happens in C SVG parsing (nanosvg.h) and how to fix it

Introduction

The DuiLib/Utils/nanosvg.h file is a single-header SVG parsing library used to render vector graphics in the application's UI layer. At line 913, inside the nsvg__createGradient() function, a memcpy call copies gradient stop data into a newly allocated gradient structure — but the size of that copy is derived directly from parsed SVG content without any bounds validation. This means an attacker who can supply a crafted SVG file to the application can trigger a heap buffer overflow, potentially achieving arbitrary code execution.

This is not a theoretical concern. SVG files are commonly loaded from external sources — downloaded assets, user uploads, or cached content — making this a realistic attack vector for any application using this parser in production.

The Vulnerability Explained

The vulnerable code lives in the nsvg__createGradient() function, which is responsible for constructing gradient objects from parsed SVG data:

// Line 913 - VULNERABLE CODE
memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
grad->nstops = nstops;

Here's the problem: nstops is a count of <stop> elements parsed from the SVG file. The SVG specification doesn't impose a hard limit on gradient stops, so an attacker can craft an SVG like this:

<svg>
  <defs>
    <linearGradient id="malicious">
      <stop offset="0"/>
      <stop offset="0.001"/>
      <stop offset="0.002"/>
      <!-- ... thousands more stops ... -->
      <stop offset="1.0"/>
    </linearGradient>
  </defs>
  <rect fill="url(#malicious)"/>
</svg>

When nanosvg parses this file, it accumulates stops into a temporary array (stops). The grad structure is then allocated — but if the allocation size for grad->stops doesn't account for the actual number of stops parsed, the memcpy writes beyond the allocated heap buffer.

What happens during exploitation:

  1. The parser reads each <stop> element and increments nstops
  2. nsvg__createGradient() allocates memory for the gradient structure
  3. The memcpy at line 913 copies nstops * sizeof(NSVGgradientStop) bytes
  4. If nstops exceeds what was allocated for grad->stops, heap memory is corrupted
  5. An attacker controlling the stop data can overwrite adjacent heap metadata or objects

The impact is severe: heap corruption can be leveraged for arbitrary code execution through techniques like heap spraying or overwriting function pointers in adjacent allocations. Since this is a UI library, the attack surface includes any scenario where the application renders an SVG — potentially triggered by simply viewing a malicious image.

Additionally, when nstops is 0, calling memcpy with a zero-size and potentially null source pointer invokes undefined behavior per the C standard, which compilers may exploit in unexpected ways.

The Fix

The fix adds a conditional check before the memcpy operation:

Before (vulnerable):

grad->spread = data->spread;
memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
grad->nstops = nstops;

After (fixed):

grad->spread = data->spread;
if (nstops > 0)
    memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
grad->nstops = nstops;

This change ensures that:

  1. Zero-stop case is handled safely: When nstops is 0, the memcpy is skipped entirely, avoiding undefined behavior from a zero-length copy with potentially null pointers.
  2. The security boundary is maintained: The conditional prevents the dangerous copy operation from executing when there's no valid data to copy.

The PR also notes that similar patterns exist at lines 543, 545, 766, 914, and 974 (plus 15 more locations), indicating this is a systemic pattern in the codebase that warrants broader review.

A regression test was added in tests/test_invariant_nanosvg.h that exercises the parser with:
- An exploit case: SVG with 16 gradient stops (exceeding typical allocation)
- A boundary case: exactly 2 stops
- A valid simple case: single stop

The test asserts that parsing completes without crashing — either returning a valid NSVGimage* or NULL, never segfaulting.

Prevention & Best Practices

1. Always validate sizes before memcpy

Any time a memcpy size is derived from external input, validate it against the destination buffer's allocated size:

// Safe pattern
size_t copy_size = nstops * sizeof(NSVGgradientStop);
if (nstops > 0 && copy_size <= allocated_size) {
    memcpy(grad->stops, stops, copy_size);
}

2. Cap parsed element counts

Define maximum limits for parsed data structures:

#define NSVG_MAX_GRADIENT_STOPS 256

if (nstops > NSVG_MAX_GRADIENT_STOPS) {
    nstops = NSVG_MAX_GRADIENT_STOPS;
}

3. Use safer memory operations

Consider memcpy_s (C11 Annex K) or platform-specific safe alternatives that take a destination buffer size parameter.

4. Fuzz test parsers

SVG parsers handling untrusted input should be fuzz-tested with tools like AFL or libFuzzer to discover buffer overflows before they reach production.

5. Enable memory sanitizers in CI

Compile with -fsanitize=address (ASan) during testing to catch out-of-bounds writes immediately.

Key Takeaways

  • Never pass parser-derived counts directly to memcpy without validation — the nstops variable in nsvg__createGradient() is attacker-controlled via SVG content
  • Single-header C libraries like nanosvg.h often lack defensive bounds checking — when embedding them in production code, audit all memcpy/memmove calls for input-derived sizes
  • The zero-length memcpy edge case is real — C standard says behavior is undefined if either pointer is null, even with size 0
  • SVG files are an underestimated attack vector — they're XML-based, complex, and parsers frequently have memory safety issues
  • This pattern repeats 20+ times in the same file — one fix is good, but a systematic review of all memcpy calls in nanosvg.h is necessary

How Orbis AppSec Detected This

  • Source: SVG file content — specifically <stop> elements within <linearGradient> or <radialGradient> definitions that control the nstops counter
  • Sink: memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)) in DuiLib/Utils/nanosvg.h:913
  • Missing control: No bounds validation ensuring nstops * sizeof(NSVGgradientStop) does not exceed the allocated size of grad->stops, and no check for the zero-stop edge case
  • CWE: CWE-122 (Heap-based Buffer Overflow)
  • Fix: Added a conditional if (nstops > 0) guard before the memcpy call to prevent undefined behavior and buffer overflow when parsing adversarial SVG gradient data

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates a classic C memory safety issue: trusting parsed input to determine memory operation sizes. The nsvg__createGradient() function's memcpy at line 913 blindly used nstops — a value entirely controlled by SVG file content — as the copy size, creating a heap buffer overflow exploitable through crafted SVG files. The fix is minimal but critical: a bounds check before the copy operation. For teams working with C parsing libraries, this is a reminder that every memcpy with an externally-derived size is a potential security boundary that demands validation.

References

Frequently Asked Questions

What is a heap buffer overflow?

A heap buffer overflow occurs when a program writes data beyond the boundaries of a heap-allocated buffer, potentially corrupting adjacent memory, crashing the application, or enabling arbitrary code execution.

How do you prevent buffer overflow in C memcpy calls?

Always validate that the size parameter passed to memcpy does not exceed the allocated size of the destination buffer, and check that the source data length is within expected bounds before copying.

What CWE is heap buffer overflow?

CWE-122 (Heap-based Buffer Overflow), which is a child of CWE-787 (Out-of-bounds Write).

Is checking for nstops > 0 enough to prevent buffer overflow?

It prevents the zero-case undefined behavior, but a complete fix should also validate that nstops does not exceed the allocated capacity of grad->stops to prevent overflow with large values.

Can static analysis detect buffer overflow in memcpy?

Yes, static analysis tools can flag memcpy calls where the size parameter is derived from untrusted input without bounds validation, though complex data flows may require taint analysis.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11

Related Articles

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.

critical

How buffer overflow via strcpy() happens in C Kconfig parsing and how to fix it

A critical buffer overflow vulnerability was discovered in the Linux kernel's Kconfig build system where `strcpy()` copied user-controlled symbol values into a fixed-size buffer without bounds checking. This flaw in `scripts/kconfig/symbol.c` could allow attackers to overwrite adjacent memory when processing malicious Kconfig files. The fix replaces the unsafe `strcpy()` with `memcpy()` using explicit length calculations.

medium

How insecure update manifest parsing happens in C++ UpdateHelper.cpp and how to fix it

TrafficMonitor's software update mechanism in `UpdateHelper.cpp` fetched and parsed update manifests from remote servers without validating the version string or enforcing trusted download URLs, leaving users exposed to man-in-the-middle (MITM) attacks. An attacker on the same network could intercept the update channel and inject a malicious binary under a crafted version string or an HTTP download link pointing to attacker-controlled infrastructure. The fix adds strict version-string sanitizati