skylot/jadx · error · IllegalStateException

Read limit exceeded

Error message

Read limit exceeded

What it means

A deliberate security guard. LimitedInputStream counts every byte read and throws IllegalStateException once currentPos exceeds maxSize, where maxSize is the entry's declared uncompressed size. It is an anti-zip-bomb measure: if the real decompressed content is larger than the header claims, reading stops immediately. Because it is an unchecked exception it propagates out of read()/read(byte[]) and up through getBytes()/getInputStream() consumers.

Source

Thrown at jadx-commons/jadx-zip/src/main/java/jadx/zip/io/LimitedInputStream.java:21

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LimitedInputStream extends FilterInputStream {
	private final long maxSize;

	private long currentPos;
	private long markPos;

	public LimitedInputStream(InputStream in, long maxSize) {
		super(in);
		this.maxSize = maxSize;
	}

	private void addAndCheckPos(long count) {
		currentPos += count;
		if (currentPos > maxSize) {
			throw new IllegalStateException("Read limit exceeded");
		}
	}

	@Override
	public int read() throws IOException {
		int data = super.read();
		if (data != -1) {
			addAndCheckPos(1);
		}
		return data;
	}

	@SuppressWarnings("NullableProblems")
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int count = super.read(b, off, len);
		if (count > 0) {
			addAndCheckPos(count);

View on GitHub (pinned to e738a26571)

Solutions

  1. Treat this as evidence of a tampered or hostile archive; do not trust the entry's data.
  2. Verify the archive against a known-good checksum and re-download if it mismatches.
  3. If you control the source and the size header is simply wrong, repackage the zip with a correct uncompressed size.
  4. Catch IllegalStateException at the call site and skip the entry rather than aborting the whole archive.

Example fix

// before
byte[] bytes = zipEntry.getBytes();

// after
byte[] bytes;
try {
    bytes = zipEntry.getBytes();
} catch (IllegalStateException limit) {
    if ("Read limit exceeded".equals(limit.getMessage())) {
        LOG.warn("Entry {} exceeds declared size (possible zip bomb), skipping", zipEntry.getName());
        continue;
    }
    throw limit;
}
Defensive patterns

Strategy: validation

Validate before calling

// The limit equals the entry's declared uncompressed size.
// If you can read the header, sanity-check the declared size before reading:
long declared = entry.getUncompressedSize();
if (declared < 0 || declared > MAX_ACCEPTABLE_ENTRY_SIZE) {
    LOG.warn("Entry {} declares suspicious uncompressed size {}, skipping", entry.getName(), declared);
    return;
}

Try / catch

try {
    bytes = entry.getBytes();
} catch (IllegalStateException e) {
    if ("Read limit exceeded".equals(e.getMessage())) {
        LOG.warn("Possible zip bomb in entry {}, skipping", entry.getName());
        continue;
    }
    throw e;
}

Prevention

When it happens

Trigger: Reading from an InputStream produced by FallbackZipParser when zipSecurity.useLimitedDataStream() is enabled, for an entry whose actual decompressed byte count exceeds entry.getUncompressedSize(). Any single-byte or bulk read past the limit trips addAndCheckPos().

Common situations: A crafted zip-bomb whose compressed data decompresses to more bytes than the (spoofed) uncompressed-size field declares; a legitimately corrupt entry with a wrong size header; an obfuscator/packer that writes inaccurate uncompressed sizes. Also reached during jadx CLI/GUI processing of untrusted APKs where the size limit is on by default.

Related errors


AI-assisted analysis of skylot/jadx@e738a26571 (2026-08-14). Data as JSON: /api/errors/04bdcd00324dacab. Report an issue: GitHub.