Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/cont/src/TArrayC.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of chars or bytes (8 bits per element).
*/

#include "TArrayC.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayC::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayC::Streamer", "TArrayC", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayD.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of doubles (64 bits per element).
*/

#include "TArrayD.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -150,6 +151,8 @@ void TArrayD::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayD::Streamer", "TArrayD", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayF.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of floats (32 bits per element).
*/

#include "TArrayF.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayF::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayF::Streamer", "TArrayF", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayI.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of integers (32 bits per element).
*/

#include "TArrayI.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayI::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayI::Streamer", "TArrayI", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayL.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of longs (32 or 64 bits per element).
*/

#include "TArrayL.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayL::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayL::Streamer", "TArrayL", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayL64.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of long64s (64 bits per element).
*/

#include "TArrayL64.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayL64::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayL64::Streamer", "TArrayL64", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
3 changes: 3 additions & 0 deletions core/cont/src/TArrayS.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Array of shorts (16 bits per element).
*/

#include "TArrayS.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBuffer.h"


Expand Down Expand Up @@ -149,6 +150,8 @@ void TArrayS::Streamer(TBuffer &b)
if (b.IsReading()) {
Int_t n;
b >> n;
// Each element occupies at least one byte on file.
n = ROOT::Internal::CheckCollectionSize(b, n, "TArrayS::Streamer", "TArrayS", [] { return true; });
Set(n);
b.ReadFastArray(fArray,n);
} else {
Expand Down
11 changes: 10 additions & 1 deletion core/cont/src/TClonesArray.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ When investigating misuse of TClonesArray, please make sure of the following:
#include "TClass.h"
#include "TObject.h"
#include "TObjectTable.h"
#include "ROOT/CheckCollectionSize.hxx"

#include <cstdio>
#include <limits>
#include <cstdlib>


Expand Down Expand Up @@ -779,9 +781,16 @@ void TClonesArray::Streamer(TBuffer &b)
}

