From d4485f1afce7b52b580806a335d5d7cd651ec587 Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Thu, 23 Jul 2026 15:01:53 +0530 Subject: [PATCH] validate decimal byte length before writing in setBigEndian --- .../apache/arrow/vector/Decimal256Vector.java | 15 ++++++++++++-- .../apache/arrow/vector/DecimalVector.java | 15 ++++++++++++-- .../arrow/vector/TestDecimal256Vector.java | 19 ++++++++++++++++++ .../arrow/vector/TestDecimalVector.java | 20 +++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/Decimal256Vector.java b/vector/src/main/java/org/apache/arrow/vector/Decimal256Vector.java index f9d7e5cb9e..85f11c05ec 100644 --- a/vector/src/main/java/org/apache/arrow/vector/Decimal256Vector.java +++ b/vector/src/main/java/org/apache/arrow/vector/Decimal256Vector.java @@ -208,6 +208,14 @@ public void setBigEndian(int index, byte[] value) { BitVectorHelper.setBit(validityBuffer, index); final int length = value.length; + // Reject an oversized value before any bytes are written. The little-endian path below + // copies all `length` bytes into the fixed TYPE_WIDTH slot with unchecked native writes, + // so validating after the copy would leave adjacent memory already corrupted. + if (length > TYPE_WIDTH) { + throw new IllegalArgumentException( + "Invalid decimal value length. Valid length in [1 - 32], got " + length); + } + // do the bound check. valueBuffer.checkBytes((long) index * TYPE_WIDTH, (long) (index + 1) * TYPE_WIDTH); @@ -243,8 +251,6 @@ public void setBigEndian(int index, byte[] value) { return; } } - throw new IllegalArgumentException( - "Invalid decimal value length. Valid length in [1 - 32], got " + length); } /** @@ -308,6 +314,11 @@ public void setBigEndianSafe(int index, long start, ArrowBuf buffer, int length) handleSafe(index); BitVectorHelper.setBit(validityBuffer, index); + if (length > TYPE_WIDTH) { + throw new IllegalArgumentException( + "Invalid decimal value length. Valid length in [1 - 32], got " + length); + } + // do the bound checks. buffer.checkBytes(start, start + length); valueBuffer.checkBytes((long) index * TYPE_WIDTH, (long) (index + 1) * TYPE_WIDTH); diff --git a/vector/src/main/java/org/apache/arrow/vector/DecimalVector.java b/vector/src/main/java/org/apache/arrow/vector/DecimalVector.java index 9bf1812cc6..b2034d706c 100644 --- a/vector/src/main/java/org/apache/arrow/vector/DecimalVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/DecimalVector.java @@ -207,6 +207,14 @@ public void setBigEndian(int index, byte[] value) { BitVectorHelper.setBit(validityBuffer, index); final int length = value.length; + // Reject an oversized value before any bytes are written. The little-endian path below + // copies all `length` bytes into the fixed TYPE_WIDTH slot with unchecked native writes, + // so validating after the copy would leave adjacent memory already corrupted. + if (length > TYPE_WIDTH) { + throw new IllegalArgumentException( + "Invalid decimal value length. Valid length in [1 - 16], got " + length); + } + // do the bound check. valueBuffer.checkBytes((long) index * TYPE_WIDTH, (long) (index + 1) * TYPE_WIDTH); @@ -241,8 +249,6 @@ public void setBigEndian(int index, byte[] value) { return; } } - throw new IllegalArgumentException( - "Invalid decimal value length. Valid length in [1 - 16], got " + length); } /** @@ -306,6 +312,11 @@ public void setBigEndianSafe(int index, long start, ArrowBuf buffer, int length) handleSafe(index); BitVectorHelper.setBit(validityBuffer, index); + if (length > TYPE_WIDTH) { + throw new IllegalArgumentException( + "Invalid decimal value length. Valid length in [1 - 16], got " + length); + } + // do the bound checks. buffer.checkBytes(start, start + length); valueBuffer.checkBytes((long) index * TYPE_WIDTH, (long) (index + 1) * TYPE_WIDTH); diff --git a/vector/src/test/java/org/apache/arrow/vector/TestDecimal256Vector.java b/vector/src/test/java/org/apache/arrow/vector/TestDecimal256Vector.java index b995dc5d92..d0b2802507 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestDecimal256Vector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestDecimal256Vector.java @@ -283,6 +283,25 @@ public void decimalBE2NE() { } } + @Test + public void setBigEndianOversizedDoesNotCorruptNeighbor() { + try (Decimal256Vector decimalVector = + TestUtils.newVector( + Decimal256Vector.class, "decimal", new ArrowType.Decimal(60, 0, 256), allocator)) { + decimalVector.allocateNew(2); + + // A value whose low bytes are all non-zero, stored in the slot right after the target. + final BigInteger neighbor = new BigInteger("305419896"); // 0x12345678 + decimalVector.setBigEndian(1, neighbor.toByteArray()); + + // A value longer than the 32-byte decimal must be rejected before anything is written. + assertThrows( + IllegalArgumentException.class, () -> decimalVector.setBigEndian(0, new byte[40])); + + assertEquals(neighbor, decimalVector.getObject(1).unscaledValue()); + } + } + @Test public void setUsingArrowBufOfLEInts() { try (Decimal256Vector decimalVector = diff --git a/vector/src/test/java/org/apache/arrow/vector/TestDecimalVector.java b/vector/src/test/java/org/apache/arrow/vector/TestDecimalVector.java index 85c11e8f3d..0638905799 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestDecimalVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestDecimalVector.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -279,6 +280,25 @@ public void decimalBE2NE() { } } + @Test + public void setBigEndianOversizedDoesNotCorruptNeighbor() { + try (DecimalVector decimalVector = + TestUtils.newVector( + DecimalVector.class, "decimal", new ArrowType.Decimal(38, 0, 128), allocator)) { + decimalVector.allocateNew(2); + + // A value whose low bytes are all non-zero, stored in the slot right after the target. + final BigInteger neighbor = new BigInteger("305419896"); // 0x12345678 + decimalVector.setBigEndian(1, neighbor.toByteArray()); + + // A value longer than the 16-byte decimal must be rejected before anything is written. + assertThrows( + IllegalArgumentException.class, () -> decimalVector.setBigEndian(0, new byte[24])); + + assertEquals(neighbor, decimalVector.getObject(1).unscaledValue()); + } + } + @Test public void setUsingArrowBufOfInts() { try (DecimalVector decimalVector =