iBotPeaches/Apktool · error · IOException

Stream advanced past chunk header end.

Error message

Stream advanced past chunk header end.

What it means

Thrown by ResChunkPullParser.skipHeader() when the stream position is already past the chunk header's end (chunkOffset + header.headerSize). This means the caller consumed part or all of the header region before asking to skip it — the parser cannot skip backwards. Like error 40, this throw occurs inside the method's try block and is re-wrapped as 'Error while skipping chunk header.' (error 45) in practice.

Source

Thrown at brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResChunkPullParser.java:182

        } catch (EOFException ignored) {
            throw new EOFException("Unexpected EOF while skipping chunk.");
        } catch (IOException ex) {
            throw new IOException("Error while skipping chunk.", ex);
        }
    }

    public int skipHeader() throws IOException {
        if (mChunkHeader == null) {
            throw new IllegalStateException();
        }
        try {
            long position = mIn.position();
            long headerEnd = headerEnd();
            if (position == headerEnd) {
                return 0;
            }
            if (position > headerEnd) {
                throw new IOException("Stream advanced past chunk header end.");
            }
            return mIn.skipBytes((int) (headerEnd - position));
        } catch (EOFException ignored) {
            throw new EOFException("Unexpected EOF while skipping chunk header.");
        } catch (IOException ex) {
            throw new IOException("Error while skipping chunk header.", ex);
        }
    }
}

View on GitHub (pinned to 79b63384d7)

Solutions

  1. Do not read header bytes from stream() yourself; use the parser's accessors and call skipHeader() exactly once per chunk.
  2. Check in.position() < parser.headerEnd() before calling skipHeader().
  3. Validate headerSize >= ResChunkHeader.SIZE (8) — next() already rejects headers smaller than that, so a smaller value indicates tampering.

Example fix

// before
int type = in.readShort(); // consumed part of the header
parser.skipHeader();       // throws: position > headerEnd

// after
int type = parser.chunkType(); // accessor, no stream consumption
parser.skipHeader();
Defensive patterns

Strategy: validation

Validate before calling

// Only skip a header we have not started reading
if (parser.isChunk() && parser.stream().position() < parser.chunkStart() + parser.headerSize()) {
    parser.skipHeader();
} else {
    throw new IllegalStateException("Header already (partially) consumed");
}

Try / catch

try {
    parser.skipHeader();
} catch (IOException ex) {
    // header overrun or I/O failure; check cause chain and abort chunk parsing
}

Prevention

When it happens

Trigger: Reading header fields manually from parser.stream() and then calling skipHeader(); calling skipHeader() twice for the same chunk; a chunk header whose headerSize field is inconsistent with what was actually read.

Common situations: Custom parsers that inspect the raw header bytes (type, headerSize, size) directly instead of using the accessors (chunkType(), headerSize(), chunkSize()); obfuscated APKs with malformed headerSize values.

Related errors


AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14). Data as JSON: /api/errors/b8199e2003a7a289. Report an issue: GitHub.