NationalSecurityAgency/ghidra · error · IOException

Decompression limit exceeded: {}

Error message

Decompression limit exceeded: {}

What it means

RestrictedInflaterInputStream wraps an inflater (zlib/deflate) with a hard readLimit to prevent decompression bombs. Each read() increments readCount and, if readCount already meets readLimit, throws before reading more. It is a deliberate safety cap supplied to the constructor (RestrictedInflaterInputStream(file, readLimit) or (byte[], readLimit)).

Source

Thrown at GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java:310

			try {
				super.close();
			}
			finally {
				// Cleanup temporary file input stream and remove file
				in.close();
				if (tempCompressedFile != null) {
					tempCompressedFile.delete();
				}
			}
		}

		@Override
		public int read(byte[] b, int off, int length) throws IOException {
			if (length == 0) {
				return 0;
			}
			if (readCount >= readLimit) {
				throw new IOException("Decompression limit exceeded: " + readLimit);
			}
			// Limit read length to avoid exceeding readLimit
			int limit = Math.min(readLimit - readCount, length);
			int count = super.read(b, off, limit);
			if (count > 0) {
				readCount += count;
			}
			return count;
		}
	}

	public List<String> getInfo(String path) {
		if (path != null) {
			DmgInfoGenerator info = new DmgInfoGenerator(this, path, parser);
			return info.getInformation();
		}
		return null;
	}

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Raise readLimit to comfortably exceed the expected inflated size of the DMG block being processed.
  2. Verify the DMG block is not a decompression bomb by comparing its compressed size to the declared uncompressed size before decompressing.
  3. Confirm the readLimit is being passed in bytes (not a smaller unit) and matches the block's declared output size.
  4. If the limit is intentional policy, treat this as expected behavior and abort the untrusted image.

Example fix

// before
new RestrictedInflaterInputStream(compressedBlock, 1024 * 1024);

// after - size the limit to the declared uncompressed block size
int limit = Math.toIntExact(Math.addExact(declaredUncompressedSize, safetyMargin));
new RestrictedInflaterInputStream(compressedBlock, limit);
Defensive patterns

Strategy: validation

Validate before calling

// Size the decompression cap to the declared uncompressed block size plus margin
int safeLimit = Math.toIntExact(Math.addExact(declaredUncompressedSize, safetyMarginBytes));
if (safeLimit <= 0) {
    throw new IOException("Invalid decompression limit derived from declared size " + declaredUncompressedSize);
}
new RestrictedInflaterInputStream(compressedBlock, safeLimit);

Type guard

boolean limitCoversExpected(int readLimit, long declaredUncompressed) {
    return readLimit > 0 && ((long) readLimit) >= declaredUncompressed;
}

Try / catch

try {
    int n = stream.read(buf, off, len);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Decompression limit exceeded")) {
        // Treat as policy/security abort for untrusted input; do not silently raise the cap
        throw new IOException("Decompression bomb or unexpectedly large block; aborting", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Decompressing a DMG block whose inflated output exceeds the readLimit passed when constructing RestrictedInflaterInputStream. The limit bounds total bytes read from the inflated stream, so a genuinely large (or malicious zip-bomb-like) block trips it.

Common situations: A legitimate but large compressed block whose decompressed size exceeds the configured cap; a malicious/adversarial DMG crafted as a decompression bomb; the readLimit was set too low for the expected image size; counting logic accumulating more than expected due to repeated reads.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/0666b190a473e394. Report an issue: GitHub.