Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed
high swesweep-fasthttp · http.go · BodyGunzip · run #4
Description
BodyGunzip(), BodyInflate(), BodyUnbrotli(), BodyUnzstd(), and BodyUncompressed() all call their WithLimit counterparts with maxBodySize=0, meaning no cap on decompressed output. An attacker sends a minimal gzip/deflate/brotli/zstd payload (~100 bytes) that decompresses to several GB. gunzipData/inflateData/etc. allocate the full decompressed size into a ByteBuffer with no bound, causing OOM and process termination. The convenience (no-limit) methods are the most commonly called entry points, making this the default behavior for any application that does not explicitly use the WithLimit variants.
Triage
- Verdict
- VALID confidence 0.83
- Model
- qwen/qwen3.8-27b
- Rounds
- 6
- Cost
- $0.0724 · 1008.6 s
[ARBITER] The public API methods `BodyGunzip`, `BodyInflate`, `BodyUnbrotli`, `BodyUnzstd`, and `BodyUncompressed` (and their Response counterparts) explicitly call their `WithLimit` variants with `maxBodySize=0`. The code documentation and implementation (e.g., `if maxBodySize <= 0 { ... }` checks in internal helpers like `copyZeroAllocWithLimit` or `readBody`) confirm that a limit of 0 disables size enforcement. Since these methods are public entry points that process potentially untrusted compressed data (HTTP bodies), an attacker can send a small compressed payload that decompresses into a very large amount of data, causing unbounded memory allocation and OOM. Under the library threat model, unbounded resource usage (OOM) triggered via public API with crafted input is a valid security vulnerability.All rounds
[
{
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"verdict": "VALID",
"reasoning": "The report claims that `BodyGunzip()`, `BodyInflate()`, `BodyUnbrotli()`, `BodyUnzstd()`, and `BodyUncompressed()` are vulnerable to decompression bombs because they call their `WithLimit` counterparts with `maxBodySize=0`, which implies no limit. I verified this in the code: `req.BodyGunzip()` returns `req.BodyGunzipWithLimit(0)`. The `WithLimit` methods pass this value to internal functions like `gunzipData`. The threat model for this library (fasthttp) explicitly includes \"resource bounds (unbounded allocation... infinite loops on crafted input)\" as a valid asset/impact. A decompression bomb is a classic resource exhaustion attack where a small compressed input results in a massive decompressed output, leading to Out-Of-Memory (OOM) and process termination. Since these are public API methods that process attacker-controlled data (HTTP request/response bodies), and the default behavior (`maxBodySize=0`) provides no protection against this, the vulnerability is real and reachable by an external attacker. The library does not enforce a global size limit on decompressed bodies by default, relying on the developer to use the `WithLimit` variants. Therefore, the absence of a limit in the default convenience methods constitutes a security vulnerability under the provided threat model.\n\nCRUX: The public API methods BodyGunzip, BodyInflate, BodyUnbrotli, BodyUnzstd, and BodyUncompressed default to maxBodySize=0, allowing unbounded memory allocation during decompression of attacker-controlled data, leading to potential OOM crashes.\nGREP: func (req *Request) BodyGunzip()",
"elapsed": 66.3,
"tokens": 24992,
"tokens_in": 24630,
"tokens_out": 362,
"cost_usd": 0.0114306,
"file": "http.go",
"round": 1,
"grep_used": true,
"grep_results": "GREP `func (req *Request) BodyGunzip() (simplified to: BodyGunzip)`:\n```\nfs_test.go:402:\tbody, err = resp.BodyGunzip()\nfs_test.go:914:\tbody, err = resp.BodyGunzip()\nhttp_test.go:379:\trespBody, err := resp.BodyGunzip()\nhttp_test.go:388:\t_, errUnzip := resp.BodyGunzip()\nhttp_test.go:1644:\t\tbody, err = r1.BodyGunzip()\nserver_test.go:2454:\tbody, err := resp.BodyGunzip()\nserver_test.go:2477:\tbody, err = resp.BodyGunzip()\nserver_test.go:2560:\tbody, err := resp.BodyGunzip()\nserver_test.go:2587:\tbody, err = resp.BodyGunzip()\nbrotli_test.go:147:\tbody, err := resp.BodyGunzip()\nhttp.go:489:// BodyGunzip returns un-gzipped body data.\nhttp.go:494:func (req *Request) BodyGunzip() ([]byte, error) {\nhttp.go:495:\treturn req.BodyGunzipWithLimit(0)\nhttp.go:498:// BodyGunzipWithLimit returns un-gzipped body data and limits the size\nhttp.go:502:func (req *Request) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\nhttp.go:506:// BodyGunzip returns un-gzipped body data.\nhttp.go:511:func (resp *Response) BodyGunzip() ([]byte, error) {\nhttp.go:512:\treturn resp.BodyGunzipWithLimit(0)\nhttp.go:515:// BodyGunzipWithLimit returns un-gzipped body data and limits the size\nhttp.go:519:func (resp *Response) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\nhttp.go:678:\t\treturn req.BodyGunzipWithLimit(maxBodySize)\nhttp.go:709:\t\treturn resp.BodyGunzipWithLimit(maxBodySize)\nfs_fs_test.go:218:\tbody, err = resp.BodyGunzip()\nfs_fs_test.go:509:\tbody, err = resp.BodyGunzip()\nfs_fs_test.go:716:\tbody, err = resp.BodyGunzip()\n```"
},
{
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"verdict": "VALID",
"reasoning": "The report claims that `BodyGunzip()` and similar methods are vulnerable to decompression bombs because they pass `maxBodySize=0` to their `WithLimit` counterparts, which results in no size limit for the decompressed data. I verified this in the code: `func (req *Request) BodyGunzip() ([]byte, error) { return req.BodyGunzipWithLimit(0) }`. The internal function `gunzipData` (and similar) allocates a `bytebufferpool.ByteBuffer` and writes the decompressed data into it. If `maxBodySize <= 0`, no limit is applied, and the buffer grows to the size of the decompressed payload. An attacker can send a small compressed payload (e.g., ~100 bytes of gzip) that expands to several gigabytes, causing an Out-Of-Memory (OOM) condition in the host process. This is a classic decompression bomb attack. The threat model for this library explicitly lists 'resource bounds (unbounded allocation...)' as a valid asset/impact. These methods are public API entry points (`Request.BodyGunzip`, `Response.BodyGunzip`) that process untrusted input (HTTP bodies). The default behavior (no limit) is a security vulnerability because it allows a remote attacker to exhaust server memory by sending a crafted request or receiving a crafted response. The `WithLimit` variants provide a defense, but the standard convenience methods do not use them by default. Therefore, the vulnerability is real, attacker-reachable, and results in meaningful harm (crash/OOM).\n\nCRUX: BodyGunzip and other decompression methods default to maxBodySize=0, allowing unbounded memory allocation for attacker-controlled compressed data, leading to OOM.\nGREP: BodyGunzipWithLimit(0)",
"elapsed": 195.5,
"tokens": 26058,
"tokens_in": 25673,
"tokens_out": 385,
"cost_usd": 0.01193766,
"file": "http.go",
"round": 2,
"grep_used": true,
"grep_results": "GREP `BodyGunzipWithLimit(0)`:\n```\nhttp.go:495:\treturn req.BodyGunzipWithLimit(0)\nhttp.go:512:\treturn resp.BodyGunzipWithLimit(0)\n```"
},
{
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"verdict": "UNCERTAIN",
"reasoning": "The report identifies that the public API methods `BodyGunzip`, `BodyInflate`, `BodyUnbrotli`, `BodyUnzstd`, and `BodyUncompressed` call their `WithLimit` counterparts with `maxBodySize=0`, which effectively disables any size limit on the decompressed output. I verified this by tracing the code: `req.BodyGunzip()` calls `req.BodyGunzipWithLimit(0)`, which calls `gunzipData(req.Body(), 0)`. Inside `gunzipData`, the function `writeGunzip` is called with `maxBodySize=0`. The `writeGunzip` function (and similar for other formats) uses `io.Copy` or direct reads into a `ByteBuffer` without enforcing a limit when `maxBodySize <= 0`. \n\nHowever, we must assess the threat model. The threat model states: \"Attacker: Code that calls the public API... parsers, decoders... receive adversarial payloads that the host application obtained from untrusted sources.\" It also states: \"Not a vulnerability under this model: Missing parameter validation in non-public... functions that are not directly reachable from outside when the public boundary already enforces the invariant.\" \n\nCrucially, these methods (`BodyGunzip`, etc.) are convenience helpers for *decoding* the body. The actual *reading* of the body from the network connection is done via `Request.ReadLimitBody` or `Response.ReadLimitBody`. In a standard server or client setup, the host application *must* call these `Read...LimitBody` methods to parse the HTTP request/response from the stream. If the host application uses `ReadLimitBody(r, 0)`, the *compressed* body size is still unlimited, but typically host applications enforce a limit on the *raw* body size (e.g., 1MB) to prevent DoS via large uploads. Even if the raw body is small (e.g., 100KB), a decompression bomb could expand it to GBs. \n\nThe key question is: Is the *absence of a default limit* in these convenience methods a vulnerability in the *library*, or is it the *host application's* responsibility? The library provides `BodyGunzipWithLimit` specifically for cases where the user wants to limit the decompressed size. The fact that the convenience method `BodyGunzip` does *not* apply a limit is a design choice (to avoid surprising users who want to decompress large legitimate files) rather than a security flaw in the library's *parsing* logic. The library does not *force* the user to use `BodyGunzip`; it provides both limited and unlimited variants. \n\nFurthermore, the threat model says: \"Missing parameter validation in non-public... functions that are not directly reachable from outside when the public boundary already enforces the invariant\" is NOT a vulnerability. Here, `BodyGunzip` is public, but it is not the *entry point* for receiving data; it operates on data *already* in the `Request`/`Response` struct. The entry point for *receiving* data is `ReadLimitBody`. If the host app calls `ReadLimitBody(r, 1024*1024)`, the raw body is limited to 1MB. If that 1MB is a gzip bomb, it *could* expand to GBs. But the library *does* provide `BodyGunzipWithLimit`. The ",
"elapsed": 271.2,
"tokens": 26366,
"tokens_in": 25631,
"tokens_out": 735,
"cost_usd": 0.01297002,
"file": "http.go",
"round": 3
},
{
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"verdict": "VALID",
"reasoning": "The report correctly identifies that `BodyGunzip()`, `BodyInflate()`, `BodyUnbrotli()`, `BodyUnzstd()`, and `BodyUncompressed()` are public API methods that default to `maxBodySize=0`, which disables size limits on decompressed output. The threat model for this library explicitly includes 'resource bounds (unbounded allocation... infinite loops on crafted input)' as a valid asset/impact. These methods are entry points for processing attacker-controlled data (HTTP bodies). While the library provides `WithLimit` variants, the default convenience methods do not enforce a limit, allowing a remote attacker to send a small compressed payload that expands to gigabytes, causing OOM and process termination in the host application. This constitutes a valid security vulnerability under the given threat model because the public API fails to enforce a safe default against a known attack vector (decompression bomb), and the absence of a default limit is a flaw in the library's defense-in-depth strategy for public entry points.\n\nCRUX: Public API methods BodyGunzip, BodyInflate, etc., default to maxBodySize=0, allowing unbounded memory allocation during decompression of attacker-controlled data, leading to OOM.\nGREP: func (req *Request) BodyGunzip()",
"elapsed": 297.9,
"tokens": 26616,
"tokens_in": 26340,
"tokens_out": 276,
"cost_usd": 0.0118908,
"file": "http.go",
"round": 4,
"grep_used": true,
"grep_results": "GREP `func (req *Request) BodyGunzip() (simplified to: BodyGunzip)`:\n```\nfs_test.go:402:\tbody, err = resp.BodyGunzip()\nfs_test.go:914:\tbody, err = resp.BodyGunzip()\nserver_test.go:2454:\tbody, err := resp.BodyGunzip()\nserver_test.go:2477:\tbody, err = resp.BodyGunzip()\nserver_test.go:2560:\tbody, err := resp.BodyGunzip()\nserver_test.go:2587:\tbody, err = resp.BodyGunzip()\nhttp_test.go:379:\trespBody, err := resp.BodyGunzip()\nhttp_test.go:388:\t_, errUnzip := resp.BodyGunzip()\nhttp_test.go:1644:\t\tbody, err = r1.BodyGunzip()\nfs_fs_test.go:218:\tbody, err = resp.BodyGunzip()\nfs_fs_test.go:509:\tbody, err = resp.BodyGunzip()\nfs_fs_test.go:716:\tbody, err = resp.BodyGunzip()\nhttp.go:489:// BodyGunzip returns un-gzipped body data.\nhttp.go:494:func (req *Request) BodyGunzip() ([]byte, error) {\nhttp.go:495:\treturn req.BodyGunzipWithLimit(0)\nhttp.go:498:// BodyGunzipWithLimit returns un-gzipped body data and limits the size\nhttp.go:502:func (req *Request) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\nhttp.go:506:// BodyGunzip returns un-gzipped body data.\nhttp.go:511:func (resp *Response) BodyGunzip() ([]byte, error) {\nhttp.go:512:\treturn resp.BodyGunzipWithLimit(0)\nhttp.go:515:// BodyGunzipWithLimit returns un-gzipped body data and limits the size\nhttp.go:519:func (resp *Response) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\nhttp.go:678:\t\treturn req.BodyGunzipWithLimit(maxBodySize)\nhttp.go:709:\t\treturn resp.BodyGunzipWithLimit(maxBodySize)\nbrotli_test.go:147:\tbody, err := resp.BodyGunzip()\n```"
},
{
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"verdict": "VALID",
"reasoning": "The report correctly identifies that `BodyGunzip`, `BodyInflate`, `BodyUnbrotli`, `BodyUnzstd`, and `BodyUncompressed` (and their Response counterparts) are public API methods that delegate to `WithLimit` variants with `maxBodySize=0`. The code explicitly states: 'If maxBodySize <= 0, then no limit is applied.' \n\nTo verify the severity, I traced the data flow: `gunzipData` calls `writeGunzip`, which (as verified in similar fasthttp code patterns and implied by the 'WithLimit' existence) copies decompressed data into a `ByteBuffer` using `io.Copy` or similar mechanisms. If `maxBodySize` is 0, no `io.LimitedReader` or equivalent check is applied, allowing the buffer to grow to the size of the decompressed payload. \n\nThe threat model for this library explicitly includes 'resource bounds (unbounded allocation...)' as a valid asset/impact. A decompression bomb (e.g., a ~100-byte gzip payload expanding to GBs) sent by an attacker would cause an Out-Of-Memory (OOM) crash in the host process. \n\nReviewer 3 argued this is a design choice/host responsibility. However, the threat model defines the attacker as code calling the public API with untrusted data. The library provides a `WithLimit` variant but defaults to unsafe behavior in the convenience methods. For a reusable library, failing to provide safe defaults against a well-known DoS vector (decompression bomb) when the alternative (limited) is available constitutes a security vulnerability. The 'absence of defense' rule applies: the bug pattern exists, input is untrusted, and no defense was found in the default path. The fact that the library offers a mitigation does not invalidate the vulnerability in the default path, especially given the 'HIGH' severity rating and the specific inclusion of resource bounds in the threat model.\n\nCRUX: The public API convenience methods for decompression (BodyGunzip, etc.) default to maxBodySize=0, which disables all size limits on decompressed output, allowing an attacker to trigger OOM via a decompression bomb payload.\nGREP: maxBodySize <= 0",
"elapsed": 45.5,
"tokens": 27642,
"tokens_in": 27162,
"tokens_out": 480,
"cost_usd": 0.01284804,
"file": "http.go",
"round": 5,
"grep_used": true,
"grep_results": "GREP `maxBodySize <= 0`:\n```\nfuzz_test.go:66:\t\tif maxBodySize <= 0 {\nfuzz_test.go:85:\t\tif maxBodySize <= 0 {\nfuzz_test.go:219:\t\tif maxBodySize <= 0 {\nserver.go:1123:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:501:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:518:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:544:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:561:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:587:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:604:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:620:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:632:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:670:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:701:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:1106:// If maxBodySize <= 0, then no limit is applied.\nhttp.go:2589:\tif maxBodySize <= 0 {\n```"
},
{
"verdict": "VALID",
"reasoning": "[ARBITER] The public API methods `BodyGunzip`, `BodyInflate`, `BodyUnbrotli`, `BodyUnzstd`, and `BodyUncompressed` (and their Response counterparts) explicitly call their `WithLimit` variants with `maxBodySize=0`. The code documentation and implementation (e.g., `if maxBodySize <= 0 { ... }` checks in internal helpers like `copyZeroAllocWithLimit` or `readBody`) confirm that a limit of 0 disables size enforcement. Since these methods are public entry points that process potentially untrusted compressed data (HTTP bodies), an attacker can send a small compressed payload that decompresses into a very large amount of data, causing unbounded memory allocation and OOM. Under the library threat model, unbounded resource usage (OOM) triggered via public API with crafted input is a valid security vulnerability.",
"round": 6,
"file": "http.go",
"finding_title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"elapsed": 132.2,
"tokens_in": 25614,
"tokens_out": 195,
"cost_usd": 0.01134288
}
]Raw scan report for this file
# Security Analysis: `http.go` (fasthttp)
## Step-by-Step Trace of Untrusted Data Flows
### 1. `readBodyChunked` → `parseChunkSize` → `appendBodyFixedSize` (CRITICAL)
**Flow:** Network bytes → `Transfer-Encoding: chunked` body → `readBodyChunked(r, maxBodySize, dst)` → `parseChunkSize(r)` returns `chunkSize` → `appendBodyFixedSize(r, dst, chunkSize+strCRLFLen)`
**Vulnerability:** `parseChunkSize` calls `readHexInt(r)` to parse the chunk size. If an attacker sends a chunk size hex value that exceeds `MaxInt` (e.g., `ffffffffffffffff`), the integer wraps to a **negative** value. The subsequent check:
```go
if maxBodySize > 0 && len(dst)+chunkSize > maxBodySize {
return dst, ErrBodyTooLarge
}
```
passes because `len(dst) + negative_value` is less than `maxBodySize`. Then:
```go
dst, err = appendBodyFixedSize(r, dst, chunkSize+strCRLFLen)
```
With `chunkSize = -3` (from hex `fffffffffffffffd`), `chunkSize + strCRLFLen = -1`. Inside `appendBodyFixedSize`:
```go
dstLen := offset + n // 0 + (-1) = -1
dst = dst[:dstLen] // PANIC: slice bounds out of range
```
**Impact:** Remote DoS — server process crashes. No authentication required.
---
### 2. `appendBodyFixedSize` accepts negative `n` (CRITICAL)
The function only guards against `n == 0`, not `n < 0`:
```go
func appendBodyFixedSize(r *bufio.Reader, dst []byte, n int) ([]byte, error) {
if n == 0 {
return dst, nil
}
offset := len(dst)
dstLen := offset + n
if cap(dst) < dstLen { // false when dstLen < 0
...
}
dst = dst[:dstLen] // PANIC if dstLen < 0
...
}
```
Any negative `n` causes `dst[:dstLen]` to panic. This is the concrete crash primitive triggered by the chunk-size overflow above.
---
### 3. Decompression Bomb via Default No-Limit Methods (HIGH)
```go
func (req *Request) BodyGunzip() ([]byte, error) {
return req.BodyGunzipWithLimit(0) // 0 = no limit
}
```
All of `BodyGunzip`, `BodyInflate`, `BodyUnbrotli`, `BodyUnzstd`, and `BodyUncompressed` default to `maxBodySize = 0` (unbounded). An attacker sends a ~25-byte gzip body that decompresses to multi-GB. The library allocates the full decompressed size into `bb.B` with no cap → OOM / process kill.
The `WithLimit` variants exist, but the convenience methods that most code uses first are the dangerous ones.
---
### 4. Multipart Form + Gzip with No Size Limit (HIGH)
In `MultipartFormWithLimit(0)` (called by `MultipartForm()`), the streaming + gzip path:
```go
if bytes.Equal(ce, strGzip) {
if bodyStream, err = gzip.NewReader(bodyStream); err != nil { ... }
}
if maxBodySize > 0 {
lr = &io.LimitedReader{ R: bodyStream, N: int64(maxBodySize) + 1 }
bodyStream = lr
}
// maxBodySize == 0 → no LimitedReader
mr := multipart.NewReader(bodyStream, req.multipartFormBoundary)
req.multipartForm, err = mr.ReadForm(8 * 1024)
```
When `maxBodySize <= 0`, the gzip decompressed stream is read **without any size cap**. A crafted `Content-Encoding: gzip` + `Content-Type: multipart/form-data` request with a small compressed payload causes unbounded memory growth in `mr.ReadForm`.
---
### 5. `readBodyIdentity` — Unbounded Allocation when `maxBodySize <= 0` (MEDIUM)
```go
func readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {
...
for {
nn, err := r.Read(dst[offset:])
...
offset += nn
if maxBodySize > 0 && offset > maxBodySize {
return dst[:offset], ErrBodyTooLarge
}
if len(dst) == offset {
n := roundUpForSliceCap(2 * offset)
// no cap when maxBodySize <= 0
b := make([]byte, n)
...
}
}
}
```
When called with `maxBodySize <= 0` (the default via `Request.Read(r)` → `ReadLimitBody(r, 0)`), an attacker who controls connection lifetime (no `Content-Length`, no `Transfer-Encoding`) can send data indefinitely, forcing the server to grow the buffer without bound until OOM.
---
### 6. `ContinueReadBodyStream` — `ErrBodyTooLarge` Leaves Oversized Stream Active (MEDIUM)
```go
if err == ErrBodyTooLarge {
req.Header.SetContentLength(contentLength) // original, oversized value
req.body = bodyBuf
req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)
return nil // no error to caller!
}
```
When the pre-read exceeds `maxBodySize`, the function **silently succeeds** and hands back a stream that will continue reading the full (oversized) body. The caller receives `nil` error and a `Content-Length` that exceeds the configured limit. If downstream code trusts `Content-Length` for buffer sizing, this can lead to unexpected large allocations.
---
## JSON Findings
```json
[
{
"severity": "critical",
"title": "Integer overflow in chunked Transfer-Encoding chunk size causes panic (remote DoS)",
"function": "readBodyChunked",
"description": "parseChunkSize() calls readHexInt() which can overflow Go's signed int when the attacker supplies a 16-hex-digit chunk size >= 0x8000000000000000. The resulting negative chunkSize bypasses the 'len(dst)+chunkSize > maxBodySize' check (negative + small <= max), then appendBodyFixedSize() is called with chunkSize+strCRLFLen. When the sum is negative (e.g. hex 'fffffffffffffffd' → -3 + 2 = -1), appendBodyFixedSize executes 'dst = dst[:dstLen]' with a negative dstLen, panicking with 'slice bounds out of range' and crashing the server process. Triggerable by any client sending a chunked body with a crafted chunk size; no authentication required."
},
{
"severity": "critical",
"title": "appendBodyFixedSize lacks negative-size guard, panics on negative n",
"function": "appendBodyFixedSize",
"description": "The function only returns early when n == 0 but does not validate n < 0. A negative n produces a negative dstLen (offset + n), and the subsequent 'dst = dst[:dstLen]' panics. This is the crash primitive reached via the chunk-size integer overflow in readBodyChunked, and could also be triggered by any other code path that passes an unvalidated negative size (e.g. an overflowed Content-Length if header parsing is bypassed)."
},
{
"severity": "high",
"title": "Decompression bomb via default no-limit BodyGunzip/BodyInflate/BodyUnbrotli/BodyUnzstd/BodyUncompressed",
"function": "BodyGunzip",
"description": "BodyGunzip(), BodyInflate(), BodyUnbrotli(), BodyUnzstd(), and BodyUncompressed() all call their WithLimit counterparts with maxBodySize=0, meaning no cap on decompressed output. An attacker sends a minimal gzip/deflate/brotli/zstd payload (~100 bytes) that decompresses to several GB. gunzipData/inflateData/etc. allocate the full decompressed size into a ByteBuffer with no bound, causing OOM and process termination. The convenience (no-limit) methods are the most commonly called entry points, making this the default behavior for any application that does not explicitly use the WithLimit variants."
},
{
"severity": "high",
"title": "Unbounded gzip decompression in MultipartFormWithLimit(0) streaming path",
"function": "MultipartFormWithLimit",
"description": "When a multipart/form-data request has Content-Encoding: gzip and arrives as a body stream, MultipartFormWithLimit(0) (the default from MultipartForm()) wraps the stream in gzip.NewReader but only applies an io.LimitedReader when maxBodySize > 0. With the default limit of 0, the decompressed stream is passed to multipart.NewReader().ReadForm() without any size cap. A small gzipped body containing a high-ratio gzip bomb causes unbounded memory allocation during multipart parsing, leading to OOM/DoS."
},
{
"severity": "medium",
"title": "readBodyIdentity allows unbounded body growth when maxBodySize <= 0",
"function": "readBodyIdentity",
"description": "Called with maxBodySize=0 (the default from Request.Read / ReadLimitBody(r, 0)) for requests without Content-Length or Transfer-Encoding, the function reads from the network until EOF with no size cap. The buffer doubles each time it fills (roundUpForSliceCap(2 * offset)). An attacker who controls connection lifetime (e.g. a slow drip over a long-lived TCP connection) forces the server to grow the body buffer without bound until OOM. No per-request size limit is enforced."
},
{
"severity": "medium",
"title": "ContinueReadBodyStream silently returns oversized body stream on ErrBodyTooLarge",
"function": "ContinueReadBodyStream",
"description": "When readBodyWithStreaming detects that contentLength exceeds maxBodySize, the function returns nil (no error) and sets req.bodyStream to a requestStream that will continue reading the full oversized body from the network. The Content-Length header is set to the original (oversized) value. Downstream code that uses Content-Length to pre-allocate buffers or that assumes the body is within the configured limit will process an unbounded amount of data, defeating the purpose of the size limit."
}
]
```