Aircompressor's Snappy and LZ4 Java-based decompressor implementation can leak information from reused output buffer
Description
Aircompressor is a library with ports of the Snappy, LZO, LZ4, and Zstandard compression algorithms to Java. In versions 3.3 and below, incorrect handling of malformed data in Java-based decompressor implementations for Snappy and LZ4 allow remote attackers to read previous buffer contents via crafted compressed input. With certain crafted compressed inputs, elements from the output buffer can end up in the uncompressed output, potentially leaking sensitive data. This is relevant for applications that reuse the same output buffer to uncompress multiple inputs. This can be the case of a web server that allocates a fix-sized buffer for performance purposes. There is similar vulnerability in GHSA-cmp6-m4wj-q63q. This issue is fixed in version 3.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.airlift:aircompressor-v3Maven | < 3.4 | 3.4 |
io.airlift:aircompressorMaven | < 2.0.3 | 2.0.3 |
Affected products
1- Range: 3.0, 3.1, 3.2, …
Patches
2f2b489b39877Fix LZ4 decompressor data leak when match offset is zero
3 files changed · +15 −2
src/main/java/io/airlift/compress/v3/lz4/Lz4RawDecompressor.java+1 −1 modified@@ -114,7 +114,7 @@ public static int decompress( input += SIZE_OF_SHORT; long matchAddress = output - offset; - if (matchAddress < outputAddress) { + if (matchAddress < outputAddress || matchAddress >= output) { throw new MalformedInputException(input - inputAddress, "offset outside destination buffer"); }
src/test/java/io/airlift/compress/v3/lz4/AbstractTestLz4.java+1 −1 modified@@ -63,6 +63,6 @@ void testMatchLengthOverflow() byte[] data = buffer.toByteArray(); assertThatThrownBy(() -> getDecompressor().decompress(data, 0, data.length, new byte[2048], 0, 2048)) - .hasMessageMatching("Malformed input.*|Unknown error occurred.*"); + .hasMessageMatching("Malformed input.*|Unknown error occurred.*|offset outside destination buffer.*"); } }
src/test/java/io/airlift/compress/v3/lz4/TestLz4.java+13 −0 modified@@ -15,9 +15,13 @@ import io.airlift.compress.v3.Compressor; import io.airlift.compress.v3.Decompressor; +import io.airlift.compress.v3.MalformedInputException; import io.airlift.compress.v3.thirdparty.JPountzLz4Compressor; import io.airlift.compress.v3.thirdparty.JPountzLz4Decompressor; import net.jpountz.lz4.LZ4Factory; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; class TestLz4 extends AbstractTestLz4 @@ -45,4 +49,13 @@ protected Decompressor getVerifyDecompressor() { return new JPountzLz4Decompressor(LZ4Factory.fastestInstance()); } + + @Test + void testZeroMatchOffset() + { + byte[] compressed = new byte[] {15, 0, 0, -1, -1, -118, 49, -1, -1, 0}; + assertThatThrownBy(() -> getDecompressor().decompress(compressed, 0, compressed.length, new byte[1024], 0, 1024)) + .isInstanceOf(MalformedInputException.class) + .hasMessageContaining("offset outside destination buffer: offset=3"); + } }
ff12c4d5757cFix data leak when snappy match offset is zero
2 files changed · +14 −1
src/main/java/io/airlift/compress/v3/snappy/SnappyRawDecompressor.java+1 −1 modified@@ -150,7 +150,7 @@ private static int uncompressAll( // bit 8). int matchOffset = entry & 0x700; matchOffset += trailer; - if (matchOffset < 0) { + if (matchOffset <= 0) { throw new MalformedInputException(input - inputAddress); }
src/test/java/io/airlift/compress/v3/snappy/TestSnappyJava.java+13 −0 modified@@ -15,8 +15,12 @@ import io.airlift.compress.v3.Compressor; import io.airlift.compress.v3.Decompressor; +import io.airlift.compress.v3.MalformedInputException; import io.airlift.compress.v3.thirdparty.XerialSnappyCompressor; import io.airlift.compress.v3.thirdparty.XerialSnappyDecompressor; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; class TestSnappyJava extends AbstractTestSnappy @@ -44,4 +48,13 @@ protected Decompressor getVerifyDecompressor() { return new XerialSnappyDecompressor(); } + + @Test + void testZeroMatchOffsetFails() + { + byte[] zeroMatchOffset = new byte[] {16, 1, 0, 1, 0, 1, 0, 1, 0}; + assertThatThrownBy(() -> new SnappyJavaDecompressor().decompress(zeroMatchOffset, 0, zeroMatchOffset.length, new byte[64], 0, 64)) + .isInstanceOf(MalformedInputException.class) + .hasMessageContaining("Malformed input: offset=2"); + } }
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
7- github.com/advisories/GHSA-vx9q-rhv9-3jvgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-67721ghsaADVISORY
- github.com/airlift/aircompressor/commit/f2b489b398779b40c1ee29ddb11d7edef54ddc15ghsax_refsource_MISCWEB
- github.com/airlift/aircompressor/commit/ff12c4d5757c9d6d1de3d39a10402f1f84f9b765ghsax_refsource_MISCWEB
- github.com/airlift/aircompressor/pull/309ghsaWEB
- github.com/airlift/aircompressor/releases/tag/2.0.3ghsaWEB
- github.com/airlift/aircompressor/security/advisories/GHSA-vx9q-rhv9-3jvgghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.