{"record":{"id":"cbd9b35ab49719fe","repo":"Tencent/tinker","slug":"corrupt-by-wrong-patch-file","errorCode":null,"errorMessage":"Corrupt by wrong patch file.","messagePattern":"Corrupt by wrong patch file\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java","lineNumber":347,"sourceCode":"        in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE);\n        InputStream extraBlockIn = new GZIPInputStream(in);\n\n        // byte[] newBuf = new byte[newsize + 1];\n        byte[] newBuf = new byte[newsize];\n\n        int oldpos = 0;\n        int newpos = 0;\n        int[] ctrl = new int[3];\n\n        // int nbytes;\n        while (newpos < newsize) {\n\n            for (int i = 0; i <= 2; i++) {\n                ctrl[i] = ctrlBlockIn.readInt();\n            }\n\n            if (newpos + ctrl[0] > newsize) {\n                throw new IOException(\"Corrupt by wrong patch file.\");\n            }\n\n            // Read ctrl[0] bytes from diffBlock stream\n            if (!BSUtil.readFromStream(diffBlockIn, newBuf, newpos, ctrl[0])) {\n                throw new IOException(\"Corrupt by wrong patch file.\");\n            }\n\n            for (int i = 0; i < ctrl[0]; i++) {\n                if ((oldpos + i >= 0) && (oldpos + i < oldsize)) {\n                    newBuf[newpos + i] += oldBuf[oldpos + i];\n                }\n            }\n\n            newpos += ctrl[0];\n            oldpos += ctrl[0];\n\n            if (newpos + ctrl[1] > newsize) {\n                throw new IOException(\"Corrupt by wrong patch file.\");","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java#L329-L365","documentation":"BSPatch.apply streams control triples (add-length, copy-length, seek) and applies them to produce the new file. The first control value ctrl[0] says how many bytes the diff block contributes at the current new-file position; if newpos + ctrl[0] would overrun newsize, the patch's control stream disagrees with the declared output size, so the patch cannot be applied to this input. This is a hard integrity check that prevents writing past the end of the output buffer.","triggerScenarios":"Calling BSPatch.patch (or its variants) where the control triple's first entry would push newpos beyond newsize: a patch file truncated or byte-corrupted in its control block, a patch applied to an old input that is not the exact base it was diffed against, or a patch generated by an incompatible bsdiff version/format.","commonSituations":"OTA/patch applied to the wrong base APK version; patch file corrupted during download (partial transfer, proxy truncation); MD5 of the patch not verified before apply; mixing bsdiff/bspatch builds with different control-block endianness or header layouts.","solutions":["Verify the old file's digest matches the digest recorded when the patch was created (tinker patches carry old-file MD5s); if not, fetch or locate the correct base version.","Verify the patch file's MD5/SHA against the value from the server before calling BSPatch; re-download on mismatch.","Regenerate the patch from the exact old/new pair with the same bsdiff/bspatch implementation pair.","If corruption recurs, check storage/transport: disk full, interrupted download, or a proxy mangling binary payloads."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify old-file and patch digests before applying a bsdiff patch\nboolean safeToPatch(java.io.File oldFile, java.io.File patchFile,\n                    String expectedOldMd5, String expectedPatchMd5) throws Exception {\n    return md5Of(oldFile).equals(expectedOldMd5)\n        && md5Of(patchFile).equals(expectedPatchMd5);\n}\nString md5Of(java.io.File f) throws Exception {\n    java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n    try (java.io.InputStream in = new java.io.FileInputStream(f)) {\n        byte[] buf = new byte[8192]; int n;\n        while ((n = in.read(buf)) > 0) md.update(buf, 0, n);\n    }\n    StringBuilder sb = new StringBuilder();\n    for (byte b : md.digest()) sb.append(String.format(\"%02x\", b));\n    return sb.toString();\n}","typeGuard":null,"tryCatchPattern":"try {\n    byte[] out = com.tencent.tinker.bsdiff.BSPatch.patch(oldData, newDataLen, patchStream);\n} catch (java.io.IOException e) {\n    if (\"Corrupt by wrong patch file.\".equals(e.getMessage())) {\n        // do NOT retry with the same inputs; re-verify digests, re-download patch or correct base file\n        throw new IllegalStateException(\"patch/base mismatch: verify old-file and patch MD5s\", e);\n    }\n    throw e;\n}","preventionTips":["Ship and check MD5s for both the base file and the patch before apply.","Download patches to a temp file, verify size and hash, then atomically rename.","Never re-sign or re-zipalign the base APK after the patch is generated — bytes must stay identical."],"tags":["bsdiff","patch","integrity","tinker"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}