@@ -187,15 +187,33 @@ arrow::Result<std::shared_ptr<arrow::Buffer>> TTreeDeferredReadOutputStream::Fin
187187
188188arrow::Result<int64_t > TTreeDeferredReadOutputStream::Tell () const { return position_; }
189189
190+ // Bulk reads follow the basket boundaries in the file, so a corrupted file must not overrun the target buffers.
191+ auto checkReadRange = [](ReadOps const & op, int readEntries, int readLast) {
192+ if (readLast <= 0 ) {
193+ throw runtime_error_f (" Error while reading branch %s starting from %d: got %d entries." , op.branch ->GetName (), readEntries, readLast);
194+ }
195+ if (static_cast <int64_t >(readEntries) + readLast > op.rootBranchEntries ) {
196+ throw runtime_error_f (" Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld." ,
197+ op.branch ->GetName (), readEntries, readLast, static_cast <long long >(op.rootBranchEntries ));
198+ }
199+ };
200+
201+ auto checkBasketBytes = [](ReadOps const & op, int readEntries, int64_t bytesNeeded, TBufferFile const & rootBuffer) {
202+ int64_t available = static_cast <int64_t >(rootBuffer.BufferSize ()) - rootBuffer.Length ();
203+ if (bytesNeeded < 0 || bytesNeeded > available) {
204+ throw runtime_error_f (" Basket of branch %s starting from %d holds %lld bytes, but %lld are needed." ,
205+ op.branch ->GetName (), readEntries, static_cast <long long >(available), static_cast <long long >(bytesNeeded));
206+ }
207+ };
208+
190209auto readValues = [](uint8_t * target, ReadOps& op, TBufferFile& rootBuffer) {
191210 int readEntries = 0 ;
192211 rootBuffer.Reset ();
193212 while (readEntries < op.rootBranchEntries ) {
194213 auto readLast = op.branch ->GetBulkRead ().GetEntriesSerialized (readEntries, rootBuffer);
195- if (readLast < 0 ) {
196- throw runtime_error_f (" Error while reading branch %s starting from %zu." , op.branch ->GetName (), readEntries);
197- }
214+ checkReadRange (op, readEntries, readLast);
198215 int size = readLast * op.listSize ;
216+ checkBasketBytes (op, readEntries, static_cast <int64_t >(size) * op.typeSize , rootBuffer);
199217 readEntries += readLast;
200218 bigEndianCopy (target, rootBuffer.GetCurrent (), size, op.typeSize );
201219 target += (ptrdiff_t )(size * op.typeSize );
@@ -211,10 +229,9 @@ auto readBoolValues = [](uint8_t* target, ReadOps& op, TBufferFile& rootBuffer)
211229 while (readEntries < op.rootBranchEntries ) {
212230 auto beginValue = readEntries;
213231 readLast = op.branch ->GetBulkRead ().GetBulkEntries (readEntries, rootBuffer);
214- if (readLast < 0 ) {
215- throw runtime_error_f (" Error while reading branch %s starting from %d." , op.branch ->GetName (), readEntries);
216- }
232+ checkReadRange (op, readEntries, readLast);
217233 int size = readLast * op.listSize ;
234+ checkBasketBytes (op, readEntries, size, rootBuffer);
218235 readEntries += readLast;
219236 for (int i = beginValue; i < beginValue + size; ++i) {
220237 auto value = static_cast <uint8_t >(rootBuffer.GetCurrent ()[i - beginValue] << (i % 8 ));
@@ -225,24 +242,25 @@ auto readBoolValues = [](uint8_t* target, ReadOps& op, TBufferFile& rootBuffer)
225242
226243auto readVLAValues = [](uint8_t * target, ReadOps& op, ReadOps const & offsetOp, TBufferFile& rootBuffer) {
227244 int readEntries = 0 ;
245+ // The offsets are only valid for as many entries as the size branch has.
246+ if (op.rootBranchEntries != offsetOp.rootBranchEntries ) {
247+ throw runtime_error_f (" Branch %s has %lld entries, but its size branch %s has %lld." ,
248+ op.branch ->GetName (), static_cast <long long >(op.rootBranchEntries ),
249+ offsetOp.branch ->GetName (), static_cast <long long >(offsetOp.rootBranchEntries ));
250+ }
228251 auto * tPtrOffset = reinterpret_cast <const int *>(offsetOp.targetBuffer ->data ());
229252 std::span<int const > const offsets{tPtrOffset, tPtrOffset + offsetOp.rootBranchEntries + 1 };
230253
231254 rootBuffer.Reset ();
232255 while (readEntries < op.rootBranchEntries ) {
233256 auto readLast = op.branch ->GetBulkRead ().GetEntriesSerialized (readEntries, rootBuffer);
234- if (readLast < 0 ) {
235- throw runtime_error_f (" Error while reading branch %s starting from %d." , op.branch ->GetName (), readEntries);
236- }
237- if (readEntries + readLast > op.rootBranchEntries ) {
238- throw runtime_error_f (" Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld." ,
239- op.branch ->GetName (), readEntries, readLast, static_cast <long long >(op.rootBranchEntries ));
240- }
257+ checkReadRange (op, readEntries, readLast);
241258 int size = offsets[readEntries + readLast] - offsets[readEntries];
242259 if (size < 0 ) {
243260 throw runtime_error_f (" Invalid offset range for branch %s: offsets[%d]=%d, offsets[%d]=%d." ,
244261 op.branch ->GetName (), readEntries, offsets[readEntries], readEntries + readLast, offsets[readEntries + readLast]);
245262 }
263+ checkBasketBytes (op, readEntries, static_cast <int64_t >(size) * op.typeSize , rootBuffer);
246264 readEntries += readLast;
247265 bigEndianCopy (target, rootBuffer.GetCurrent (), size, op.typeSize );
248266 target += (ptrdiff_t )(size * op.typeSize );
@@ -578,7 +596,7 @@ struct BranchFieldMapping {
578596};
579597
580598auto readOffsets = [](ReadOps& op, TBufferFile& rootBuffer) {
581- uint32_t offset = 0 ;
599+ int64_t offset = 0 ;
582600 std::span<int > offsets;
583601 int readEntries = 0 ;
584602 int count = 0 ;
@@ -589,14 +607,17 @@ auto readOffsets = [](ReadOps& op, TBufferFile& rootBuffer) {
589607 rootBuffer.Reset ();
590608 while (readEntries < op.rootBranchEntries ) {
591609 auto readLast = op.branch ->GetBulkRead ().GetEntriesSerialized (readEntries, rootBuffer);
592- if (readLast == -1 ) {
593- throw runtime_error_f (" Unable to read from branch %s." , op.branch ->GetName ());
594- }
610+ checkReadRange (op, readEntries, readLast);
611+ checkBasketBytes (op, readEntries, static_cast <int64_t >(readLast) * sizeof (uint32_t ), rootBuffer);
595612 readEntries += readLast;
596613 for (auto i = 0 ; i < readLast; ++i) {
597614 offsets[count++] = (int )offset;
598615 uint32_t raw = reinterpret_cast <uint32_t *>(rootBuffer.GetCurrent ())[i];
599616 offset += (std::endian::native == std::endian::little) ? __builtin_bswap32 (raw) : raw;
617+ // Arrow lists use 32 bit offsets, a larger total can only come from corrupted sizes.
618+ if (offset > INT32_MAX ) {
619+ throw runtime_error_f (" Invalid sizes for branch %s: offsets overflow at entry %d." , op.branch ->GetName (), count - 1 );
620+ }
600621 }
601622 }
602623 offsets[count] = (int )offset;
@@ -919,6 +940,9 @@ arrow::Result<std::shared_ptr<arrow::Schema>> TTreeFileFormat::Inspect(const arr
919940 // Notice that we abuse of the API here and do not release the TTree,
920941 // so that it's still managed by ROOT.
921942 auto tree = objectHandler->GetObjectAsOwner <TTree>().release ();
943+ if (tree == nullptr ) {
944+ return arrow::Status::IOError (" Unable to read tree " , source.path ());
945+ }
922946
923947 auto branches = tree->GetListOfBranches ();
924948 auto n = branches->GetEntries ();
@@ -928,6 +952,9 @@ arrow::Result<std::shared_ptr<arrow::Schema>> TTreeFileFormat::Inspect(const arr
928952 bool prevIsSize = false ;
929953 for (auto i = 0 ; i < n; ++i) {
930954 auto branch = static_cast <TBranch*>(branches->At (i));
955+ if (branch == nullptr || branch->GetListOfLeaves ()->At (0 ) == nullptr ) {
956+ return arrow::Status::IOError (" Invalid branch " , i, " in tree " , source.path ());
957+ }
931958 std::string name = branch->GetName ();
932959 if (prevIsSize && fields.back ()->name () != name + " _size" ) {
933960 throw runtime_error_f (" Unexpected layout for VLA container %s." , branch->GetName ());
@@ -951,7 +978,7 @@ arrow::Result<std::shared_ptr<arrow::Schema>> TTreeFileFormat::Inspect(const arr
951978 }
952979 }
953980
954- if (fields.back ()->name ().ends_with (" _size" )) {
981+ if (!fields. empty () && fields.back ()->name ().ends_with (" _size" )) {
955982 throw runtime_error_f (" Missing values for VLA indices %s." , fields.back ()->name ().c_str ());
956983 }
957984 return std::make_shared<arrow::Schema>(fields);
0 commit comments