Skip to content

GH-1246: validate decimal byte length before writing in setBigEndian - #1247

Open
Arawoof06 wants to merge 2 commits into
apache:mainfrom
Arawoof06:decimal-setbigendian-length-check
Open

Arawoof06 wants to merge 2 commits into
apache:mainfrom
Arawoof06:decimal-setbigendian-length-check

Conversation

@Arawoof06

Copy link
Copy Markdown
Contributor

What's Changed

setBigEndian(int, byte[]) on DecimalVector and Decimal256Vector byte-swaps the value into the fixed-width slot with unchecked MemoryUtil writes and only checks the length afterwards, so a byte array longer than the type width overruns the slot into adjacent off-heap memory before the IllegalArgumentException fires. setBigEndianSafe(int, long, ArrowBuf, int) does the same write with no length check at all. This moves the length check ahead of the write and adds it to the safe variant, so oversized input is rejected before any memory is touched; valid lengths are unaffected.

Closes #1246.

@github-actions

This comment has been minimized.

@lidavidm lidavidm added the bug-fix PRs that fix a big. label Aug 25, 2026
@lidavidm

Copy link
Copy Markdown
Member

@Arawoof06 please rebase.

@github-actions github-actions Bot added this to the 20.0.0 milestone Aug 25, 2026
@Arawoof06
Arawoof06 force-pushed the decimal-setbigendian-length-check branch from d4485f1 to 1e2344a Compare August 25, 2026 11:11
@Arawoof06

Copy link
Copy Markdown
Contributor Author

Rebased on main, should be good now.

@Arawoof06
Arawoof06 force-pushed the decimal-setbigendian-length-check branch from 1e2344a to 611438c Compare October 5, 2026 19:10
@Arawoof06

Copy link
Copy Markdown
Contributor Author

Rebased onto current main again. The CI runs from the August push failed at startup on a workflow file issue and never ran any jobs, and the new ones are waiting on approval. Could a maintainer approve the workflows?

// 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BitVectorHelper.setBit(validityBuffer, index) a few lines above still runs before this check. If an oversized value is rejected here, the slot is already marked valid. A caller that catches the exception then sees isNull(index) == false and reads stale or zero bytes instead of null.

Could you move this check above the setBit call, so a rejected call leaves the vector unchanged?

// 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here as in DecimalVector.setBigEndian. setBit runs before this check, so a value longer than 32 bytes leaves the slot marked valid after the exception.

Please validate the length first.

handleSafe(index);
BitVectorHelper.setBit(validityBuffer, index);

if (length > TYPE_WIDTH) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleSoft(index) and setBit both run before this check. A rejected oversize call can still grow the vector and mark the slot valid.

Please validate length at the top of the method so a failed call has no side effects.

handleSafe(index);
BitVectorHelper.setBit(validityBuffer, index);

if (length > TYPE_WIDTH) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as in DecimalVector.setBigEndianSafe.

Please validate length before handleSafe and setBit.

assertThrows(
IllegalArgumentException.class, () -> decimalVector.setBigEndian(0, new byte[24]));

assertEquals(neighbor, decimalVector.getObject(1).unscaledValue());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This covers the original bug, since the neighboring slot stays intact. It doesn't check the target slot after the exception.

Once the check is moved, please also assert assertTrue(decimalVector.isNull(0)) after the failed call. That would catch the validity-bit ordering issue.

A matching test for setBigEndianSafe would help too. It should assert isNull and that the value capacity is unchanged after the rejected call.

assertThrows(
IllegalArgumentException.class, () -> decimalVector.setBigEndian(0, new byte[40]));

assertEquals(neighbor, decimalVector.getObject(1).unscaledValue());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the TestDecimalVector comment. Please assert isNull(0) after the rejected call, and add a setBigEndianSafe case.

Move the length check ahead of handleSafe/setBit so a rejected oversize
call leaves the vector unchanged: the slot stays null and the vector is
not grown. Add setBigEndianSafe tests and assert isNull after rejection.
@Arawoof06

Copy link
Copy Markdown
Contributor Author

Good catch on the ordering. Pushed a fix: the length check now runs first in all four methods, before setBit (and before handleSafe in the safe variants), so a rejected oversize call leaves the slot null and doesn't grow the vector.

Also updated the tests per your note: both setBigEndian tests now assert isNull(0) after the rejected call, and I added setBigEndianSafe cases for DecimalVector and Decimal256Vector asserting isNull and unchanged value capacity. Full TestDecimalVector + TestDecimal256Vector run is green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PRs that fix a big.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DecimalVector.setBigEndian writes oversized value before validating length

3 participants