Living Off The Land: Exploiting Memory Safety in libcurl Without Touching Its Source

If you spend enough time in the weeds of C code, you’ll notice a peculiar thing about “not a bug” reports: they often are bugs — just bugs whose triggers live at the API boundary rather than in the library’s internals.

libcurl is an incredible piece of software. It moves bits across the network for everything from embedded devices to cloud services. But like most C libraries, it assumes that the caller is sane, the parameters are consistent, and the API contract is honored. In the real world, that assumption is a fantasy.

The following cases show how an attacker can abuse perfectly normal libcurl APIs to smash heap boundaries, read past buffers, and sometimes gain code execution — all without touching the source code or violating the “API contract” in ways detectable at compile time.


Case 1: Curl_memdup0() Heap Buffer Overflow via CURLOPT_COPYPOSTFIELDS

Bug class: Heap buffer over-read / potential overflow

Impact: Info disclosure, crash, potential RCE

Root cause: Blind trust in size parameter from CURLOPT_POSTFIELDSIZE

The sequence is simple:

  1. Set CURLOPT_POSTFIELDSIZE to a huge value (controlled by attacker input).
  2. Pass a much smaller buffer to CURLOPT_COPYPOSTFIELDS.
  3. libcurl’s Curl_memdup0() trusts the size, calls memcpy(), and reads off the heap until it hits unmapped memory or ASAN intercepts.

Living Off The Land angle:

You don’t need a malicious build of libcurl. You don’t even need a heap spray primitive. If you can influence POST size metadata upstream — from a gateway, proxy, or length-prefixed higher-layer protocol — you can cause predictable OOB reads in the victim’s process.


Case 2: HTTP/3 Stack Use-After-Scope via CURLOPT_POSTFIELDS

Bug class: Stack use-after-scope

Impact: DoS, memory corruption, possible control flow hijack

Root cause: Pointer lifetime mismatch when using stack buffers

Here’s the trick:

  1. Create a stack-allocated POST buffer inside a function block.
  2. Set it with CURLOPT_POSTFIELDS for an HTTP/3 request.
  3. Exit the scope (stack memory invalidated).
  4. curl_easy_perform() later dereferences the dangling pointer.

The crash happens in Curl_pretransfer() during a strlen() call on freed stack memory.

Living Off The Land angle:

HTTP/3’s adoption curve is working in your favor here. Many developers are still “just trying it” and may use stack buffers for quick POSTs. If you can influence control flow so the transfer starts after the stack frame is gone, you get deterministic crashes. No weird race conditions required.


Case 3: curl_slist_append() Heap Buffer Overflow via Unterminated String

Bug class: Heap buffer over-read

Impact: Info disclosure, DoS

Root cause: strdup()strlen() on non-NUL-terminated input

FFI boundaries are your best friend here. In C, a string is “by definition” null-terminated, but in Rust, Python, or Go bindings, that’s not always true unless you sanitize manually.

Steps:

  1. Allocate a heap buffer with arbitrary nonzero bytes.
  2. Pass it to curl_slist_append() without a null terminator.
  3. Watch strlen() read off into adjacent heap memory.

Living Off The Land angle:

Any time libcurl is wrapped by a higher-level language binding, there’s a good chance strings cross the API boundary without explicit termination checks. The bug lives in the gap between “what C thinks a string is” and “what your binding sends it.”


Why This Works Without “Breaking the Rules”

In all three cases, the maintainers classified the issues as “Not Applicable” because the inputs violated the API contract. From their perspective, this is user error.

From an attacker’s perspective, this is attack surface.

Attackers don’t need to be the developer — they only need a path to influence API parameters in a running process. These bugs are “living off the land” because they don’t require code injection, memory patching, or exotic ROP chains. They simply exploit:

  • Trust in size parameters
  • Trust in pointer lifetimes
  • Trust in null termination

…and they do it using the exact public APIs the library wants you to call.


Defensive Takeaways

If you ship libcurl in anything exposed to untrusted data, treat every API like an FFI boundary — even in C:

  • Bounds check everything before it hits libcurl.
  • Prefer CURLOPT_COPYPOSTFIELDS over raw pointer passing — and still validate sizes.
  • Use heap buffers with known lifetimes for all POST data.
  • For lists and strings, enforce null termination at the boundary, not inside libcurl.

The C contract doesn’t protect you from the real world. Memory safety doesn’t care about your API docs.


Final Thought

A caller bug should never become an attacker-controlled heap overflow in a security-critical library. But until every C API is hardened against trivial misuse, those bugs will keep living off the land — and some of us will keep harvesting them.

Stay glitchy.

@geeknik


You'll only receive email when they publish something new.

More from Geeknik`s Lab
All posts