From 2d9b7ce6443cdce1209cd85364cb05a448bad89e Mon Sep 17 00:00:00 2001 From: Jonas Konrad Date: Fri, 25 Sep 2026 17:26:56 +0200 Subject: [PATCH] Throw IOException for invalid or unsupported frame descriptors LZ4FrameInputStream.readHeader() passed the FLG and BD bytes to FLG.fromByte/BD.fromByte unguarded, so reserved bits, dependent blocks, dictionary IDs, bad versions and invalid block size indicators surfaced as RuntimeException/IllegalArgumentException from read(), skip() and the expected content size getters. Wrap them in an IOException instead. Fixes #102 Co-Authored-By: Claude Opus 5.5 --- .../net/jpountz/lz4/LZ4FrameInputStream.java | 13 ++++-- .../net/jpountz/lz4/LZ4FrameIOStreamTest.java | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/java/net/jpountz/lz4/LZ4FrameInputStream.java b/src/java/net/jpountz/lz4/LZ4FrameInputStream.java index 728524de..2ffed01b 100644 --- a/src/java/net/jpountz/lz4/LZ4FrameInputStream.java +++ b/src/java/net/jpountz/lz4/LZ4FrameInputStream.java @@ -46,6 +46,7 @@ public class LZ4FrameInputStream extends FilterInputStream { static final String NOT_SUPPORTED = "Stream unsupported"; static final String BLOCK_HASH_MISMATCH = "Block checksum mismatch"; static final String DESCRIPTOR_HASH_MISMATCH = "Stream frame descriptor corrupted"; + static final String INVALID_DESCRIPTOR = "Invalid or unsupported frame descriptor"; static final int MAGIC_SKIPPABLE_BASE = 0x184D2A50; private final LZ4SafeDecompressor decompressor; @@ -193,10 +194,16 @@ private void readHeader() throws IOException { } final byte flgByte = (byte) (flgRead & 0xFF); - final LZ4FrameOutputStream.FLG flg = LZ4FrameOutputStream.FLG.fromByte(flgByte); - headerBuffer.put(flgByte); final byte bdByte = (byte) (bdRead & 0xFF); - final LZ4FrameOutputStream.BD bd = LZ4FrameOutputStream.BD.fromByte(bdByte); + final LZ4FrameOutputStream.FLG flg; + final LZ4FrameOutputStream.BD bd; + try { + flg = LZ4FrameOutputStream.FLG.fromByte(flgByte); + bd = LZ4FrameOutputStream.BD.fromByte(bdByte); + } catch (RuntimeException e) { + throw new IOException(INVALID_DESCRIPTOR, e); + } + headerBuffer.put(flgByte); headerBuffer.put(bdByte); this.frameInfo = new LZ4FrameOutputStream.FrameInfo(flg, bd); diff --git a/src/test/net/jpountz/lz4/LZ4FrameIOStreamTest.java b/src/test/net/jpountz/lz4/LZ4FrameIOStreamTest.java index dcd6607a..04623085 100644 --- a/src/test/net/jpountz/lz4/LZ4FrameIOStreamTest.java +++ b/src/test/net/jpountz/lz4/LZ4FrameIOStreamTest.java @@ -16,6 +16,7 @@ * limitations under the License. */ +import net.jpountz.xxhash.XXHashFactory; import org.junit.After; import org.junit.Assert; import org.junit.Assume; @@ -576,4 +577,46 @@ public void testAvailable() throws IOException { lz4File.delete(); } } + + private static byte[] frameHeader(int flg, int bd) { + final byte[] descriptor = new byte[]{(byte) flg, (byte) bd}; + final byte hc = (byte) ((XXHashFactory.fastestInstance().hash32().hash(descriptor, 0, descriptor.length, 0) >> 8) & 0xFF); + final ByteBuffer header = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN); + header.putInt(LZ4FrameOutputStream.MAGIC); + header.put(descriptor); + header.put(hc); + return header.array(); + } + + private static void assertInvalidDescriptor(IOException e) { + Assert.assertEquals(LZ4FrameInputStream.INVALID_DESCRIPTOR, e.getMessage()); + } + + @Test + public void testInvalidOrUnsupportedFrameDescriptor() throws IOException { + Assume.assumeTrue(testSize == 0); // does not depend on the test size + final int[][] descriptors = { + {0x40, 0x70}, // dependent blocks + {0x61, 0x70}, // dictionary ID + {0x62, 0x70}, // reserved FLG bit + {0x20, 0x70}, // unsupported version + {0x60, 0x30}, // block size indicator 3 + {0x60, 0x41}, // reserved BD bit + }; + for (final int[] d : descriptors) { + final byte[] header = frameHeader(d[0], d[1]); + try (InputStream is = new LZ4FrameInputStream(new ByteArrayInputStream(header))) { + assertInvalidDescriptor(Assert.assertThrows(IOException.class, is::read)); + } + try (InputStream is = new LZ4FrameInputStream(new ByteArrayInputStream(header))) { + assertInvalidDescriptor(Assert.assertThrows(IOException.class, () -> is.skip(1))); + } + try (LZ4FrameInputStream is = new LZ4FrameInputStream(new ByteArrayInputStream(header), true)) { + assertInvalidDescriptor(Assert.assertThrows(IOException.class, is::getExpectedContentSize)); + } + try (LZ4FrameInputStream is = new LZ4FrameInputStream(new ByteArrayInputStream(header), true)) { + assertInvalidDescriptor(Assert.assertThrows(IOException.class, is::isExpectedContentSizeDefined)); + } + } + } }