[io] Improve checks when reading arrays with TBufferFile#21814
[io] Improve checks when reading arrays with TBufferFile#21814dpiparo wants to merge 2 commits intoroot-project:masterfrom
Conversation
io/io/inc/TBufferFile.h
Outdated
| @@ -268,6 +269,11 @@ class TBufferFile : public TBufferIO { | |||
|
|
|||
| //---------------------- TBufferFile inlines --------------------------------------- | |||
|
|
|||
| //______________________________________________________________________________ | |||
| inline bool TBufferFile::IsBadCollLen(Int_t len, Int_t n) const { | |||
| return n <= 0 || len <=0 || (size_t)len > (size_t)(fBufMax - fBufCur); | |||
There was a problem hiding this comment.
Side note: the value zero is a totally valid (not bad) value that just happens to be treated the same way as the invalid values (i.e. read nothing for the array)
There was a problem hiding this comment.
Do you have a better name in mind? Maybe ShouldReadColl?
io/io/src/TBufferFile.cxx
Outdated
| @@ -818,7 +822,7 @@ Int_t TBufferFile::ReadArray(Int_t *&ii) | |||
| *this >> n; | |||
| Int_t l = sizeof(Int_t)*n; | |||
|
|
|||
| if (l <= 0 || l > fBufSize) return 0; | |||
| if (IsBadCollLen(l, n)) return 0; | |||
There was a problem hiding this comment.
| if (IsBadCollLen(l, n)) return 0; | |
| if (!n || ExceedsBufferCapacity<Int_t>(n)) return 0; |
and remove the variable l entirely.
If preferring not template:
| if (IsBadCollLen(l, n)) return 0; | |
| if (!n || ExceedsBufferCapacity(n, sizeof(Int_t)) return 0; |
There was a problem hiding this comment.
Are you suggesting to change the checks previously in place?
Suppose l is dropped. What would happen to the case where n is positive, but l, equal to n*sizeof(type) is negative?
Test Results 22 files 22 suites 3d 5h 1m 13s ⏱️ For more details on these failures, see this check. Results for commit bc06948. |
io/io/inc/TBufferFile.h
Outdated
| @@ -268,6 +269,11 @@ class TBufferFile : public TBufferIO { | |||
|
|
|||
| //---------------------- TBufferFile inlines --------------------------------------- | |||
|
|
|||
| //______________________________________________________________________________ | |||
| inline bool TBufferFile::IsBadCollLen(Int_t len, Int_t n) const { | |||
There was a problem hiding this comment.
Can we have either better named parameters or a doc comment for their meaning?
io/io/src/TBufferFile.cxx
Outdated
| @@ -290,6 +290,10 @@ void TBufferFile::ReadCharStar(char* &s) | |||
| Int_t nch; | |||
| *this >> nch; | |||
| if (nch > 0) { | |||
| if (nch == std::numeric_limits<Int_t>::max()) { | |||
| Error("ReadCharStar", "The number of characters nch is equal to max int!"); | |||
There was a problem hiding this comment.
I would rephrase this as something like "Cannot allocate buffer: maximum capacity exceeded (%d bytes)"
|
The comments above are relevant, and easy to address. Thanks. I think that the tests failures are due to an issue of the file, which was never detected before. This simple program, that can be run as a macro too, reveals it: #include "TFile.h"
#include "TTree.h"
/*
Invalid read happens at
- file "root://eospublic.cern.ch//eos/root-eos/testfiles/atlasFlushed.root"
- tree "CollectionTree"
- branch m_TrigMuonEFInfoContainers.vector<TPObjRef>
- basket 23
#0 0x00007ffff448d02c in __pthread_kill_implementation () from /lib64/libc.so.6
#1 0x00007ffff443fb86 in raise () from /lib64/libc.so.6
#2 0x00007ffff4429873 in abort () from /lib64/libc.so.6
#3 0x00007ffff48a1b21 in __gnu_cxx::__verbose_terminate_handler() [clone .cold] () from /lib64/libstdc++.so.6
#4 0x00007ffff48ad53c in __cxxabiv1::__terminate(void (*)()) () from /lib64/libstdc++.so.6
#5 0x00007ffff48ad5a7 in std::terminate() () from /lib64/libstdc++.so.6
#6 0x00007ffff48ad809 in __cxa_throw () from /lib64/libstdc++.so.6
#7 0x00007ffff72c639b in TBufferFile::IsBadCollLen (this=0x9112330, len=256, n=64) at /foo/root/io/io/src/TBufferFile.cxx:68
Here: len is 256, fBufMax 152116592, fBufCur 152116338, fBufMax-fBufCur = 254: two extra bytes
#8 0x00007ffff72bbcea in TBufferFile::ReadArray (this=0x9112330, ii=@0x90e3358: 0x0) at /foo/root/io/io/src/TBufferFile.cxx:839
#9 0x00007ffff634314d in TBasket::ReadBasketBuffers (this=0x90e3280, pos=298023188, len=3586, file=0x1723640) at /foo/root/tree/tree/src/TBasket.cxx:671
#10 0x00007ffff6352a21 in TBranch::GetBasketImpl (this=0x31aec30, basketnumber=23, user_buffer=0x0) at /foo/root/tree/tree/src/TBranch.cxx:1267
#11 0x00007ffff6353198 in TBranch::GetBasketAndFirst (this=0x31aec30, basket=@0x7fffffffcc80: 0x0, first=@0x7fffffffcc78: 1100, user_buffer=0x0)
at /foo/root/tree/tree/src/TBranch.cxx:1389
#12 0x00007ffff6353fb0 in TBranch::GetEntry (this=0x31aec30, entry=1100, getall=0) at /foo/root/tree/tree/src/TBranch.cxx:1715
#13 0x00007ffff6366ec4 in TBranchElement::GetEntry (this=0x31aec30, entry=1100, getall=0) at /foo/root/tree/tree/src/TBranchElement.cxx:2826
#14 0x00007ffff6366a34 in TBranchElement::GetEntry (this=0x31ae610, entry=1100, getall=0) at /foo/root/tree/tree/src/TBranchElement.cxx:2761
#15 0x00007ffff6366a34 in TBranchElement::GetEntry (this=0x31adfd0, entry=1100, getall=0) at /foo/root/tree/tree/src/TBranchElement.cxx:2761
#16 0x00007ffff63e4347 in operator() (__closure=0x7fffffffd2b0) at /foo/root/tree/tree/src/TTree.cxx:5741
#17 0x00007ffff63e44d5 in TTree::GetEntry (this=0x13dd370, entry=1100, getall=0) at /foo/root/tree/tree/src/TTree.cxx:5819
#18 0x00000000004021e0 in main () at repro.cpp:9
(gdb) f 10
#10 0x00007ffff6352a21 in TBranch::GetBasketImpl (this=0x31aec30, basketnumber=23, user_buffer=0x0) at /foo/root/tree/tree/src/TBranch.cxx:1267
*/
int repro()
{
auto f = std::unique_ptr<TFile>{TFile::Open("root://eospublic.cern.ch//eos/root-eos/testfiles/atlasFlushed.root")};
auto t = f->Get<TTree>("CollectionTree");
// not strictly necessary, but just to illustrate the matter
t->SetBranchStatus("*",0);
t->SetBranchStatus("m_TrigMuonEFInfoContainers.vector<TPObjRef>", 1);
t->GetEntry(1100); // Invalid Read!
return 0;
}
int main(){return repro();} |
b224ca1 to
6cbd362
Compare
i.e. in the TBufferFile::Read*Array* methods.
Previously, the collection length was checked to be positive
and that it was less than the total buffer size.
Those checks have been improved with a battery of 3 checks:
1. The collection's number of elements is checked to be positive
2. The collection length is checked to be positive
3. The collection length is checked to be less than the portion
of the buffer that remains to be read
The test roottest-root-io-prefetching-run had to be modified as well
because of what seems to be a corrupted input file.
thanks also to @Sebasteuo
No description provided.