From b830a0a8d44e491a3d79b06e9e74d561f5227112 Mon Sep 17 00:00:00 2001 From: Martin Dindoffer Date: Tue, 4 Jun 2024 14:31:37 +0200 Subject: [PATCH] Fix infinite loop in PackedInputStream when the supplied ArrayInputStream is smaller than needed Additionally: * ArrayInputStream is no longer final, as there is no reason to stop consumers from extending and adjusting the behaviour * The wrapped ByteBuffer is not private, as according to the (previously) undocumented contract, it should be only accessible through #getReadBuffer(). --- .../main/java/org/capnproto/ArrayInputStream.java | 11 +++++++---- .../main/java/org/capnproto/BufferedInputStream.java | 11 ++++++++++- .../test/java/org/capnproto/SerializePackedTest.java | 12 ++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/runtime/src/main/java/org/capnproto/ArrayInputStream.java b/runtime/src/main/java/org/capnproto/ArrayInputStream.java index 20d287dd..93651859 100644 --- a/runtime/src/main/java/org/capnproto/ArrayInputStream.java +++ b/runtime/src/main/java/org/capnproto/ArrayInputStream.java @@ -23,11 +23,10 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -public final class ArrayInputStream implements BufferedInputStream { +public class ArrayInputStream implements BufferedInputStream { - public final ByteBuffer buf; + private final ByteBuffer buf; public ArrayInputStream(ByteBuffer buf) { this.buf = buf.asReadOnlyBuffer(); @@ -52,7 +51,11 @@ public final int read(ByteBuffer dst) throws IOException { @Override public final ByteBuffer getReadBuffer() { - return this.buf; + if (buf.remaining() > 0) { + return buf; + } else { + throw new DecodeException("Premature EOF while reading buffer"); + } } @Override diff --git a/runtime/src/main/java/org/capnproto/BufferedInputStream.java b/runtime/src/main/java/org/capnproto/BufferedInputStream.java index 7b3e3409..136dd803 100644 --- a/runtime/src/main/java/org/capnproto/BufferedInputStream.java +++ b/runtime/src/main/java/org/capnproto/BufferedInputStream.java @@ -25,5 +25,14 @@ import java.nio.channels.ReadableByteChannel; public interface BufferedInputStream extends ReadableByteChannel { - public ByteBuffer getReadBuffer() throws java.io.IOException; + + /** + * Returns a {@link ByteBuffer} to read data from. + * If there is no more data to read, throws a {@link DecodeException}. + * + * @return a {@link ByteBuffer} with data to read from, if any available + * @throws java.io.IOException when an I/O error occurs + * @throws DecodeException when trying to read more data than available + */ + ByteBuffer getReadBuffer() throws java.io.IOException; } diff --git a/runtime/src/test/java/org/capnproto/SerializePackedTest.java b/runtime/src/test/java/org/capnproto/SerializePackedTest.java index f4197f04..ebcac10a 100644 --- a/runtime/src/test/java/org/capnproto/SerializePackedTest.java +++ b/runtime/src/test/java/org/capnproto/SerializePackedTest.java @@ -82,4 +82,16 @@ private void assertPacksTo(byte[] unpacked, byte[] packed) { Assert.assertTrue(Arrays.equals(bytes, unpacked)); } } + + @Test(timeout = 1000, expected = DecodeException.class) + public void read_shouldThrowDecodingExceptionOnEmptyArrayInputStream() throws IOException { + byte[] emptyByteArray = {}; + MessageReader reader = SerializePacked.read(new ArrayInputStream(ByteBuffer.wrap(emptyByteArray)), ReaderOptions.DEFAULT_READER_OPTIONS); + } + + @Test(timeout = 1000, expected = DecodeException.class) + public void read_shouldThrowDecodingExceptionWhenTryingToReadMoreThanAvailableFromArrayInputStream() throws IOException { + byte[] bytes = {17, 0, 127, 0, 0, 0, 0}; //segment0 size of 127 words, which is way larger than the tiny 7 byte input + MessageReader reader = SerializePacked.read(new ArrayInputStream(ByteBuffer.wrap(bytes)), ReaderOptions.DEFAULT_READER_OPTIONS); + } }