MuntashirAkon/AppManager · error · IOException

Block overrun while expanding RLE in MTF, " + lastShadow + "

Error message

Block overrun while expanding RLE in MTF, " + lastShadow + " exceeds " + limitLast

What it means

While reversing the MTF/RLE stage of a bzip2 block, the decoder expands run-length entries into the ll8 buffer. If the expanded position (lastShadow) passes the block's declared limit (limitLast), the block's uncompressed length is inconsistent with its contents, i.e. corrupted input, and decoding stops.

Source

Thrown at app/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java:654

                    }
                    final int tmp = zvec - base_zt[zn];
                    checkBounds(tmp, MAX_ALPHA_SIZE, "zvec");
                    nextSym = perm_zt[tmp];
                }
                checkBounds(s, this.data.ll8.length, "s");

                final int yy0 = yy[0];
                checkBounds(yy0, 256, "yy");
                final byte ch = seqToUnseq[yy0];
                unzftab[ch & 0xff] += s + 1;

                final int from = ++lastShadow;
                lastShadow += s;
                checkBounds(lastShadow, this.data.ll8.length, "lastShadow");
                Arrays.fill(ll8, from, lastShadow + 1, ch);

                if (lastShadow >= limitLast) {
                    throw new IOException("Block overrun while expanding RLE in MTF, "
                            + lastShadow + " exceeds " + limitLast);
                }
            } else {
                if (++lastShadow >= limitLast) {
                    throw new IOException("Block overrun in MTF, "
                            + lastShadow + " exceeds " + limitLast);
                }
                checkBounds(nextSym, 256 + 1, "nextSym");

                final char tmp = yy[nextSym - 1];
                checkBounds(tmp, 256, "yy");
                unzftab[seqToUnseq[tmp] & 0xff]++;
                ll8[lastShadow] = seqToUnseq[tmp];

                /*
                 * This loop is hammered during decompression, hence avoid
                 * native method call overhead of System.arraycopy for very
                 * small ranges to copy.

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Treat the archive as corrupt: verify with `bzip2 -t` and re-obtain the file.
  2. Ensure the whole stream (not a partial copy) is supplied to the decompressor.
  3. Catch IOException and report/skip the corrupt block instead of retrying the same bytes.
  4. If inputs are untrusted, run decompression with limits (size/time caps) since such inputs may be hostile.

Example fix

// before
IOUtils.copy(bz, out); // unbounded, throws raw IOException on corrupt block
// after
try {
    IOUtils.copy(bz, LimitedOutputStream.bounded(out, maxBytes));
} catch (IOException e) {
    throw new CorruptArchiveException("bzip2 block overrun: " + e.getMessage(), e);
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    IOUtils.copy(bz, boundedOut);
} catch (IOException e) {
    if (e.getMessage().contains("Block overrun"))
        throw new CorruptArchiveException("bzip2 block overrun: " + e.getMessage(), e);
    throw e;
}

Prevention

When it happens

Trigger: BZip2CompressorInputStream.read() during block decoding: an RLE run symbol pushes lastShadow beyond limitLast after checkBounds(lastShadow, ll8.length, ...) has passed.

Common situations: Corrupted or truncated archives, crafted inputs claiming shorter blocks than they decode to, wrong bytes fed to the decompressor.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/078211a88e05a787. Report an issue: GitHub.