Back to Blog
high SEVERITY7 min read

How remote memory exhaustion happens in Rust QUIC (Quinn) and how to fix it

A high-severity vulnerability (GHSA-4w2j-m93h-cj5j) in `quinn-proto`, the QUIC protocol implementation underlying the Quinn library, allowed remote attackers to exhaust server memory by sending unbounded out-of-order stream data. The `crosshash` project's `Cargo.lock` pinned the vulnerable `quinn-proto` 0.11.14; upgrading to 0.11.15 closes the gap by bounding how much out-of-order stream data the reassembly buffer will retain.

O
By Orbis AppSec
Published September 7, 2026Reviewed September 7, 2026

Answer Summary

This is a remote memory exhaustion vulnerability (CWE-770, related to CWE-400) in `quinn-proto` 0.11.14, the QUIC protocol engine used by the Rust Quinn library, caused by unbounded buffering of out-of-order stream data during reassembly. The fix is to upgrade `quinn-proto` to 0.11.15, which the Quinn maintainers patched to enforce limits on out-of-order stream reassembly buffers, preventing a single malicious peer from forcing unbounded memory allocation.

Vulnerability at a Glance

cweCWE-770 (Allocation of Resources Without Limits or Throttling)
fixUpgrade `quinn-proto` from 0.11.14 to 0.11.15 in `crosshash/Cargo.lock`, which bounds out-of-order reassembly buffering
riskA remote, unauthenticated peer can send crafted QUIC stream frames out of order to force the server to buffer unbounded data, exhausting memory and causing denial of service
languageRust
root cause`quinn-proto` 0.11.14's stream reassembly logic did not cap the amount of out-of-order data it would hold before the missing gaps arrived
vulnerabilityRemote memory exhaustion via unbounded out-of-order stream reassembly

Introduction

The crosshash/Cargo.lock file locks the exact dependency graph used to build the crosshash project — including transitive dependencies pulled in by the Quinn QUIC stack. One of those transitive dependencies, quinn-proto 0.11.14, contained a flaw in how it reassembles QUIC stream data that arrives out of order. Because QUIC (like TCP) allows packets to arrive in any order over the network, the protocol implementation has to buffer data until the missing pieces show up. If that buffering has no upper bound, a remote attacker who controls a QUIC connection can weaponize normal-looking traffic into a memory-exhaustion denial-of-service attack against any server using this library.

This isn't a bug in application code written for crosshash — it's a bug in a widely-used protocol library that crosshash depends on. That's exactly why dependency-level vulnerabilities deserve the same attention as first-party code: the vulnerable code path runs inside your process, with your process's memory limits, regardless of who wrote it.

The Vulnerability Explained

QUIC streams support out-of-order delivery: a receiver can get bytes 1000–2000 of a stream before it gets bytes 0–1000. To handle this correctly, quinn-proto's stream state machine keeps a reassembly buffer that stores "future" chunks until the gaps preceding them are filled, at which point the buffered data is delivered to the application in order.

The vulnerable behavior in quinn-proto 0.11.14 was that this reassembly buffering was effectively unbounded relative to the attacker's ability to generate out-of-order chunks. A malicious or misbehaving peer could:

  1. Open one or more QUIC streams.
  2. Send many small chunks of data at large, scattered offsets within each stream — deliberately skipping the low offsets so the data can never be delivered to the application.
  3. Keep the connection alive, repeating this pattern across many streams or connections.

Because the receiving endpoint has to assume the missing gaps might arrive eventually, it keeps accumulating and holding onto all of this out-of-order data in memory. Multiply this across many streams and connections, and a single attacker (or a small botnet) can drive the target's memory usage up dramatically without ever completing a legitimate data transfer — a classic CWE-770: Allocation of Resources Without Limits or Throttling, closely related to CWE-400: Uncontrolled Resource Consumption.

For any service built on Quinn — file-transfer daemons, HTTP/3 servers, custom RPC layers, or in this case whatever network-facing component of crosshash uses the Quinn stack — this translates into a straightforward remote DoS primitive: no authentication bypass is needed, just the ability to open a QUIC connection and send crafted frames.

