@@ -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