Summary
Mint's HTTP/1 response parser accumulates every parsed response header (and every chunked-trailer header) into an unbounded per-request list that only clears when the terminating blank line arrives. A remote HTTP server reachable by the Mint client (directly, via SSRF, via an attacker-controlled redirect target, or via a man-in-the-middle) can stream complete header or trailer lines forever, growing connection state on the client until the BEAM node is killed by the operating system's out-of-memory handler.
Details
1. Unbounded response-header accumulator. Mint.HTTP1.decode_headers/5 in lib/mint/http1.ex walks the response header section by calling the underlying line parser and prepending each {name, value} tuple to a growing list. When the incoming TCP segment ends mid-section, the accumulated list is stashed in request.headers_buffer and the trailing bytes in conn.buffer, ready to resume on the next Mint.HTTP1.stream/2 invocation. The section is only released when the parser returns :eof (the terminating blank line), and nothing in between caps the number of headers or the byte size of the section.
2. Same pattern for chunked trailers. Mint.HTTP1.decode_trailer_headers/4 reuses the same accumulator-plus-headers_buffer shape for HTTP/1.1 chunked trailers. After a zero-size chunk, the parser enters trailer mode and every trailer line is prepended to a list stored on the request across stream/2 calls, again only terminated by :eof.
3. Underlying parser has no built-in cap. Mint.HTTP1.Response.decode_header/1 in lib/mint/http1/response.ex invokes :erlang.decode_packet(:httph_bin, binary, []) with an empty option list, so the packet_size and line_length options both default to 0 (unlimited). No layer between the socket and the accumulator constrains the section.
4. Denial of service. A malicious server sends the status line (or, for the trailer variant, Transfer-Encoding: chunked plus a small chunk followed by 0\r\n) and then streams complete header or trailer lines indefinitely without ever writing the closing blank line. The client's conn state grows on every stream/2 call, driving the BEAM process resident set until the OS OOM-kills the node.
PoC
- Point a Mint HTTP/1 client at an attacker-controlled origin (direct connection, SSRF, or a redirect the client auto-follows).
- From that origin, respond with
HTTP/1.1 200 OK\r\n followed by a stream of minimal header lines (A:\r\n, or larger X-Pad-N: <junk>\r\n for faster growth) and never emit the closing \r\n.
- The client's normal
Mint.HTTP1.stream/2 receive loop grows request.headers_buffer and conn.buffer on every packet until the BEAM node is OOM-killed. The chunked-trailer variant substitutes Transfer-Encoding: chunked, a single small chunk, 0\r\n, and then an endless stream of trailer lines.
Impact
Any application using Mint as an HTTP/1 client is vulnerable whenever it can be induced to talk to an attacker-controlled server, whether directly, through an auto-followed redirect, through SSRF, or through a network man-in-the-middle. A single such connection is enough to exhaust the BEAM node's memory and terminate the entire application process.
References
Summary
Mint's HTTP/1 response parser accumulates every parsed response header (and every chunked-trailer header) into an unbounded per-request list that only clears when the terminating blank line arrives. A remote HTTP server reachable by the Mint client (directly, via SSRF, via an attacker-controlled redirect target, or via a man-in-the-middle) can stream complete header or trailer lines forever, growing connection state on the client until the BEAM node is killed by the operating system's out-of-memory handler.
Details
1. Unbounded response-header accumulator.
Mint.HTTP1.decode_headers/5inlib/mint/http1.exwalks the response header section by calling the underlying line parser and prepending each{name, value}tuple to a growing list. When the incoming TCP segment ends mid-section, the accumulated list is stashed inrequest.headers_bufferand the trailing bytes inconn.buffer, ready to resume on the nextMint.HTTP1.stream/2invocation. The section is only released when the parser returns:eof(the terminating blank line), and nothing in between caps the number of headers or the byte size of the section.2. Same pattern for chunked trailers.
Mint.HTTP1.decode_trailer_headers/4reuses the same accumulator-plus-headers_buffershape for HTTP/1.1 chunked trailers. After a zero-size chunk, the parser enters trailer mode and every trailer line is prepended to a list stored on the request acrossstream/2calls, again only terminated by:eof.3. Underlying parser has no built-in cap.
Mint.HTTP1.Response.decode_header/1inlib/mint/http1/response.exinvokes:erlang.decode_packet(:httph_bin, binary, [])with an empty option list, so thepacket_sizeandline_lengthoptions both default to 0 (unlimited). No layer between the socket and the accumulator constrains the section.4. Denial of service. A malicious server sends the status line (or, for the trailer variant,
Transfer-Encoding: chunkedplus a small chunk followed by0\r\n) and then streams complete header or trailer lines indefinitely without ever writing the closing blank line. The client'sconnstate grows on everystream/2call, driving the BEAM process resident set until the OS OOM-kills the node.PoC
HTTP/1.1 200 OK\r\nfollowed by a stream of minimal header lines (A:\r\n, or largerX-Pad-N: <junk>\r\nfor faster growth) and never emit the closing\r\n.Mint.HTTP1.stream/2receive loop growsrequest.headers_bufferandconn.bufferon every packet until the BEAM node is OOM-killed. The chunked-trailer variant substitutesTransfer-Encoding: chunked, a single small chunk,0\r\n, and then an endless stream of trailer lines.Impact
Any application using Mint as an HTTP/1 client is vulnerable whenever it can be induced to talk to an attacker-controlled server, whether directly, through an auto-followed redirect, through SSRF, or through a network man-in-the-middle. A single such connection is enough to exhaust the BEAM node's memory and terminate the entire application process.
References