This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 2f36427b07 [common] Fix bounded bulk reads past end of stream (#8590)
2f36427b07 is described below
commit 2f36427b07614148532783f653509eaac8e10ffd
Author: QuakeWang <[email protected]>
AuthorDate: Wed Jul 15 08:11:50 2026 +0800
[common] Fix bounded bulk reads past end of stream (#8590)
---
.../paimon/fs/OffsetSeekableInputStream.java | 13 +++++-
.../paimon/fs/OffsetSeekableInputStreamTest.java | 51 ++++++++++++++++++++++
2 files changed, 62 insertions(+), 2 deletions(-)
diff --git
a/paimon-common/src/main/java/org/apache/paimon/fs/OffsetSeekableInputStream.java
b/paimon-common/src/main/java/org/apache/paimon/fs/OffsetSeekableInputStream.java
index 94e2cf33e2..66f7f08c0d 100644
---
a/paimon-common/src/main/java/org/apache/paimon/fs/OffsetSeekableInputStream.java
+++
b/paimon-common/src/main/java/org/apache/paimon/fs/OffsetSeekableInputStream.java
@@ -62,11 +62,20 @@ public class OffsetSeekableInputStream extends
SeekableInputStream {
@Override
public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
if (length != -1) {
- len = (int) Math.min(len, length - getPos());
- if (len == 0) {
+ long remaining = length - getPos();
+ if (remaining <= 0) {
return -1;
}
+ len = (int) Math.min(len, remaining);
}
return wrapped.read(b, off, len);
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/fs/OffsetSeekableInputStreamTest.java
b/paimon-common/src/test/java/org/apache/paimon/fs/OffsetSeekableInputStreamTest.java
index 94f7c47fa0..16a5e3924c 100644
---
a/paimon-common/src/test/java/org/apache/paimon/fs/OffsetSeekableInputStreamTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/fs/OffsetSeekableInputStreamTest.java
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -150,6 +151,56 @@ public class OffsetSeekableInputStreamTest {
}
}
+ @Test
+ public void testReadByteArrayPastEnd() throws IOException {
+ long offset = 5;
+ long length = 10;
+ try (OffsetSeekableInputStream stream =
+ new OffsetSeekableInputStream(wrapped, offset, length)) {
+ stream.seek(length + 1);
+ assertThat(stream.read(new byte[5], 0, 5)).isEqualTo(-1);
+ }
+ }
+
+ @Test
+ public void testReadZeroLength() throws IOException {
+ long offset = 5;
+ long length = 10;
+ byte[] buffer = new byte[5];
+ try (OffsetSeekableInputStream stream =
+ new OffsetSeekableInputStream(wrapped, offset, length)) {
+ assertThat(stream.read(buffer, 0, 0)).isZero();
+
+ stream.seek(length);
+ assertThat(stream.read(buffer, 0, 0)).isZero();
+
+ stream.seek(length + 1);
+ assertThat(stream.read(buffer, 0, 0)).isZero();
+ }
+ }
+
+ @Test
+ public void testInvalidReadArguments() throws IOException {
+ long offset = 5;
+ long length = 10;
+ byte[] buffer = new byte[5];
+ try (OffsetSeekableInputStream stream =
+ new OffsetSeekableInputStream(wrapped, offset, length)) {
+ stream.seek(length);
+
+ assertThatThrownBy(() -> stream.read(null, 0, 1))
+ .isInstanceOf(NullPointerException.class);
+ assertThatThrownBy(() -> stream.read(buffer, -1, 1))
+ .isInstanceOf(IndexOutOfBoundsException.class);
+ assertThatThrownBy(() -> stream.read(buffer, 0, -1))
+ .isInstanceOf(IndexOutOfBoundsException.class);
+ assertThatThrownBy(() -> stream.read(buffer, buffer.length + 1, 0))
+ .isInstanceOf(IndexOutOfBoundsException.class);
+ assertThatThrownBy(() -> stream.read(buffer, 1, buffer.length))
+ .isInstanceOf(IndexOutOfBoundsException.class);
+ }
+ }
+
@Test
public void testClose() throws IOException {
SeekableInputStream mockStream = mock(SeekableInputStream.class);