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.LimitReaderreturns 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.Copyorio.CopyBuffermay 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.