Skip to content

Commit 2d9f135

Browse files
authored
GH-1217: Bounds check before alloc in var-width view vectors (#1291)
Also, document that when enable_unsafe_memory_access is enabled, all bets are off. Reported by n0mi1k.
1 parent f804f90 commit 2d9f135

4 files changed

Lines changed: 117 additions & 80 deletions

File tree

‎memory/memory-core/src/main/java/org/apache/arrow/memory/ArrowBuf.java‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,24 @@ public void getBytes(long index, byte[] dst, int dstIndex, int length) {
731731
}
732732
}
733733

734+
/**
735+
* Copy data from this ArrowBuf into a newly allocated array.
736+
*
737+
* <p>This method is more resilient to invalid data inadvertently causing large allocations, as
738+
* the byte[] will not be allocated until we check the length.
739+
*
740+
* @param index index (0 based relative to the portion of memory this ArrowBuf has access to)
741+
* @param length length of data to copy from this ArrowBuf
742+
*/
743+
public byte[] getBytesAsArray(long index, int length) {
744+
checkIndex(index, length);
745+
byte[] dst = new byte[length];
746+
if (length != 0) {
747+
MemoryUtil.copyFromMemory(addr(index), dst, 0, length);
748+
}
749+
return dst;
750+
}
751+
734752
/**
735753
* Copy data from a given byte array into this ArrowBuf starting at a given index.
736754
*
@@ -1008,8 +1026,7 @@ public int setBytes(long index, InputStream in, int length) throws IOException {
10081026
/**
10091027
* Copy a certain length of bytes from this ArrowBuf at a given index into the given OutputStream.
10101028
*
1011-
* @param index index index (0 based relative to the portion of memory this ArrowBuf has access
1012-
* to)
1029+
* @param index index (0 based relative to the portion of memory this ArrowBuf has access to)
10131030
* @param out dst stream to copy data into
10141031
* @param length length of data to copy
10151032
* @throws IOException on failing to write to stream

‎memory/memory-core/src/main/java/org/apache/arrow/memory/BoundsChecking.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
* "arrow.enable_unsafe_memory_access" or "drill.enable_unsafe_memory_access". The latter is
2525
* deprecated. The environmental variable is named "ARROW_ENABLE_UNSAFE_MEMORY_ACCESS". When both
2626
* the system property and the environmental variable are set, the system property takes precedence.
27+
*
28+
* <p>WARNING: disabling bounds checking means that out-of-bounds memory access is possible! This
29+
* can lead to security vulnerabilities. You should not read or write untrusted data when bounds
30+
* checking is disabled.
2731
*/
2832
public class BoundsChecking {
2933

‎vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthViewVector.java‎

Lines changed: 77 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,13 @@ private void splitAndTransferViewBufferAndDataBuffer(
912912
viewBuffer.getInt(
913913
((long) i * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH + BUF_INDEX_WIDTH);
914914
final ArrowBuf dataBuf = dataBuffers.get(readBufIndex);
915+
if (readBufOffset < 0
916+
|| ((long) readBufOffset + (long) stringLength) > dataBuf.capacity()) {
917+
throw new IndexOutOfBoundsException(
918+
String.format(
919+
"index: %d, length: %d (expected: range(0, %d))",
920+
readBufOffset, stringLength, dataBuf.capacity()));
921+
}
915922

916923
// allocate data buffer
917924
ArrowBuf currentDataBuf = target.allocateOrGetLastDataBuffer(stringLength);
@@ -1432,7 +1439,7 @@ public void copyFrom(int fromIndex, int thisIndex, ValueVector from) {
14321439
BitVectorHelper.unsetBit(validityBuffer, thisIndex);
14331440
} else {
14341441
final int viewLength = from.getDataBuffer().getInt((long) fromIndex * ELEMENT_SIZE);
1435-
copyFromNotNull(fromIndex, thisIndex, from, viewLength);
1442+
copyFromNotNull(from, fromIndex, thisIndex, viewLength);
14361443
}
14371444
lastSet = thisIndex;
14381445
}
@@ -1454,39 +1461,35 @@ public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
14541461
} else {
14551462
final int viewLength = from.getDataBuffer().getInt((long) fromIndex * ELEMENT_SIZE);
14561463
handleSafe(thisIndex, viewLength);
1457-
copyFromNotNull(fromIndex, thisIndex, from, viewLength);
1464+
copyFromNotNull(from, fromIndex, thisIndex, viewLength);
14581465
}
14591466
lastSet = thisIndex;
14601467
}
14611468