Example attack scenario: An attacker connects to a crosshash-based service that accepts QUIC connections. Instead of sending a legitimate request, the attacker's client opens a stream and immediately sends a chunk at offset 10,000,000, then another at offset 20,000,000, and so on — never sending the initial bytes at offset 0. quinn-proto's reassembly logic buffers each of these chunks, waiting for the gap to be filled. The attacker repeats this on hundreds of streams across a handful of connections. The server's memory footprint balloons, degrading performance for legitimate users and potentially triggering an OOM kill of the process — a full outage from what looks, at the network layer, like ordinary QUIC traffic.

The Fix

The fix here is intentionally minimal and surgical: bump the pinned version of quinn-proto in crosshash/Cargo.lock from the vulnerable 0.11.14 to the patched 0.11.15.

Before:

[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"

After:

[[package]]
name = "quinn-proto"
version = "0.11.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"

Version 0.11.15 upstream (published by the Quinn maintainers in response to GHSA-4w2j-m93h-cj5j) introduces bounds on how much out-of-order stream data the reassembly logic will retain per stream/connection before it either drops, rejects, or otherwise limits further buffering. This directly removes the exploit primitive: an attacker can no longer force unbounded buffer growth simply by sending scattered, gap-filled chunks.

Because this change is purely a dependency version bump — with the lockfile's checksum updated to match the new, verified crate contents — no application logic in crosshash needed to change. The public API surface used by crosshash remains identical between 0.11.14 and 0.11.15, so this is a drop-in, behavior-preserving upgrade for all valid, well-formed QUIC traffic. Only the handling of adversarial, out-of-order input is tightened.

This is also a good example of why "just bump the lockfile" changes are legitimate security fixes, not busywork: the vulnerable logic lives entirely inside the third-party crate, so the only way to remediate it without vendoring a patch is to consume the upstream fix.

Prevention & Best Practices

  • Track dependency advisories continuously. Use cargo audit or cargo deny in CI to catch known-vulnerable crate versions (like quinn-proto < 0.11.15) before they ship.
  • Pin dependencies, but review lockfile diffs. Cargo.lock pins exact versions for reproducibility — make sure your review process treats lockfile-only PRs (like this one) as security-relevant, not "noise."
  • Apply resource limits at the application layer too. Even with a patched library, consider setting your own caps on concurrent streams, connections, and per-connection memory via Quinn's configuration APIs (TransportConfig) as defense in depth.
  • Prefer software composition analysis (SCA) in your pipeline. Tools like Trivy, cargo audit, and GitHub's Dependabot alerts specifically catch vulnerabilities like GHSA-4w2j-m93h-cj5j that live in transitive dependencies you may never directly interact with.
  • Understand your network stack's trust boundary. Any protocol implementation that buffers data on behalf of an untrusted peer (reassembly, decompression, deduplication) is a candidate for resource-exhaustion bugs — treat these subsystems as high-risk during dependency review.

Key Takeaways

  • The vulnerable code path was entirely inside the quinn-proto dependency's stream reassembly logic — crosshash itself required zero code changes, only a version bump.
  • Out-of-order QUIC stream delivery is a legitimate protocol feature that becomes an attack surface when reassembly buffering isn't bounded — this is the specific mechanism behind GHSA-4w2j-m93h-cj5j.
  • The fix is a single-line-per-field lockfile change: quinn-proto 0.11.14 → 0.11.15, with a corresponding checksum update in crosshash/Cargo.lock.
  • This is classified as CWE-770/CWE-400 (unbounded resource allocation), a pattern worth specifically checking for whenever you evaluate network protocol libraries.
  • Lockfile-only PRs that bump a transitive dependency version deserve the same security scrutiny as source-code changes — they can close remotely exploitable DoS vectors.

How Orbis AppSec Detected This

  • Source: Untrusted QUIC stream frames sent by a remote peer over the network, specifically out-of-order data chunks at arbitrary stream offsets.
  • Sink: quinn-proto's internal stream reassembly buffer (stream state machine in the vendored quinn-proto 0.11.14 dependency resolved via crosshash/Cargo.lock).
  • Missing control: No upper bound on the amount of out-of-order data the reassembly buffer would retain per stream/connection before the corresponding gaps were filled.
  • CWE: CWE-770 — Allocation of Resources Without Limits or Throttling (related: CWE-400 — Uncontrolled Resource Consumption).
  • Fix: Upgraded quinn-proto from 0.11.14 to 0.11.15 in crosshash/Cargo.lock, pulling in upstream's bounded reassembly-buffer handling.

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

GHSA-4w2j-m93h-cj5j is a reminder that memory-safety issues aren't only about buffer overflows and use-after-free bugs — resource-exhaustion flaws in protocol-handling code can be just as damaging, and just as remotely exploitable, without a single line of unsafe code involved. In this case, the fix for crosshash was as simple as it gets: bump quinn-proto from 0.11.14 to 0.11.15 in Cargo.lock and pick up the upstream bound on out-of-order stream reassembly. The lesson for every Rust project consuming network protocol libraries is to treat lockfile updates for crates like quinn/quinn-proto as security-critical, keep automated dependency scanning in your CI pipeline, and layer your own resource limits on top of what the library provides.

References

Frequently Asked Questions

What is remote memory exhaustion in quinn-proto?

It's a denial-of-service condition where a QUIC peer sends stream data out of order faster than gaps can be filled, causing `quinn-proto`'s reassembly buffers to grow without bound and consume all available memory on the receiving endpoint.

How do you prevent memory exhaustion vulnerabilities in Rust QUIC applications?

Keep dependencies like `quinn` and `quinn-proto` up to date, monitor security advisories (GHSA/RustSec), and, where possible, configure application-level limits on stream counts, buffer sizes, and connection concurrency.

What CWE is remote memory exhaustion classified under?

It's typically classified as CWE-770 (Allocation of Resources Without Limits or Throttling), a specific case of the broader CWE-400 (Uncontrolled Resource Consumption).

Is rate-limiting connections enough to prevent this kind of memory exhaustion?

No — rate-limiting new connections doesn't stop a single already-established connection from sending an unbounded amount of out-of-order stream data; the underlying protocol library must itself enforce reassembly buffer limits, which is exactly what the 0.11.15 patch does.

Can static analysis detect this quinn-proto vulnerability?

Not directly through source code analysis of the application itself — this flaw lives inside a third-party dependency's internal buffering logic. Software composition analysis (SCA) tools like Trivy, which scan `Cargo.lock` against vulnerability databases, are what actually catch this class of issue.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #266

Related Articles

high

How Denial-of-Service via Unbounded Brace Expansion Happens in Node.js and How to Fix It

A critical denial-of-service vulnerability in the `brace-expansion` package allowed attackers to exhaust process memory through unbounded intermediate array expansion. The fix upgrades the package to patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) that implement proper expansion length limits, preventing out-of-memory crashes in production applications.

