This is an automated email from the ASF dual-hosted git repository.

dongjoon-hyun pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 7070360d3a2d [SPARK-57426][CORE] Validate length before allocation in 
`Encoders.Strings.decode`
7070360d3a2d is described below

commit 7070360d3a2da8c6ac0cd6a09eb55fb7035504f5
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Sat Jun 13 16:58:38 2026 -0700

    [SPARK-57426][CORE] Validate length before allocation in 
`Encoders.Strings.decode`
    
    ### What changes were proposed in this pull request?
    
    `Encoders.Strings.decode()` now validates the length prefix before 
allocating the array, requiring `0 <= length <= buf.readableBytes()` and 
throwing `IndexOutOfBoundsException` otherwise. After `readInt()`, the reader 
index is past the prefix, so `readableBytes()` equals the remaining payload 
bytes.
    
    - Note that we use the Java built-in `Objects.checkFromIndexSize` because 
we cannot use **Netty's `checkReadableBytes`**. However, both methods throws 
`IndexOutOfBoundsException` identically for the error cases.
      - 
https://netty.io/4.2/api/io/netty/buffer/AbstractByteBuf.html#checkReadableBytes(int)
      - 
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#checkFromIndexSize(int,int,int)
    
    ### Why are the changes needed?
    
    `decode()` allocated `new byte[length]` from an untrusted length.
    - A negative value throws an opaque `NegativeArraySizeException`.
    - An oversized value can trigger `OutOfMemoryError` on a corrupt or hostile 
frame.
    
    Validating first fails fast with a clear error.
    
    Note that `OutOfMemoryError` can happen even in a secured environment at 
the first parse on an unauthenticated channel.
    
    
https://github.com/apache/spark/blob/67eafbddf7962185b2399b97241a008cf8d0d333/common/network-common/src/main/java/org/apache/spark/network/sasl/SaslRpcHandler.java#L72-L80
    
    
https://github.com/apache/spark/blob/67eafbddf7962185b2399b97241a008cf8d0d333/common/network-common/src/main/java/org/apache/spark/network/sasl/SaslMessage.java#L68-L74
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Pass the CIs with newly added test cases.
    
    Without this PR, 
`testStringsDecodeShouldFailWhenLengthExceedsReadableBytes` fails with 
`OutOfMemoryError`.
    ```
    [info] Test 
org.apache.spark.network.protocol.EncodersSuite#testStringsDecodeShouldFailWhenLengthExceedsReadableBytes()
 started
    [0.473s][warning][oom,vendor] java.lang.OutOfMemoryError occurred: 
Requested array size exceeds VM limit
    [error] java.lang.OutOfMemoryError: Requested array size exceeds VM limit
    
    ```
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
    
    Closes #56488 from dongjoon-hyun/SPARK-57426.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 768569d6913be88bcb7af4a653ed2a52a0b568e7)
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../apache/spark/network/protocol/Encoders.java    |  2 ++
 .../spark/network/protocol/EncodersSuite.java      | 22 ++++++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git 
a/common/network-common/src/main/java/org/apache/spark/network/protocol/Encoders.java
 
b/common/network-common/src/main/java/org/apache/spark/network/protocol/Encoders.java
index 8bab808ad686..488e36004f2f 100644
--- 
a/common/network-common/src/main/java/org/apache/spark/network/protocol/Encoders.java
+++ 
b/common/network-common/src/main/java/org/apache/spark/network/protocol/Encoders.java
@@ -20,6 +20,7 @@ package org.apache.spark.network.protocol;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Objects;
 
 import io.netty.buffer.ByteBuf;
 import org.roaringbitmap.RoaringBitmap;
@@ -41,6 +42,7 @@ public class Encoders {
 
     public static String decode(ByteBuf buf) {
       int length = buf.readInt();
+      Objects.checkFromIndexSize(0, length, buf.readableBytes());
       byte[] bytes = new byte[length];
       buf.readBytes(bytes);
       return new String(bytes, StandardCharsets.UTF_8);
diff --git 
a/common/network-common/src/test/java/org/apache/spark/network/protocol/EncodersSuite.java
 
b/common/network-common/src/test/java/org/apache/spark/network/protocol/EncodersSuite.java
index 127835d29bc0..55b09b66aef6 100644
--- 
a/common/network-common/src/test/java/org/apache/spark/network/protocol/EncodersSuite.java
+++ 
b/common/network-common/src/test/java/org/apache/spark/network/protocol/EncodersSuite.java
@@ -48,6 +48,28 @@ public class EncodersSuite {
       () -> Encoders.Bitmaps.encode(buf, bitmap));
   }
 
+  @Test
+  public void testStringsEncodeDecode() {
+    String s = "spark";
+    ByteBuf buf = Unpooled.buffer(Encoders.Strings.encodedLength(s));
+    Encoders.Strings.encode(buf, s);
+    assertEquals(s, Encoders.Strings.decode(buf));
+  }
+
+  @Test
+  public void testStringsDecodeShouldFailWhenLengthIsNegative() {
+    ByteBuf buf = Unpooled.buffer();
+    buf.writeInt(-1);
+    assertThrows(IndexOutOfBoundsException.class, () -> 
Encoders.Strings.decode(buf));
+  }
+
+  @Test
+  public void testStringsDecodeShouldFailWhenLengthExceedsReadableBytes() {
+    ByteBuf buf = Unpooled.buffer();
+    buf.writeInt(Integer.MAX_VALUE);
+    assertThrows(IndexOutOfBoundsException.class, () -> 
Encoders.Strings.decode(buf));
+  }
+
   @Test
   public void testBitmapArraysEncodeDecode() {
     RoaringBitmap[] bitmaps = new RoaringBitmap[] {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to