1462-
private void copyFromNotNull(int fromIndex, int thisIndex, ValueVector from, int viewLength) {
1469+
private void copyFromNotNull(ValueVector from, int fromIndex, int thisIndex, int viewLength) {
14631470
BitVectorHelper.setBit(validityBuffer, thisIndex);
14641471
final int start = thisIndex * ELEMENT_SIZE;
14651472
final int copyStart = fromIndex * ELEMENT_SIZE;
14661473
if (viewLength > INLINE_SIZE) {
1467-
final int bufIndex =
1468-
from.getDataBuffer()
1469-
.getInt(((long) fromIndex * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH);
1470-
final int dataOffset =
1471-
from.getDataBuffer()
1472-
.getInt(
1473-
((long) fromIndex * ELEMENT_SIZE)
1474-
+ LENGTH_WIDTH
1475-
+ PREFIX_WIDTH
1476-
+ BUF_INDEX_WIDTH);
1477-
final ArrowBuf dataBuf = ((BaseVariableWidthViewVector) from).dataBuffers.get(bufIndex);
1478-
final ArrowBuf thisDataBuf = allocateOrGetLastDataBuffer(viewLength);
1479-
1480-
viewBuffer.setBytes(start, from.getDataBuffer(), copyStart, LENGTH_WIDTH + PREFIX_WIDTH);
1481-
int writePosition = start + LENGTH_WIDTH + PREFIX_WIDTH;
1482-
// set buf id
1483-
viewBuffer.setInt(writePosition, dataBuffers.size() - 1);
1484-
writePosition += BUF_INDEX_WIDTH;
1485-
// set offset
1486-
viewBuffer.setInt(writePosition, (int) thisDataBuf.writerIndex());
1487-
1488-
thisDataBuf.setBytes(thisDataBuf.writerIndex(), dataBuf, dataOffset, viewLength);
1489-
thisDataBuf.writerIndex(thisDataBuf.writerIndex() + viewLength);
1474+
BaseVariableWidthViewVector fromVector = (BaseVariableWidthViewVector) from;
1475+
fromVector.getData(
1476+
fromIndex,
1477+
(dataBuf, dataOffset, dataLength) -> {
1478+
assert dataLength == viewLength;
1479+
viewBuffer.setBytes(
1480+
start, fromVector.getDataBuffer(), copyStart, LENGTH_WIDTH + PREFIX_WIDTH);
1481+
//noinspection resource
1482+
final ArrowBuf thisDataBuf = allocateOrGetLastDataBuffer(viewLength);
1483+
int writePosition = start + LENGTH_WIDTH + PREFIX_WIDTH;
1484+
// set buf id
1485+
viewBuffer.setInt(writePosition, dataBuffers.size() - 1);
1486+
writePosition += BUF_INDEX_WIDTH;
1487+
// set offset
1488+
viewBuffer.setInt(writePosition, (int) thisDataBuf.writerIndex());
1489+
thisDataBuf.setBytes(thisDataBuf.writerIndex(), dataBuf, dataOffset, viewLength);
1490+
thisDataBuf.writerIndex(thisDataBuf.writerIndex() + viewLength);
1491+
return null;
1492+
});
14901493
} else {
14911494
from.getDataBuffer().getBytes(copyStart, viewBuffer, start, ELEMENT_SIZE);
14921495
}
@@ -1502,16 +1505,12 @@ public ArrowBufPointer getDataPointer(int index, ArrowBufPointer reuse) {
15021505
if (isNull(index)) {
15031506
reuse.set(null, 0, 0);
15041507
} else {
1505-
int length = getValueLength(index);
1506-
if (length < INLINE_SIZE) {
1507-
int start = index * ELEMENT_SIZE + LENGTH_WIDTH;
1508-
reuse.set(viewBuffer, start, length);
1509-
} else {
1510-
final int bufIndex =
1511-
viewBuffer.getInt(((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH);
1512-
ArrowBuf dataBuf = dataBuffers.get(bufIndex);
1513-
reuse.set(dataBuf, 0, length);
1514-
}
1508+
getData(
1509+
index,
1510+
(buf, offset, length) -> {
1511+
reuse.set(buf, offset, length);
1512+
return null;
1513+
});
15151514
}
15161515
return reuse;
15171516
}
@@ -1526,19 +1525,45 @@ public int hashCode(int index, ArrowBufHasher hasher) {
15261525
if (isNull(index)) {
15271526
return ArrowBufPointer.NULL_HASH_CODE;
15281527
}
1529-
final int length = getValueLength(index);
1530-
if (length < INLINE_SIZE) {
1531-
int start = index * ELEMENT_SIZE + LENGTH_WIDTH;
1532-
return ByteFunctionHelpers.hash(hasher, this.getDataBuffer(), start, start + length);
1533-
} else {
1534-
final int bufIndex =
1528+
return getData(
1529+
index,
1530+
(buf, offset, length) -> ByteFunctionHelpers.hash(hasher, buf, offset, offset + length));
1531+
}
1532+
1533+
@FunctionalInterface
1534+
protected interface ViewElementConsumer<T> {
1535+
T consume(ArrowBuf buf, int offset, int length);
1536+
}
1537+
1538+
/** Helper to get a single view value with sanity checking. */
1539+
protected <T> T getData(int index, ViewElementConsumer<T> consumer) {
1540+
final int dataLength = getValueLength(index);
1541+
final ArrowBuf dataBuffer;
1542+
final int dataOffset;
1543+
if (dataLength > INLINE_SIZE) {
1544+
final int bufferIndex =
15351545
viewBuffer.getInt(((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH);
1536-
final int dataOffset =
1546+
dataOffset =
15371547
viewBuffer.getInt(
15381548
((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH + BUF_INDEX_WIDTH);
1539-
ArrowBuf dataBuf = dataBuffers.get(bufIndex);
1540-
return ByteFunctionHelpers.hash(hasher, dataBuf, dataOffset, dataOffset + length);
1549+
dataBuffer = dataBuffers.get(bufferIndex);
1550+
} else {
1551+
dataBuffer = viewBuffer;
1552+
dataOffset = index * ELEMENT_SIZE + BUF_INDEX_WIDTH;
1553+
}
1554+
if (dataOffset < 0
1555+
|| dataLength < 0
1556+
|| ((long) dataOffset + (long) dataLength) > dataBuffer.capacity()) {
1557+
// In this case we don't check BOUNDS_CHECKING_ENABLED
1558+
// Likely this check is redundant, but we are trying to check eagerly before downstream code
1559+
// potentially
1560+
// tries to allocate based on the given dataLength
1561+
throw new IndexOutOfBoundsException(
1562+
String.format(
1563+
"index: %d, length: %d (expected: range(0, %d))",
1564+
dataOffset, dataLength, dataBuffer.capacity()));
15411565
}
1566+
return consumer.consume(dataBuffer, dataOffset, dataLength);
15421567
}
15431568

15441569
/**
@@ -1555,42 +1580,16 @@ public int hashCode(int index, ArrowBufHasher hasher) {
15551580
* @return byte array containing the data of the element
15561581
*/
15571582
protected byte[] getData(int index) {
1558-
final int dataLength = getValueLength(index);
1559-
byte[] result = new byte[dataLength];
1560-
if (dataLength > INLINE_SIZE) {
1561-
// data is in the data buffer
1562-
// get buffer index
1563-
final int bufferIndex =
1564-
viewBuffer.getInt(((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH);
1565-
// get data offset
1566-
final int dataOffset =
1567-
viewBuffer.getInt(
1568-
((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH + BUF_INDEX_WIDTH);
1569-
dataBuffers.get(bufferIndex).getBytes(dataOffset, result, 0, dataLength);
1570-
} else {
1571-
// data is in the view buffer
1572-
viewBuffer.getBytes((long) index * ELEMENT_SIZE + BUF_INDEX_WIDTH, result, 0, dataLength);
1573-
}
1574-
return result;
1583+
return getData(index, ArrowBuf::getBytesAsArray);
15751584
}
15761585

15771586
protected void getData(int index, ReusableBuffer<?> buffer) {
1578-
final int dataLength = getValueLength(index);
1579-
if (dataLength > INLINE_SIZE) {
1580-
// data is in the data buffer
1581-
// get buffer index
1582-
final int bufferIndex =
1583-
viewBuffer.getInt(((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH);
1584-
// get data offset
1585-
final int dataOffset =
1586-
viewBuffer.getInt(
1587-
((long) index * ELEMENT_SIZE) + LENGTH_WIDTH + PREFIX_WIDTH + BUF_INDEX_WIDTH);
1588-
ArrowBuf dataBuf = dataBuffers.get(bufferIndex);
1589-
buffer.set(dataBuf, dataOffset, dataLength);
1590-
} else {
1591-
// data is in the value buffer
1592-
buffer.set(viewBuffer, ((long) index * ELEMENT_SIZE) + BUF_INDEX_WIDTH, dataLength);
1593-
}
1587+
getData(
1588+
index,
1589+
(buf, offset, length) -> {
1590+
buffer.set(buf, offset, length);
1591+
return null;
1592+
});
15941593
}
15951594

15961595
@Override

‎vector/src/test/java/org/apache/arrow/vector/TestVariableWidthViewVector.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2921,4 +2921,21 @@ public void testValidate() {
29212921
assertTrue(e.getMessage().contains("Not enough capacity for data buffer"));
29222922
}
29232923
}
2924+
2925+
@Test
2926+
public void testValidateInvalidOffsets() {
2927+
try (final ViewVarCharVector vector = new ViewVarCharVector("v", allocator)) {
2928+
vector.allocateNew(16, 1);
2929+
vector.allocateOrGetLastDataBuffer(8);
2930+
var offsets = vector.getDataBuffer();
2931+
offsets.setInt(0, 64);
2932+
offsets.setInt(4, 0);
2933+
offsets.setInt(8, 0);
2934+
offsets.setInt(12, 1024);
2935+
vector.setValueCount(1);
2936+
vector.setIndexDefined(0);
2937+
var e = assertThrows(IndexOutOfBoundsException.class, vector::validateFull);
2938+
assertTrue(e.getMessage().contains("index: 1024"));
2939+
}
2940+
}
29242941
}

0 commit comments

Comments
 (0)