If the DynArray within an XMLPrinter object carries 2 gigabytes of
data or more, XMLPrinter::CStrSize returns a truncated result. If a
program casts this back to size_t without thought, sign extension
leads to bad things(tm).
```c++
int main()
{
tinyxml2::XMLDocument doc;
doc.InsertEndChild(doc.NewDeclaration());
auto root = doc.NewElement("root");
size_t sz = 0x80000002;
auto blank = new char[sz];
memset(blank, ' ', sz);
blank[sz-1]='\0';
root->SetText(blank);
doc.InsertEndChild(root);
tinyxml2::XMLPrinter printer(nullptr);
doc.Print(&printer);
std::string_view sv{printer.CStr(), static_cast<size_t>(printer.CStrSize())};
// sv.size() is way too big, causing overflows on access
std::string dup(sv); // boom
}
```
Fixes: 2.0.2-873-geb3ab0d
The expression ``const int newAllocated = cap * 2;`` easily causes
overflow, as soon as the input is 1.0 GiB. This goes unnoticed because
release builds of tinyxml2 do not have active assertions.
The change in commit 9.0.0-20-g8fd6cc6 did not do anything useful;
the signed multiplication overflow (and thus undefined behavior)
still occurs.
Using ``int`` in this class is really archaic, because it limits the
class to a gigabyte even on 64-bit platforms.
The multiplication overflow check also needs to include sizeof(T),
otherwise you can run into unsigned multiplication overflow (defined,
but undesirable) in the memcpy() call.
testcase:
int main()
{
tinyxml2::XMLDocument doc;
doc.InsertEndChild(doc.NewDeclaration());
auto root = doc.NewElement("root");
size_t sz = 0x80000001;
auto blank = new char[sz];
memset(blank, ' ', sz);
blank[sz-1]='\0';
root->SetText(blank);
doc.InsertEndChild(root);
tinyxml2::XMLPrinter printer(nullptr);
doc.Print(&printer);
}
XMLPrinter::ClearBuffer shouldn't set _firstElement to true, unless
called after the document is completely generated. Otherwise, the
layout of the XML file is messed up when the buffer is flushed
intermittently. This commit enables this using a default-initialized
parameter, chosen such that the existing behavior remains uneffected.