CVE-2026-47077: Unbounded body accumulation in HTTP/3 response loop in hackney
Summary
hackneyh3:awaitresponseloop/6 in src/hackneyh3.erl accumulates the HTTP/3 response body in memory without any size cap. The after Timeout clause is a per-message inactivity timer, not a wall-clock deadline: every received streamdata chunk, housekeeping select message, or settings frame resets it. A malicious HTTP/3 server that drips one small chunk every Timeout - 1 ms with Fin = false and never terminates the stream keeps the loop alive indefinitely while the accumulation buffer grows without bound, eventually exhausting the BEAM process heap.
Details
In src/hackneyh3.erl, awaitresponseloop/6 (line 430) builds the body with:
erlang NewBody = <<AccBody/binary, Data/binary>>
There is no maxbody check and no monotonic deadline. The after Timeout clause at line 463 is restarted on each loop iteration. A server that ensures at least one message arrives within Timeout ms indefinitely (one small chunk per interval is sufficient) prevents the timeout from firing while AccBody grows linearly. The same module's waitconnected/3 (lines 388-389) shows the correct pattern: track an absolute start time and pass a shrinking Remaining budget into each receive. This loop does not.
Configurations
Only the HTTP/3 transport is affected. Applications using the default TCP/TLS hackney transport are not vulnerable. The vulnerability requires using hackneyh3 directly or passing {transport, h3} to hackney:request/5.
PoC
1. Stand up an HTTP/3 server that responds with 200 OK headers (Fin = false), then emits a small streamdata chunk every Timeout - margin ms with Fin = false indefinitely. 2. Issue hackney:request(get, Url, [], <<>>, [{transport, h3}]) against it. 3. Watch the client process heap grow monotonically. The configured timeout never fires; the process is eventually killed by maxheapsize or the OS OOM killer.
Impact
Remote denial of service via unbounded memory consumption. Affects hackney 2.0.0 through 4.0.0 when using the HTTP/3 transport against an attacker-controlled or attacker-influenced server. Each affected request consumes unbounded memory until the BEAM is killed. CVSS v4.0: 8.2 (HIGH).
Resources
Introduction commit: https://github.com/benoitc/hackney/commit/0334af206d5099fdf510ed9eda18e34396f065ad Patch commit: https://github.com/benoitc/hackney/commit/3d25f9fea26c90609de9d64366fedfe5065413bc
Other sources
Allocation of Resources Without Limits or Throttling vulnerability in benoitc hackney allows Flooding. hackneyh3:awaitresponseloop/6 accumulates the HTTP/3 response body in memory without any size cap. The after Timeout clause is a per-message inactivity timer that resets on every received chunk, housekeeping message, or settings frame — it is not a wall-clock deadline. A malicious HTTP/3 server that emits one small chunk every Timeout - 1 ms with Fin = false and never sends a final frame keeps the loop alive indefinitely while the accumulation buffer grows linearly without bound, eventually exhausting the BEAM process heap and causing an out-of-memory condition.
This issue affects hackney: from 2.0.0 before 4.0.1.
— NVD
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
erlang/hackneyto a version that resolves this vulnerability.Fixed in 4.0.1 - Upgrade
Upgrade
benoitc/hackneyto a version that resolves this vulnerability.Fixed in 4.0.1 - Configuration
Avoid the vulnerable HTTP/3 transport by not using `hackney_h3` directly and not passing `{transport, h3}` to `hackney:request/5`; applications using the default TCP/TLS hackney transport are not vulnerable.
hackney_h3 (hackney HTTP/3 transport) request transport = Use default TCP/TLS hackney transport instead of {transport, h3}
Event History
Frequently Asked Questions
Who is exposed to this issue?
Applications using hackney's HTTP/3 response handling are exposed when they connect to a malicious or compromised HTTP/3 server. The server can keep a response stream open and cause the client BEAM process heap to grow until memory is exhausted.
What does an attacker need to do?
The attacker needs to control an HTTP/3 server response and send at least one message before each inactivity timeout expires. Small non-final stream_data chunks are sufficient; the response never needs to terminate.
Does the configured timeout prevent exploitation?
No. The timeout is reset for every received stream_data chunk, select message, or settings frame, so it is an inactivity timer rather than a total response deadline.
What can be done before applying a fix?
Avoid making HTTP/3 requests to untrusted servers where possible. A mitigation should impose a maximum response-body size and an absolute monotonic deadline that decreases across loop iterations, rather than relying only on the per-message timeout.
How can I determine whether my code is affected?
Inspect src/hackney_h3.erl for await_response_loop/6. Affected code appends response data to AccBody with binary concatenation without a maximum-body check and restarts the after Timeout clause on each loop iteration instead of tracking an absolute deadline.