VYPR
High severityOSV Advisory· Published Dec 5, 2025· Updated Apr 15, 2026

CVE-2025-66566

CVE-2025-66566

Description

yawkat LZ4 Java provides LZ4 compression for Java. Insufficient clearing of the output buffer in Java-based decompressor implementations in lz4-java 1.10.0 and earlier allows remote attackers to read previous buffer contents via crafted compressed input. In applications where the output buffer is reused without being cleared, this may lead to disclosure of sensitive data. JNI-based implementations are not affected. This vulnerability is fixed in 1.10.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
at.yawk.lz4:lz4-javaMaven
< 1.10.11.10.1
org.lz4:lz4-javaMaven
<= 1.8.1
org.lz4:lz4-pure-javaMaven
<= 1.8.1
net.jpountz.lz4:lz4Maven
<= 1.8.1

Affected products

1

Patches

1
33d180cb70c4

Merge commit from fork

https://github.com/yawkat/lz4-javaJonas KonradDec 5, 2025via ghsa
4 files changed · +81 4
  • src/build/source_templates/decompress.template+8 1 modified
    @@ -133,7 +133,14 @@
             throw new LZ4Exception("Too large matchLen");
           }
     
    -      if (notEnoughSpace(destEnd - matchCopyEnd, COPY_LENGTH)) {
    +      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
    +        LZ4Utils.zero(dest, dOff, matchCopyEnd);
    +      } else if (notEnoughSpace(destEnd - matchCopyEnd, COPY_LENGTH)) {
             if (matchCopyEnd > destEnd) {
               throw new LZ4Exception("Malformed input at " + sOff);
             }
    
  • src/java/net/jpountz/lz4/LZ4Utils.java+27 0 modified
    @@ -16,6 +16,9 @@
      * limitations under the License.
      */
     
    +import java.nio.ByteBuffer;
    +import java.util.Arrays;
    +
     import static net.jpountz.lz4.LZ4Constants.HASH_LOG;
     import static net.jpountz.lz4.LZ4Constants.HASH_LOG_64K;
     import static net.jpountz.lz4.LZ4Constants.HASH_LOG_HC;
    @@ -106,6 +109,30 @@ static int hashHC(int i) {
         return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG_HC);
       }
     
    +  /**
    +   * Zero out a buffer.
    +   *
    +   * @param array The input array
    +   * @param start The start index
    +   * @param end   The end index (exclusive)
    +   */
    +  static void zero(byte[] array, int start, int end) {
    +    Arrays.fill(array, start, end, (byte) 0);
    +  }
    +
    +  /**
    +   * Zero out a buffer.
    +   *
    +   * @param bb    The input buffer
    +   * @param start The start index
    +   * @param end   The end index (exclusive)
    +   */
    +  static void zero(ByteBuffer bb, int start, int end) {
    +    for (int i = start; i < end; i++) {
    +      bb.put(i, (byte) 0);
    +    }
    +  }
    +
       static class Match {
         int start, ref, len;
     
    
  • src/test/net/jpountz/fuzz/LZ4DecompressorTest.java+17 3 modified
    @@ -6,6 +6,10 @@
     import net.jpountz.lz4.LZ4Factory;
     
     import java.nio.ByteBuffer;
    +import java.util.Arrays;
    +
    +import static org.junit.jupiter.api.Assertions.assertArrayEquals;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
     
     public class LZ4DecompressorTest {
       private static final int MAX_LEN = 1 << 16;
    @@ -34,11 +38,21 @@ private void test(FuzzedDataProvider data, LZ4Factory factory, boolean fast, boo
               factory.safeDecompressor().decompress(srcBuf, srcOff, src.length - srcOffEnd - srcOff, destBuf, destOff, destLen);
             }
           } else {
    -        byte[] dest = new byte[destOff + destLen];
    +        // For byte[], we decompress twice with different prior data in the output array, and compare the results. This
    +        // makes sure no uninitialized data remains.
    +        byte[] dest1 = new byte[destOff + destLen];
    +        byte[] dest2 = new byte[destOff + destLen];
    +        Arrays.fill(dest2, (byte) 'x');
             if (fast) {
    -          factory.fastDecompressor().decompress(src, srcOff, dest, destOff, destLen);
    +          int n1 = factory.fastDecompressor().decompress(src, srcOff, dest1, destOff, destLen);
    +          int n2 = factory.fastDecompressor().decompress(src, srcOff, dest2, destOff, destLen);
    +          assertEquals(n1, n2);
    +          assertArrayEquals(Arrays.copyOfRange(dest1, destOff, destOff + destLen), Arrays.copyOfRange(dest2, destOff, destOff + destLen));
             } else {
    -          factory.safeDecompressor().decompress(src, srcOff, src.length - srcOffEnd - srcOff, dest, destOff);
    +          int n1 = factory.safeDecompressor().decompress(src, srcOff, src.length - srcOffEnd - srcOff, dest1, destOff);
    +          int n2 = factory.safeDecompressor().decompress(src, srcOff, src.length - srcOffEnd - srcOff, dest2, destOff);
    +          assertEquals(n1, n2);
    +          assertArrayEquals(Arrays.copyOfRange(dest1, destOff, destOff + n1), Arrays.copyOfRange(dest2, destOff, destOff + n2));
             }
           }
         } catch (LZ4Exception ignored) {
    
  • src/test/net/jpountz/lz4/OutOfBoundsTest.java+29 0 modified
    @@ -22,8 +22,11 @@
     import java.io.ByteArrayOutputStream;
     import java.nio.ByteBuffer;
     import java.util.Arrays;
    +import java.util.stream.IntStream;
     import java.util.stream.Stream;
     
    +import static net.jpountz.lz4.LZ4Constants.MIN_MATCH;
    +import static org.junit.jupiter.api.Assertions.assertArrayEquals;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertThrows;
     
    @@ -177,4 +180,30 @@ public void matchLenOverflow(FallibleDecompressor decompressor) {
         byte[] output = new byte[2055];
         assertThrows(LZ4Exception.class, () -> decompressor.decompress(input, output));
       }
    +
    +  static Stream<Object[]> copyBeyondOutputInputs() {
    +    return allDecompressors()
    +      .flatMap(decompressor ->
    +        IntStream.range(0, 14).boxed().flatMap(dec ->
    +          IntStream.range(dec, 14).mapToObj(len -> new Object[]{decompressor, dec.byteValue(), len})));
    +  }
    +
    +  @ParameterizedTest
    +  @MethodSource("copyBeyondOutputInputs")
    +  public void copyBeyondOutput(FallibleDecompressor decompressor, byte dec, int len) {
    +    byte[] compressed = {
    +      // padding frame (14 bytes)
    +      (byte) 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    +      // copy len bytes (+ 4 MIN_MATCH) from -dec
    +      (byte) len, dec, 0,
    +      // padding frame (12 bytes)
    +      (byte) 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    +    };
    +    byte[] output = new byte[14 + MIN_MATCH + len + MIN_MATCH + 12];
    +    Arrays.fill(output, (byte) 0x77);
    +
    +    decompressor.decompress(compressed, output);
    +
    +    assertArrayEquals(new byte[output.length], output); // should be all zero
    +  }
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.