{"record":{"id":"c4ba9fd08a42e477","repo":"elastic/elasticsearch","slug":"too-large-matchlen","errorCode":null,"errorMessage":"Too large matchLen","messagePattern":"Too large matchLen","errorType":"exception","errorClass":"LZ4Exception","httpStatus":null,"severity":"error","filePath":"libs/lz4/src/main/java/org/elasticsearch/lz4/ESLZ4Decompressor.java","lineNumber":134,"sourceCode":"            sOff += literalLen;\n            dOff = literalCopyEnd;\n\n            // matchs\n            final int matchDec = SafeUtils.readShortLE(src, sOff);\n            sOff += 2;\n            int matchOff = dOff - matchDec;\n\n            if (matchOff < destOff) {\n                throw new LZ4Exception(\"Malformed input at \" + sOff);\n            }\n\n            int matchLen = token & LZ4Constants.ML_MASK;\n            if (matchLen == LZ4Constants.ML_MASK) {\n                byte len = (byte) 0xFF;\n                while (sOff < srcEnd && (len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) {\n                    matchLen += 0xFF;\n                    if (matchLen < 0) {\n                        throw new LZ4Exception(\"Too large matchLen\");\n                    }\n                }\n                matchLen += len & 0xFF;\n            }\n            matchLen += LZ4Constants.MIN_MATCH;\n\n            final int matchCopyEnd = dOff + matchLen;\n            // Check for overflow\n            if (matchCopyEnd < dOff) {\n                throw new LZ4Exception(\"Too large matchLen\");\n            }\n\n            if (matchDec == 0) {\n                if (matchCopyEnd > destEnd) {\n                    throw new LZ4Exception(\"Malformed input at \" + sOff);\n                }\n                // With matchDec == 0, matchOff == dOff, so we'd copy in place. Zero the data instead. (CVE-2025-66566)\n                assert matchOff == dOff; // should always hold, but this extra check will trigger during fuzzing if my logic is wrong","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/lz4/src/main/java/org/elasticsearch/lz4/ESLZ4Decompressor.java#L116-L152","documentation":"When the match-length nibble equals ML_MASK (15), the decoder reads 0xFF extension bytes adding 255 each. If matchLen overflows int (goes negative), the declared match length is implausibly large and the stream is malformed. This bounds the variable-length match encoding symmetrically with the literal-length check.","triggerScenarios":"Decompressing a block whose match-length extension bytes sum beyond Integer.MAX_VALUE - i.e. an unbounded run of 0xFF before the terminating length byte in the match section. Indicates crafted or heavily corrupt input.","commonSituations":"Fuzzed/adversarial LZ4 input. Memory corruption overwriting match-length bytes with 0xFF. Incompatible compressor output.","solutions":["Discard the corrupt block; recover from a known-good source.","Validate the compressor that produced the input.","If recurring, audit the storage path for corruption."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Heuristic upper bound on match length vs destLen\nif (destLen < 0 || destLen > src.length * 255L) {\n    throw new IllegalArgumentException(\"Implausible destLen for match-length bounds\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    ESLZ4Decompressor.INSTANCE.decompress(src, srcOff, dest, destOff, destLen);\n} catch (LZ4Exception e) {\n    markCorruptAndRecover();\n}","preventionTips":["Reject implausible destLen before decompression.","Treat match-length overflow as corrupt/adversarial input.","Recover from a known-good source on any LZ4Exception."],"tags":["lz4","decompression","corruption","overflow","match-length"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}