elastic/elasticsearch · error · LZ4Exception

Too large matchLen

Error message

Too large matchLen

What it means

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.

Source

Thrown at libs/lz4/src/main/java/org/elasticsearch/lz4/ESLZ4Decompressor.java:134

            sOff += literalLen;
            dOff = literalCopyEnd;

            // matchs
            final int matchDec = SafeUtils.readShortLE(src, sOff);
            sOff += 2;
            int matchOff = dOff - matchDec;

            if (matchOff < destOff) {
                throw new LZ4Exception("Malformed input at " + sOff);
            }

            int matchLen = token & LZ4Constants.ML_MASK;
            if (matchLen == LZ4Constants.ML_MASK) {
                byte len = (byte) 0xFF;
                while (sOff < srcEnd && (len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) {
                    matchLen += 0xFF;
                    if (matchLen < 0) {
                        throw new LZ4Exception("Too large matchLen");
                    }
                }
                matchLen += len & 0xFF;
            }
            matchLen += LZ4Constants.MIN_MATCH;

            final int matchCopyEnd = dOff + matchLen;
            // Check for overflow
            if (matchCopyEnd < dOff) {
                throw new LZ4Exception("Too large matchLen");
            }

            if (matchDec == 0) {
                if (matchCopyEnd > destEnd) {
                    throw new LZ4Exception("Malformed input at " + sOff);
                }
                // With matchDec == 0, matchOff == dOff, so we'd copy in place. Zero the data instead. (CVE-2025-66566)
                assert matchOff == dOff; // should always hold, but this extra check will trigger during fuzzing if my logic is wrong

View on GitHub (pinned to db6a809a66)

Solutions

  1. Discard the corrupt block; recover from a known-good source.
  2. Validate the compressor that produced the input.
  3. If recurring, audit the storage path for corruption.
Defensive patterns

Strategy: try-catch

Validate before calling

// Heuristic upper bound on match length vs destLen
if (destLen < 0 || destLen > src.length * 255L) {
    throw new IllegalArgumentException("Implausible destLen for match-length bounds");
}

Try / catch

try {
    ESLZ4Decompressor.INSTANCE.decompress(src, srcOff, dest, destOff, destLen);
} catch (LZ4Exception e) {
    markCorruptAndRecover();
}

Prevention

When it happens

Trigger: 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.

Common situations: Fuzzed/adversarial LZ4 input. Memory corruption overwriting match-length bytes with 0xFF. Incompatible compressor output.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/c4ba9fd08a42e477. Report an issue: GitHub.