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);

Reply via email to