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

critical

How buffer overflow in URL parsing happens in C++ HTTP client and how to fix it

A critical buffer overflow vulnerability in the HTTP client's URL parsing function allowed attackers to overflow a stack-allocated host buffer through specially crafted URLs with excessively long hostnames. The vulnerability enabled arbitrary code execution by overwriting the return address. The fix adds proper bounds validation before the memcpy() operation to ensure the hostname length never exceeds the destination buffer size.

critical

How integer overflow in _wopendir() happens in C Windows dirent and how to fix it

A critical integer overflow vulnerability in `include/compat/dirent_msvc.h` allowed an attacker-controlled directory path length to wrap the `sizeof(wchar_t) * n + 16` allocation calculation, resulting in a dangerously undersized heap buffer. Subsequent writes to that buffer caused a heap overflow, enabling potential memory corruption or code execution on Windows systems. The fix adds a pre-allocation bounds check and proper errno signaling to safely reject overflow-inducing inputs.

critical

How buffer overflow happens in C xxd utility and how to fix it

A critical buffer overflow vulnerability was discovered in the xxd utility's `xxdline()` function where `strcpy()` was used without bounds checking on file input. An attacker could craft a malicious hex dump file with oversized lines to trigger memory corruption. The fix replaces the unsafe `strcpy()` with `snprintf()` to enforce buffer size limits.

critical

How buffer overflow in memcpy() happens in C/C++ embedded firmware and how to fix it

A critical buffer overflow vulnerability was discovered in the ESP32-based micro-journal firmware where `memcpy()` calls used `strlen()` without bounds checking, allowing oversized USB descriptor strings to corrupt adjacent memory. The fix replaces unbounded `strlen()` with `strnlen()` calls that enforce the destination buffer sizes (8, 16, and 4 bytes respectively), preventing heap/stack corruption from malicious USB devices.

high

How Denial of Service via crafted URI templates happens in Ruby addressable and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-35611) was discovered in the Ruby `addressable` gem versions prior to 2.9.0, which could allow attackers to crash or hang applications by sending specially crafted URI templates. The fix upgrades the dependency from version 2.8.7 to 2.9.0 across the Gemfile, Gemfile.lock, and gemspec in a Fastlane project, eliminating the vulnerable code path entirely.

critical

How Server-Side Request Forgery (SSRF) happens in Python requests.get() and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `models/common.py` where `requests.get()` fetched images from arbitrary URLs without validating whether the target resolved to internal infrastructure. An attacker could supply URLs targeting AWS metadata endpoints (169.254.169.254), private networks, or localhost services through the Flask REST API. The fix introduces DNS-resolution-based validation using Python's `socket.getaddrinfo()` and `ipaddress` module to block