Skip to content

Commit adc445e

Browse files
dfa1claude
andcommitted
fix(zstd): bounds-check VarBin length prefixes
buildVarBin and buildScatteredVarBin read a 4-byte length prefix and advanced the cursor by it with no validation. A crafted decompressed payload with a negative or oversized length overran the segment, surfacing as a raw IndexOutOfBoundsException instead of a VortexException. Route every prefix read through a readVarBinLen helper that range-checks the prefix and its trailing bytes against the source segment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent feac99b commit adc445e

1 file changed

Lines changed: 19 additions & 2 deletions

File tree

reader/src/main/java/io/github/dfa1/vortex/reader/decode/ZstdEncodingDecoder.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ private static VarBinArray buildScatteredVarBin(
123123
long scanPos = 0;
124124
for (long i = 0; i < rowCount; i++) {
125125
if (validity.getBoolean(i)) {
126-
int len = validValues.get(PTypeIO.LE_INT, scanPos);
126+
int len = readVarBinLen(validValues, scanPos);
127127
scanPos += 4L + len;
128128
totalDataBytes += len;
129129
}
@@ -247,11 +247,28 @@ private static Array buildPrimitive(DType.Primitive dt, long n, MemorySegment de
247247
};
248248
}
249249

250+
/// Reads a 4-byte little-endian length prefix at `pos` from a decompressed VarBin payload and
251+
/// validates that both the prefix and the `len` bytes that follow lie within `src`. Without this,
252+
/// a crafted payload with a negative or oversized length would advance the cursor out of bounds
253+
/// and surface as a raw [IndexOutOfBoundsException] instead of a
254+
/// [io.github.dfa1.vortex.core.error.VortexException].
255+
///
256+
/// @param src the decompressed VarBin payload segment
257+
/// @param pos byte offset of the length prefix within `src`
258+
/// @return the validated element length in bytes
259+
private static int readVarBinLen(MemorySegment src, long pos) {
260+
IoBounds.checkRange(pos, 4, src.byteSize());
261+
int len = src.get(PTypeIO.LE_INT, pos);
262+
// checkRange rejects len < 0 and a [pos+4, pos+4+len) range that overruns src.
263+
IoBounds.checkRange(pos + 4L, len, src.byteSize());
264+
return len;
265+
}
266+
250267
private static VarBinArray buildVarBin(DType dtype, long n, MemorySegment decompressed, DecodeContext ctx) {
251268
long totalDataBytes = 0;
252269
long pos = 0;
253270
for (long i = 0; i < n; i++) {
254-
int len = decompressed.get(PTypeIO.LE_INT, pos);
271+
int len = readVarBinLen(decompressed, pos);
255272
pos += 4 + len;
256273
totalDataBytes += len;
257274
}

0 commit comments

Comments
 (0)