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 ebec56863c89 [SPARK-57439][CORE] Validate length before allocation in
`Encoders.(Int|Long)Arrays` decode
ebec56863c89 is described below
commit ebec56863c8992d6a9010bbc2629845eac08e685
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Sun Jun 14 12:11:32 2026 -0700
[SPARK-57439][CORE] Validate length before allocation in
`Encoders.(Int|Long)Arrays` decode
### What changes were proposed in this pull request?
`Encoders.IntArrays.decode()` and `Encoders.LongArrays.decode()` now
validate the
element-count prefix before allocating the array.
Unlike the 1-byte-element `Encoders.Strings` / `Encoders.ByteArrays` cases
(SPARK-57426, SPARK-57430), where the byte length maps 1:1 to readable
bytes, each
element here occupies a fixed multiple of bytes, so the upper bound is
divided by the
element size:
- `IntArrays`: `Objects.checkFromIndexSize(0, numInts, buf.readableBytes()
/ 4)` (4 bytes per `int`)
- `LongArrays`: `Objects.checkFromIndexSize(0, numLongs,
buf.readableBytes() / 8)` (8 bytes per `long`)
This requires `0 <= count <= (remaining bytes / element size)` and throws
`IndexOutOfBoundsException` otherwise. After `readInt()`, the reader index
is past the
count prefix, so `buf.readableBytes() / elementSize` is the maximum number
of elements
actually decodable from the remaining payload.
- Note that we use the Java built-in `Objects.checkFromIndexSize` because
we cannot use
**Netty's `checkReadableBytes`**. However, both methods throw
`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 int[numInts]` / `new long[numLongs]` from an
untrusted count prefix.
- 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, consistent with SPARK-57426
(`Strings`)
and SPARK-57430 (`ByteArrays`).
These two decoders are used by the shuffle wire-protocol messages, e.g.
`FetchShuffleBlocks`, `FetchShuffleBlockChunks`, `MergeStatuses`, and
`LocalDirsForExecutors`.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Pass the CIs.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 4.8)
Closes #56500 from dongjoon-hyun/SPARK-57439.
Authored-by: Dongjoon Hyun <[email protected]>
Signed-off-by: Dongjoon Hyun <[email protected]>
(cherry picked from commit 6330518717251fa34cc6e93abf254b3f2c368f7b)
Signed-off-by: Dongjoon Hyun <[email protected]>
(cherry picked from commit 5087cbefbb32358a403695b4811ae9216c216809)
Signed-off-by: Dongjoon Hyun <[email protected]>
(cherry picked from commit 77b3f64d550330d1eaf2cdc03a20f676f5de9dee)
Signed-off-by: Dongjoon Hyun <[email protected]>
---
.../apache/spark/network/protocol/Encoders.java | 2 +
.../spark/network/protocol/EncodersSuite.java | 44 ++++++++++++++++++++++
2 files changed, 46 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 69b7c3159b2c..d69ad7c1f9ce 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
@@ -156,6 +156,7 @@ public class Encoders {
public static int[] decode(ByteBuf buf) {
int numInts = buf.readInt();
+ Objects.checkFromIndexSize(0, numInts, buf.readableBytes() / 4);
int[] ints = new int[numInts];
for (int i = 0; i < ints.length; i ++) {
ints[i] = buf.readInt();
@@ -179,6 +180,7 @@ public class Encoders {
public static long[] decode(ByteBuf buf) {
int numLongs = buf.readInt();
+ Objects.checkFromIndexSize(0, numLongs, buf.readableBytes() / 8);
long[] longs = new long[numLongs];
for (int i = 0; i < longs.length; i ++) {
longs[i] = buf.readLong();
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 66299c38fa03..0a07ebf17ded 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
@@ -110,4 +110,48 @@ public class EncodersSuite {
buf.writeInt(Integer.MAX_VALUE);
assertThrows(IndexOutOfBoundsException.class, () ->
Encoders.ByteArrays.decode(buf));
}
+
+ @Test
+ public void testIntArraysEncodeDecode() {
+ int[] arr = new int[] { 1, 2, 3, 4, 5 };
+ ByteBuf buf = Unpooled.buffer(Encoders.IntArrays.encodedLength(arr));
+ Encoders.IntArrays.encode(buf, arr);
+ assertArrayEquals(arr, Encoders.IntArrays.decode(buf));
+ }
+
+ @Test
+ public void testIntArraysDecodeShouldFailWhenLengthIsNegative() {
+ ByteBuf buf = Unpooled.buffer();
+ buf.writeInt(-1);
+ assertThrows(IndexOutOfBoundsException.class, () ->
Encoders.IntArrays.decode(buf));
+ }
+
+ @Test
+ public void testIntArraysDecodeShouldFailWhenLengthExceedsReadableBytes() {
+ ByteBuf buf = Unpooled.buffer();
+ buf.writeInt(Integer.MAX_VALUE);
+ assertThrows(IndexOutOfBoundsException.class, () ->
Encoders.IntArrays.decode(buf));
+ }
+
+ @Test
+ public void testLongArraysEncodeDecode() {
+ long[] arr = new long[] { 1L, 2L, 3L, 4L, 5L };
+ ByteBuf buf = Unpooled.buffer(Encoders.LongArrays.encodedLength(arr));
+ Encoders.LongArrays.encode(buf, arr);
+ assertArrayEquals(arr, Encoders.LongArrays.decode(buf));
+ }
+
+ @Test
+ public void testLongArraysDecodeShouldFailWhenLengthIsNegative() {
+ ByteBuf buf = Unpooled.buffer();
+ buf.writeInt(-1);
+ assertThrows(IndexOutOfBoundsException.class, () ->
Encoders.LongArrays.decode(buf));
+ }
+
+ @Test
+ public void testLongArraysDecodeShouldFailWhenLengthExceedsReadableBytes() {
+ ByteBuf buf = Unpooled.buffer();
+ buf.writeInt(Integer.MAX_VALUE);
+ assertThrows(IndexOutOfBoundsException.class, () ->
Encoders.LongArrays.decode(buf));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]