MuntashirAkon/AppManager · error · IOException

Corrupted input, nSelectors value negative

Error message

Corrupted input, nSelectors value negative

What it means

recvDecodingTables reads the 15-bit nSelectors count from the block header. Valid bzip2 data always has a non-negative nSelectors; a negative value means the bitstream is corrupt, so the decompressor aborts with this explicit message instead of allocating absurd memory or mis-decoding.

Source

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

        Arrays.fill(inUse, false);
        for (int i = 0; i < 16; i++) {
            if ((inUse16 & (1 << i)) != 0) {
                final int i16 = i << 4;
                for (int j = 0; j < 16; j++) {
                    if (bsGetBit(bin)) {
                        inUse[i16 + j] = true;
                    }
                }
            }
        }

        makeMaps();
        final int alphaSize = this.nInUse + 2;
        /* Now the selectors */
        final int nGroups = bsR(bin, 3);
        final int selectors = bsR(bin, 15);
        if (selectors < 0) {
            throw new IOException("Corrupted input, nSelectors value negative");
        }
        checkBounds(alphaSize, MAX_ALPHA_SIZE + 1, "alphaSize");
        checkBounds(nGroups, N_GROUPS + 1, "nGroups");

        // Don't fail on nSelectors overflowing boundaries but discard the values in overflow
        // See https://gnu.wildebeest.org/blog/mjw/2019/08/02/bzip2-and-the-cve-that-wasnt/
        // and https://sourceware.org/ml/bzip2-devel/2019-q3/msg00007.html

        for (int i = 0; i < selectors; i++) {
            int j = 0;
            while (bsGetBit(bin)) {
                j++;
            }
            if (i < MAX_SELECTORS) {
                selectorMtf[i] = (byte) j;
            }
        }
        final int nSelectors = selectors > MAX_SELECTORS ? MAX_SELECTORS : selectors;

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Validate the source archive (`bzip2 -t`) and get a clean copy.
  2. Confirm the InputStream position: don't start decompression mid-file or after full consumption.
  3. Catch IOException around read loops and surface a user-facing 'corrupt archive' error.
  4. Check storage/network for bit-rot if corruption recurs on many files.

Example fix

// before
while ((n = bz.read(buf)) >= 0) out.write(buf, 0, n); // raw IOException escapes
// after
try {
    while ((n = bz.read(buf)) >= 0) out.write(buf, 0, n);
} catch (IOException e) {
    throw new CorruptArchiveException("bad bzip2 data: " + e.getMessage(), e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

Process p = new ProcessBuilder("bzip2", "-t", path.toString()).redirectErrorStream(true).start();
if (p.waitFor() != 0) throw new IOException("archive failed integrity check: " + path);

Try / catch

try (BZip2CompressorInputStream bz = new BZip2CompressorInputStream(in)) {
    IOUtils.copy(bz, out);
} catch (IOException e) {
    throw new CorruptArchiveException("nSelectors/block-header corruption: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: getAndMoveToFrontDecode -> recvDecodingTables encounters a corrupted block header while BZip2CompressorInputStream is reading a block; the 15-bit selector count decodes negative.

Common situations: Truncated or corrupted .bz2 files, non-bzip2 bytes fed into the stream, transfer corruption, security fuzzing inputs.

Related errors


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