From c438631f122d68b12bd1ae37f417a13c860b8510 Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Thu, 13 Aug 2026 12:40:54 -0700 Subject: [PATCH] fix(json): bound JSON reader nesting depth to prevent stack overflow (FB-2936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JSON block parser parses iteratively (kParseIterativeFlag) and cannot overflow, but the array/struct builders are finalized recursively (RawArrayBuilder::Finish / RawBuilderSet::Finish), so deeply nested input (e.g. {"a":{"a":{...}}} or [[[...]]]) overflows the native stack and crashes the process before any limit fires. Reject input nested deeper than kMaxNestingDepth (1000) in StartNested() — the single point where built (non-skipped) object/array nesting deepens for every handler — so the pathological case is unreachable by construction. Mirrors the existing thrift skip() recursion-depth guard. Co-Authored-By: Claude Opus 4.8 --- cpp/src/arrow/json/parser.cc | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cpp/src/arrow/json/parser.cc b/cpp/src/arrow/json/parser.cc index 53f856d8012e..1ae05767d328 100644 --- a/cpp/src/arrow/json/parser.cc +++ b/cpp/src/arrow/json/parser.cc @@ -827,7 +827,7 @@ class HandlerBase : public BlockParser, } auto struct_builder = Cast(builder_); absent_fields_stack_.Push(struct_builder->num_fields(), true); - StartNested(); + RETURN_NOT_OK(StartNested()); return struct_builder->Append(); } @@ -881,7 +881,7 @@ class HandlerBase : public BlockParser, if (ARROW_PREDICT_FALSE(builder_.kind != kind)) { return IllegallyChangedTo(kind); } - StartNested(); + RETURN_NOT_OK(StartNested()); // append to the list builder in EndArrayImpl builder_ = Cast(builder_)->value_builder(); return Status::OK(); @@ -897,10 +897,20 @@ class HandlerBase : public BlockParser, /// helper method for StartArray and StartObject /// adds the current builder to a stack so its /// children can be visited and parsed. - void StartNested() { + /// + /// Rejects input nested deeper than kMaxNestingDepth. The RapidJSON parse itself is iterative + /// (kParseIterativeFlag) and cannot overflow, but the array/struct builders are finalized + /// recursively (RawArrayBuilder::Finish / RawBuilderSet::Finish), so deeply + /// nested input would otherwise overflow the native stack. Guarding here — the single point where + /// built (non-skipped) nesting deepens for both objects and arrays — makes that unreachable. + Status StartNested() { + if (ARROW_PREDICT_FALSE(builder_stack_.size() >= kMaxNestingDepth)) { + return Status::Invalid("JSON nesting depth exceeds the maximum of ", kMaxNestingDepth); + } field_index_stack_.push_back(field_index_); field_index_ = -1; builder_stack_.push_back(builder_); + return Status::OK(); } /// helper method for EndArray and EndObject @@ -928,6 +938,11 @@ class HandlerBase : public BlockParser, return scalar_values_builder_.ReserveData(size - available_storage); } + // Maximum object/array nesting depth accepted before StartNested() rejects the input, to keep + // the recursive builder finalization (RawArrayBuilder::Finish / RawBuilderSet::Finish) from + // overflowing the native stack on deeply nested JSON. + static constexpr size_t kMaxNestingDepth = 1000; + Status status_; RawBuilderSet builder_set_; BuilderPtr builder_;