Skip to content

Commit 93f8d5f

Browse files
dfa1claude
andcommitted
fix(reader): cap DType-tree recursion depth
convertDType recursed through Struct/List/FixedSizeList/Extension with no depth guard, unlike convertLayout. A crafted or self-referential FlatBuffer DType blob drove unbounded recursion into StackOverflowError — an Error, so it escaped the parseBlobs/open VortexException sanitization and leaked the reader's memory-mapped Arena. Add MAX_DTYPE_DEPTH (64) and thread depth, same shape as the existing layout-tree guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cb0db8c commit 93f8d5f

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

reader/src/main/java/io/github/dfa1/vortex/reader/PostscriptParser.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ final class PostscriptParser {
3434
/// real encoding's metadata footprint (the largest is FSST's symbol table at ~32 KiB).
3535
static final int MAX_LAYOUT_METADATA_BYTES = 4 * 1024 * 1024;
3636

37+
/// Hard cap on DType-tree recursion depth. A `DType` nests through Struct fields, List/
38+
/// FixedSizeList element types, and Extension storage types; like the layout tree, a crafted
39+
/// or self-referential FlatBuffer can drive [#convertDType(io.github.dfa1.vortex.core.fbs.FbsDType, int)]
40+
/// into unbounded recursion and a [StackOverflowError] — which, being an `Error`, would escape
41+
/// the [VortexException] sanitization and leak the reader's memory-mapped Arena. 64 is well past
42+
/// any real schema's nesting.
43+
static final int MAX_DTYPE_DEPTH = 64;
44+
3745
private PostscriptParser() {
3846
}
3947

@@ -110,7 +118,7 @@ static ParsedFile parseBlobs(MemorySegment footerBuf, MemorySegment layoutBuf, M
110118

111119
DType dtype = null;
112120
if (dtypeBuf != null && dtypeBuf.byteSize() > 0) {
113-
dtype = convertDType(io.github.dfa1.vortex.core.fbs.FbsDType.getRootAsFbsDType(dtypeBuf));
121+
dtype = convertDType(io.github.dfa1.vortex.core.fbs.FbsDType.getRootAsFbsDType(dtypeBuf), 0);
114122
}
115123

116124
return new ParsedFile(footer, dtype, layout);
@@ -188,7 +196,11 @@ private static Layout convertLayout(io.github.dfa1.vortex.core.fbs.FbsLayout l,
188196
return new Layout(encodingId, l.rowCount(), metadata, List.copyOf(children), List.copyOf(segments));
189197
}
190198

191-
private static DType convertDType(io.github.dfa1.vortex.core.fbs.FbsDType fbs) {
199+
private static DType convertDType(io.github.dfa1.vortex.core.fbs.FbsDType fbs, int depth) {
200+
if (depth > MAX_DTYPE_DEPTH) {
201+
throw new VortexException(
202+
"DType tree depth exceeds limit (" + MAX_DTYPE_DEPTH + ")");
203+
}
192204
int typeType = fbs.typeType();
193205
return switch (typeType) {
194206
case FbsType.FbsNull -> new DType.Null(true);
@@ -224,21 +236,21 @@ private static DType convertDType(io.github.dfa1.vortex.core.fbs.FbsDType fbs) {
224236
names.add(s.names(i));
225237
}
226238
for (int i = 0; i < s.dtypesLength(); i++) {
227-
types.add(convertDType(s.dtypes(i)));
239+
types.add(convertDType(s.dtypes(i), depth + 1));
228240
}
229241
yield new DType.Struct(List.copyOf(names), List.copyOf(types), s.nullable());
230242
}
231243
case FbsType.FbsList -> {
232244
var l = fbs.type(new io.github.dfa1.vortex.core.fbs.FbsList());
233-
yield new DType.List(convertDType(l.elementType()), l.nullable());
245+
yield new DType.List(convertDType(l.elementType(), depth + 1), l.nullable());
234246
}
235247
case FbsType.FbsFixedSizeList -> {
236248
var fsl = fbs.type(new FbsFixedSizeList());
237-
yield new DType.FixedSizeList(convertDType(fsl.elementType()), (int) fsl.size(), fsl.nullable());
249+
yield new DType.FixedSizeList(convertDType(fsl.elementType(), depth + 1), (int) fsl.size(), fsl.nullable());
238250
}
239251
case FbsType.FbsExtension -> {
240252
var e = fbs.type(new FbsExtension());
241-
DType storage = convertDType(e.storageDtype());
253+
DType storage = convertDType(e.storageDtype(), depth + 1);
242254
yield new DType.Extension(
243255
e.id(),
244256
storage,

0 commit comments

Comments
 (0)