Skip to content

Commit 5fadc23

Browse files
committed
Harden bulk TTree reads against corrupted baskets
1 parent 940909c commit 5fadc23

3 files changed

Lines changed: 52 additions & 18 deletions

File tree

Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "Framework/EndOfStreamContext.h"
3333
#include "Framework/DeviceSpec.h"
3434
#include "Framework/RawDeviceService.h"
35+
#include "Framework/RuntimeError.h"
3536
#include "Framework/DataSpecUtils.h"
3637
#include "Framework/MessageContext.h"
3738
#include "Framework/Signpost.h"
@@ -132,6 +133,8 @@ static std::string describeException(std::exception const& exception)
132133
std::rethrow_if_nested(exception);
133134
} catch (std::exception const& nested) {
134135
description += ": " + describeException(nested);
136+
} catch (RuntimeErrorRef const& ref) {
137+
description += ": " + std::string(error_from_ref(ref).what);
135138
} catch (...) {
136139
description += ": unknown exception";
137140
}
@@ -275,6 +278,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
275278
auto skippedTimeframes = ++totalInvalidReadSkipped;
276279
LOGP(error, "Invalid AOD read for table {}: fileCounter {}, timeFrame {}. Skipping timeframe (skipped timeframes: {}). Reason: {}",
277280
concrete.origin.as<std::string>(), fcnt, ntf, skippedTimeframes, describeException(e));
281+
clean_all_runtime_errors();
278282
didir->markTimeFrameSkipped(header::DataHeader(concrete.description, concrete.origin, concrete.subSpec), ntf);
279283
arrowContext.clear();
280284
messageContext.discard();

Framework/AnalysisSupport/src/DataInputDirector.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,9 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh
566566
}
567567

568568
auto schemaOpt = format->Inspect(fullpath);
569+
if (!schemaOpt.ok()) {
570+
throw InvalidAODReadError(fmt::format("Unable to inspect tree {}: {}", treename, schemaOpt.status().ToString()));
571+
}
569572
auto physicalSchema = schemaOpt;
570573
std::vector<std::shared_ptr<arrow::Field>> fields;
571574
for (auto& original : (*schemaOpt)->fields()) {

Framework/AnalysisSupport/src/TTreePlugin.cxx

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,33 @@ arrow::Result<std::shared_ptr<arrow::Buffer>> TTreeDeferredReadOutputStream::Fin
187187

188188
arrow::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+
190209
auto 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

226243
auto 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

580598
auto 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

Comments
 (0)