Denial of service from decompression bombs

Unbounded resource consumption during decompression

Description

Copying a decompression reader's output without a size limit can expand a small input into enough data to exhaust CPU, memory or disk resources.

Potential impact

  • The server may exhaust resources or its process may fail.
  • Upload or archive-processing functions may become a denial-of-service entry point.

Remediation

  • Limit actual decompressed output and reject data that exceeds the limit. io.LimitReader returns EOF when its limit is reached, so detect an oversized result separately.
  • Count bytes actually read instead of trusting only the declared uncompressed size. Also limit total archive output, entry count and processing time.
  • Handle reader creation and copy errors, and discard partial output from failed operations.

Examples

These are excerpts from a function that returns an error. src is an io.Reader, and dst is temporary output; the code uses compress/gzip, io and fmt. The caller must discard partial output on error. The example limit is 10 MiB, with at most one extra byte read to detect overflow of that limit.

Before

go
r, _ := gzip.NewReader(src)
_, _ = io.Copy(dst, r)

After

go
const maxExpanded int64 = 10 << 20
r, err := gzip.NewReader(src)
if err != nil {
    return err
}
defer r.Close()
n, err := io.Copy(dst, io.LimitReader(r, maxExpanded+1))
if err != nil {
    return err
}
if n > maxExpanded {
    return fmt.Errorf("expanded data exceeds limit")
}
return nil

Explanation:

  • Before: Copying a decompression reader to completion with unbounded io.Copy or io.CopyBuffer may turn a small input into output that exhausts CPU, memory or disk resources.
  • After: The code handles errors and checks the actual output size. Exceeding the limit returns an error, after which the caller must discard the temporary output.

References