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

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


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new ff70618e296b [SPARK-57475][CORE] Validate length before allocation in 
`Encoders.BitmapArrays.decode`
ff70618e296b is described below

commit ff70618e296b54a85a170508096104ea015dfd1e
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Mon Jun 15 15:14:52 2026 -0700

    [SPARK-57475][CORE] Validate length before allocation in 
`Encoders.BitmapArrays.decode`
    
    ### What changes were proposed in this pull request?
    
    `Encoders.BitmapArrays.decode()` now validates the count prefix before 
allocating the array via `Objects.checkFromIndexSize(0, numBitmaps, 
buf.readableBytes() / 8)`, which requires `0 <= numBitmaps <= 
buf.readableBytes() / 8` and throws `IndexOutOfBoundsException` otherwise. The 
divisor `8` is the minimum on-wire size of one element, since an empty 
`RoaringBitmap` serializes to 8 bytes (a 4-byte cookie followed by a 4-byte 
size).
    
    ### Why are the changes needed?
    
    To make it robust.
    
    Previously, `decode()` allocated `new RoaringBitmap[numBitmaps]` without 
checking. So,
    - A negative value throws an opaque `NegativeArraySizeException`.
    - An oversized value can trigger `OutOfMemoryError` on a corrupt or hostile 
frame.
    
    The `numBitmaps` count comes from a `MergeStatuses` reply that the driver 
decodes in
    `ExternalBlockStoreClient.finalizeShuffleMerge`. The server that produces 
this reply is
    pluggable via `spark.shuffle.push.server.mergedShuffleFileManagerImpl`, so 
the frame may be
    encoded by a third-party implementation rather than Spark's own `Encoders`. 
Since the driver
    decodes bytes it did not encode, a version mismatch, an implementation bug, 
or a truncated
    frame can reach this allocation. Validating first fails fast with a clear 
error, consistent
    with the other array decoders in `Encoders`.
    
    
https://github.com/apache/spark/blob/0caf79030573233c6db526a4289499f8756adeaf/common/network-common/src/main/java/org/apache/spark/network/util/TransportConf.java#L545-L554
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Pass the CIs with newly added test cases.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
    
    Closes #56523 from dongjoon-hyun/SPARK-57475.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 2f4d32a324deec8e3841e0bdff4959a620d4d7ed)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit e9286c59819c5244dcef35f41319036de2cdb560)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 7cca9423a5bf101e1bfd0aec08661d1e39f83cd1)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 3b781eb793ef3fa53575c94a393c3713acb76818)
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../java/org/apache/spark/network/protocol/Encoders.java   |  3 +++
 .../org/apache/spark/network/protocol/EncodersSuite.java   | 14 ++++++++++++++
 2 files changed, 17 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 a91200e825d2..c4e94847ea0a 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
@@ -213,6 +213,9 @@ public class Encoders {
 
     public static RoaringBitmap[] decode(ByteBuf buf) {
       int numBitmaps = buf.readInt();
+      // The divisor 8 is the minimum on-wire size of one element, since an 
empty RoaringBitmap
+      // serializes to 8 bytes (a 4-byte cookie followed by a 4-byte size).
+      Objects.checkFromIndexSize(0, numBitmaps, buf.readableBytes() / 8);
       RoaringBitmap[] bitmaps = new RoaringBitmap[numBitmaps];
       for (int i = 0; i < bitmaps.length; i ++) {
         bitmaps[i] = Bitmaps.decode(buf);
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 24b766639210..91c95a3c76e0 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
@@ -89,6 +89,20 @@ public class EncodersSuite {
     assertArrayEquals(bitmaps, decodedBitmaps);
   }
 
+  @Test
+  public void testBitmapArraysDecodeShouldFailWhenLengthIsNegative() {
+    ByteBuf buf = Unpooled.buffer();
+    buf.writeInt(-1);
+    assertThrows(IndexOutOfBoundsException.class, () -> 
Encoders.BitmapArrays.decode(buf));
+  }
+
+  @Test
+  public void testBitmapArraysDecodeShouldFailWhenLengthExceedsReadableBytes() 
{
+    ByteBuf buf = Unpooled.buffer();
+    buf.writeInt(Integer.MAX_VALUE);
+    assertThrows(IndexOutOfBoundsException.class, () -> 
Encoders.BitmapArrays.decode(buf));
+  }
+
   @Test
   public void testByteArraysEncodeDecode() {
     byte[] arr = new byte[] { 1, 2, 3, 4, 5 };


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

Reply via email to