{"record":{"id":"8a3f56333c07d4f9","repo":"iBotPeaches/Apktool","slug":"stream-advanced-past-chunk-end","errorCode":null,"errorMessage":"Stream advanced past chunk end.","messagePattern":"Stream advanced past chunk end\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResChunkPullParser.java","lineNumber":161,"sourceCode":"            mChunkOffset = OFFSET_ENDED;\n            return false;\n        } catch (IOException ex) {\n            throw new IOException(\"Error while reading chunk header.\", ex);\n        }\n    }\n\n    public int skipChunk() throws IOException {\n        if (mChunkHeader == null) {\n            throw new IllegalStateException();\n        }\n        try {\n            long position = mIn.position();\n            long chunkEnd = chunkEnd();\n            if (position == chunkEnd) {\n                return 0;\n            }\n            if (position > chunkEnd) {\n                throw new IOException(\"Stream advanced past chunk end.\");\n            }\n            return mIn.skipBytes((int) (chunkEnd - position));\n        } catch (EOFException ignored) {\n            throw new EOFException(\"Unexpected EOF while skipping chunk.\");\n        } catch (IOException ex) {\n            throw new IOException(\"Error while skipping chunk.\", ex);\n        }\n    }\n\n    public int skipHeader() throws IOException {\n        if (mChunkHeader == null) {\n            throw new IllegalStateException();\n        }\n        try {\n            long position = mIn.position();\n            long headerEnd = headerEnd();\n            if (position == headerEnd) {\n                return 0;","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/iBotPeaches/Apktool/blob/79b63384d7d7e22917e6ea8b453272da7012515b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResChunkPullParser.java#L143-L179","documentation":"Thrown by ResChunkPullParser.skipChunk() when the underlying stream position is already beyond the current chunk's declared end (chunkOffset + header.size). The parser tracks chunk boundaries from each chunk header's size field; if the caller has read past that boundary (or the size field itself is corrupt/too small), skipping is impossible. Note that this IOException is thrown inside the method's own try block, so in practice it surfaces wrapped as \"Error while skipping chunk.\" (error 42) with this message as the cause.","triggerScenarios":"Calling next() after manually reading more bytes from parser.stream() than the chunk contained; a chunk header in resources.arsc or a binary XML file declaring a size smaller than the data a nested parser consumed; obfuscated/packed APKs with hand-crafted chunk sizes; calling skipChunk() twice without an intervening next().","commonSituations":"Decoding obfuscated or resource-shrunk APKs whose arsc chunk size fields were rewritten incorrectly; custom code that interleaves direct BinaryDataInputStream reads with the pull parser; truncated then re-padded files where sizes no longer align.","solutions":["If you control the reading loop, never read from parser.stream() past a chunk's data: consume exactly dataSize() bytes before calling next().","Verify the chunk's declared size: chunkSize() must be >= headerSize() and chunkStart() + chunkSize() must not exceed the file size; treat violations as a corrupt file.","Regenerate or re-obtain the APK (re-download, unzip fully); corruption often comes from interrupted transfers.","If parsing untrusted APKs, catch IOException around the next()/skipChunk() loop and report the resource file as undecodable instead of crashing."],"exampleFix":"// before\nwhile (parser.next()) {\n    // read body manually with extra bytes ...\n    in.readBytes(1024); // reads past chunk end\n}\n\n// after\nwhile (parser.next()) {\n    long remaining = parser.chunkEnd() - in.position();\n    // consume at most `remaining` bytes, then let next() skip the rest\n}","handlingStrategy":"validation","validationCode":"// Before next()/skipChunk(), ensure we have not overrun the current chunk\nif (parser.isChunk()) {\n    long position = parser.stream().position();\n    long chunkEnd = parser.chunkStart() + parser.chunkSize();\n    if (position > chunkEnd) {\n        throw new IOException(\"Bug: read past chunk end by \" + (position - chunkEnd) + \" bytes\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    while (parser.next()) { /* ... */ }\n} catch (IOException ex) {\n    if (ex.getMessage() != null && ex.getMessage().contains(\"chunk end\")) {\n        // chunk-size metadata vs. reads mismatch -> treat file/loop as broken\n    }\n    throw ex;\n}","preventionTips":["Never read from parser.stream() beyond dataSize()/chunkEnd() for the current chunk","Consume chunk bodies exactly; let next() do the skipping","Validate chunkSize() >= headerSize() and chunk boundaries against total file size before trusting them"],"tags":["android","apktool","binary-parsing","resource-table","corruption"],"backgroundTag":null,"analyzedSha":"79b63384d7d7e22917e6ea8b453272da7012515b","analyzedAt":"2026-08-14T10:43:28.812Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}