b >> nobjects;
if (nobjects < 0)
if (nobjects < 0 && nobjects != std::numeric_limits<Int_t>::min())
nobjects = -nobjects; // still there for backward compatibility
b >> fLowerBound;
// Without bypassing the streamer, each object starts with a byte telling whether it is present. Otherwise, the
// objects are read below with the layout of fClass, which stays the target class if the user changed it.
nobjects = ROOT::Internal::CheckCollectionSize(b, nobjects, "TClonesArray::Streamer", "TClonesArray", [&] {
TClass *readClass = fClass && classv != fClass->GetName() ? fClass : cl;
return !CanBypassStreamer() || b.TestBit(TBuffer::kCannotHandleMemberWiseStreaming) ||
ROOT::Internal::MemberWiseElementsUseBytes(readClass->GetStreamerInfo(clv));
});
if (!fClass) {
fClass = cl;
if (!fKeep) {
Expand Down
4 changes: 4 additions & 0 deletions core/cont/src/TObjArray.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ set to the owner of its own content.
#include "TROOT.h"
#include "TBuffer.h"
#include "TVirtualMutex.h"
#include "ROOT/CheckCollectionSize.hxx"
#include <cstdlib>


Expand Down Expand Up @@ -460,6 +461,9 @@ void TObjArray::Streamer(TBuffer &b)

b >> nobjects;
b >> fLowerBound;
// Each object occupies at least one byte on file, as it is stored as a pointer, i.e. a tag of 4 bytes.
nobjects =
ROOT::Internal::CheckCollectionSize(b, nobjects, "TObjArray::Streamer", "TObjArray", [] { return true; });
if (nobjects >= fSize) Expand(nobjects);
fLast = -1;
TObject *obj;
Expand Down
4 changes: 4 additions & 0 deletions core/cont/src/TRefArray.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Fatal in <TProcessID::AddProcessID>: Maximum number of TProcessID (65535) has be
#include "TRefTable.h"
#include "TBuffer.h"
#include "TError.h"
#include "ROOT/CheckCollectionSize.hxx"
#include "TBits.h"


Expand Down Expand Up @@ -524,6 +525,9 @@ void TRefArray::Streamer(TBuffer &R__b)
fName.Streamer(R__b);
R__b >> nobjects;
R__b >> fLowerBound;
// Each object occupies at least one byte on file, as it is stored as its unique ID of 4 bytes.
nobjects =
ROOT::Internal::CheckCollectionSize(R__b, nobjects, "TRefArray::Streamer", "TRefArray", [] { return true; });
if (nobjects >= fSize) Expand(nobjects);
fLast = -1;
R__b >> pidf;
Expand Down
2 changes: 2 additions & 0 deletions core/meta/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ target_sources(Core PRIVATE
target_include_directories(Core
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
PRIVATE
res
)

ROOT_INSTALL_HEADERS()
Expand Down
122 changes: 122 additions & 0 deletions core/meta/res/ROOT/CheckCollectionSize.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#ifndef ROOT_CheckCollectionSize
#define ROOT_CheckCollectionSize

// Private header shared by libCore and libRIO.

#include "RtypesCore.h"
#include "TBuffer.h"
#include "TError.h"
#include "TObjArray.h"
#include "TStreamerElement.h"
#include "TVirtualStreamerInfo.h"

namespace ROOT {
namespace Internal {

////////////////////////////////////////////////////////////////////////////////
/// Validate the number of elements of a collection that was just read from `buf`.
///
/// The collection size is stored as an Int_t but consumed as an unsigned value
/// (TVirtualCollectionProxy::Allocate, std::vector::resize), so a negative value
/// coming from corrupt data would turn into a request for billions of elements.
/// Report the problem from `location` and read the collection as empty instead.
inline Int_t CheckCollectionSize(TBuffer &buf, Int_t nElements, const char *location, const char *collectionName)
{
if (R__unlikely(nElements < 0)) {
::Error(location,
"Corrupt data: the collection of type %s read from %s has a negative number of elements (%d); "
"reading it as empty.",
collectionName ? collectionName : "(unknown)",
buf.GetParent() ? buf.GetParent()->GetName() : "memory/socket", nElements);
return 0;
}
return nElements;
}

////////////////////////////////////////////////////////////////////////////////
/// Validate the number of elements of a collection that was just read from `buf`.
///
/// In addition to the checks of the overload above, reject sizes larger than
/// the number of bytes left in the buffer if each element is known to occupy
/// at least one byte on file. Such a size can only come from corrupt data, and
/// unlike a negative one it is not caught by Allocate or resize, which would
/// try to allocate and initialise up to 2^31 elements.
///
/// \param[in] elementsUseBytes Callable returning whether each element occupies
/// at least one byte on file. It is only called for sizes larger than
/// the remaining bytes, so it does not slow down reading valid data.
template <typename ElementsUseBytes>
inline Int_t CheckCollectionSize(TBuffer &buf, Int_t nElements, const char *location, const char *collectionName,
ElementsUseBytes &&elementsUseBytes)
{
nElements = CheckCollectionSize(buf, nElements, location, collectionName);
const Long64_t remaining = (Long64_t)buf.BufferSize() - buf.Length();
// TBufferText and its subclasses do not keep the data in the buffer. TBufferSQL inherits from TBufferFile
// without doing so either, but TTreeSQL does not support branches of collections or objects.
if (R__unlikely(nElements > remaining) && buf.InheritsFrom("TBufferFile") && elementsUseBytes()) {
::Error(location,
"Corrupt data: the collection of type %s read from %s has %d elements, but only %lld bytes are left "
"in the buffer; reading it as empty.",
collectionName ? collectionName : "(unknown)",
buf.GetParent() ? buf.GetParent()->GetName() : "memory/socket", nElements, remaining);
return 0;
}
return nElements;
}

////////////////////////////////////////////////////////////////////////////////
/// Return whether each element of a collection streamed member-wise with the
/// on-file layout `info` occupies at least one byte on file.
///
/// This is the case as soon as one of the data members is streamed as a
/// basic type or an array of them, a pointer written with an object tag, a
/// string or an STL collection. Members of class type are not considered: an
/// object can occupy zero bytes, e.g. with a custom streamer. Neither are
/// pointers marked `//->`, whose object is streamed in place.
inline bool MemberWiseElementsUseBytes(const TVirtualStreamerInfo *info)
{
if (!info || !info->GetElements())
return false;
for (const auto *obj : *info->GetElements()) {
const auto *element = dynamic_cast<const TStreamerElement *>(obj);
// Artificial elements are set by I/O customization rules, they do not read from the buffer, and custom member
// streamers can read anything.
if (!element || dynamic_cast<const TStreamerArtificial *>(element) || element->GetStreamer())
continue;
const Int_t type = element->GetType();
switch (type) {
case TVirtualStreamerInfo::kObjectP:
case TVirtualStreamerInfo::kTString:
case TVirtualStreamerInfo::kTObject:
case TVirtualStreamerInfo::kTNamed:
case TVirtualStreamerInfo::kAnyP:
case TVirtualStreamerInfo::kAnyPnoVT:
case TVirtualStreamerInfo::kSTLp: return true;
default:
if (type > TVirtualStreamerInfo::kBase && type < TVirtualStreamerInfo::kObject) {
// A basic type, an array of them (kOffsetL), or a pointer to an array of them (kOffsetP), which starts
// with a byte telling whether the array is present.
const Int_t basicType = type % TVirtualStreamerInfo::kOffsetL;
if (basicType >= TVirtualStreamerInfo::kChar && basicType <= TVirtualStreamerInfo::kFloat16 &&
basicType != TVirtualStreamerInfo::kLegacyChar)
return true;
} else if (type >= TVirtualStreamerInfo::kSTL && type <= TVirtualStreamerInfo::kSTLstring) {
return true;
}
}
}
return false;
}

} // namespace Internal
} // namespace ROOT

#endif
54 changes: 54 additions & 0 deletions io/io/src/ObjectWiseElementsUseBytes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#ifndef ROOT_ObjectWiseElementsUseBytes
#define ROOT_ObjectWiseElementsUseBytes

#include "TDataType.h"
#include "TDictionary.h"
#include "TGenCollectionProxy.h"

namespace ROOT {
namespace Internal {

////////////////////////////////////////////////////////////////////////////////
/// Return whether an element described by `value` occupies at least one byte on
/// file when streamed object-wise by a collection proxy.
///
/// Objects of class type can occupy zero bytes, e.g. with a custom streamer, and
/// pointers to fundamental types are not streamed at all.
inline bool ObjectWiseElementUsesBytes(const TGenCollectionProxy::Value *value)
{
if (!value)
return false;
switch (value->fCase) {
case kIsFundamental:
case kIsEnum:
// These kinds are not supported, and are not streamed.
return value->fKind != kchar && value->fKind != kNoType_t && value->fKind != kOther_t;
case TGenCollectionProxy::kBIT_ISSTRING:
case 0U | kIsPointer | kIsClass:
case 0U | kIsPointer | TGenCollectionProxy::kBIT_ISSTRING:
case 0U | kIsPointer | TGenCollectionProxy::kBIT_ISTSTRING | kIsClass: return true;
default: return false;
}
}

////////////////////////////////////////////////////////////////////////////////
/// Return whether each element of a collection proxy with the given key (for
/// maps, nullptr otherwise) and value descriptors occupies at least one byte on
/// file when streamed object-wise.
inline bool ObjectWiseElementsUseBytes(const TGenCollectionProxy::Value *key, const TGenCollectionProxy::Value *value)
{
return ObjectWiseElementUsesBytes(key) || ObjectWiseElementUsesBytes(value);
}

} // namespace Internal
} // namespace ROOT

#endif
Loading
Loading