Living Off The Land: Exploiting Memory Safety in libcurl Without Touching Its Source
August 9, 2025•779 words
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:
- Set
CURLOPT_POSTFIELDSIZE
to a huge value (controlled by attacker input). - Pass a much smaller buffer to
CURLOPT_COPYPOSTFIELDS
. - libcurl’s
Curl_memdup0()
trusts the size, callsmemcpy()
, 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:
- Create a stack-allocated POST buffer inside a function block.
- Set it with
CURLOPT_POSTFIELDS
for an HTTP/3 request. - Exit the scope (stack memory invalidated).
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:
- Allocate a heap buffer with arbitrary nonzero bytes.
- Pass it to
curl_slist_append()
without a null terminator. - 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