high

How Inherited libvips Vulnerabilities in sharp Impact Image Processing and How to Fix Them

A critical vulnerability (GHSA-f88m-g3jw-g9cj) was discovered where the sharp image processing library inherited four dangerous libvips vulnerabilities that could be exploited through maliciously crafted images. The fix involved upgrading sharp from version 0.34.5 to 0.35.0, which includes hardened input handling and updated libvips bindings to prevent exploitation of these inherited weaknesses.

high

How insecure-use-string-copy-fn happens in C and how to fix it

A high-severity vulnerability was identified in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to handle path strings without proper bounds checking or guaranteed null-termination. The fix replaces `strcpy()` with direct character assignment and `strncpy()` with `snprintf()`, eliminating both buffer overflow and missing null-terminator risks in the plugin installation workflow.

critical

How NULL pointer dereference from unchecked malloc() happens in C and how to fix it

A critical memory safety vulnerability was discovered in `bench/tokenizer/tokenizer.c` where `malloc()` was called without checking its return value before passing the pointer to `memcpy()`. If allocation fails and `malloc()` returns NULL, the subsequent `memcpy()` writes to address zero, causing heap corruption or potential arbitrary code execution. The fix adds a single NULL check immediately after allocation, exiting cleanly on failure rather than proceeding with a dangerously invalid pointer

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.