Bound recursive Matroska and MP4 element trees (#1425)

The Matroska and MP4 parsers limited child elements per container, but
allowed that limit to repeat at every nesting level. A crafted media file
could therefore retain millions of small elements and exhaust process
memory while it was opened.

Carry a shared element count through recursive parsing so the existing
per-container limits also have a global bound.
This commit is contained in:
Acts1631
2026-08-19 18:25:21 +02:00
committed by GitHub
parent 94f7b5af4c
commit 5f869755c8
4 changed files with 38 additions and 4 deletions
+11 -2
View File
@@ -99,8 +99,15 @@ void EBML::MasterElement::setMinRenderSize(offset_t minimumSize)
}
bool EBML::MasterElement::read(File &file, int depth)
{
unsigned int elementCount = 0;
return read(file, depth, elementCount);
}
bool EBML::MasterElement::read(File &file, int depth, unsigned int &elementCount)
{
static constexpr int MAX_EBML_DEPTH = 64;
static constexpr int MAX_EBML_ELEMENT_COUNT = 50000;
static constexpr int MAX_EBML_ELEMENT_COUNT_PER_LEVEL = 50000;
if(depth > MAX_EBML_DEPTH) {
debug("EBML: Maximum nesting depth exceeded");
@@ -109,12 +116,14 @@ bool EBML::MasterElement::read(File &file, int depth)
const offset_t maxOffset = file.tell() + dataSize;
std::unique_ptr<Element> element;
while((element = findNextElement(file, maxOffset))) {
if(elements.size() >= MAX_EBML_ELEMENT_COUNT_PER_LEVEL) {
if(elementCount >= MAX_EBML_ELEMENT_COUNT ||
elements.size() >= MAX_EBML_ELEMENT_COUNT_PER_LEVEL) {
debug("EBML: Maximum element count exceeded");
return false;
}
++elementCount;
if(auto master = dynamic_cast<MasterElement *>(element.get())) {
if(!master->read(file, depth + 1)) {
if(!master->read(file, depth + 1, elementCount)) {
debug("EBML: Invalid MasterElement");
continue;
}
+1
View File
@@ -56,6 +56,7 @@ namespace TagLib
protected:
bool read(File &file, int depth);
bool read(File &file, int depth, unsigned int &elementCount);
offset_t offset;
offset_t padding = 0;
+23 -2
View File
@@ -40,6 +40,7 @@ namespace {
"stbl", "minf", "moof", "traf", "trak",
"stsd", "stem"
};
constexpr int MAX_MP4_ATOM_COUNT = 50000;
constexpr int MAX_MP4_ATOM_COUNT_PER_LEVEL = 50000;
} // namespace
@@ -56,6 +57,25 @@ public:
MP4::Atom::Atom(File *file, int depth)
: d(std::make_unique<AtomPrivate>(file->tell()))
{
unsigned int atomCount = 0;
read(file, depth, atomCount);
}
MP4::Atom::Atom(File *file, int depth, unsigned int &atomCount)
: d(std::make_unique<AtomPrivate>(file->tell()))
{
read(file, depth, atomCount);
}
void MP4::Atom::read(File *file, int depth, unsigned int &atomCount)
{
if(++atomCount > MAX_MP4_ATOM_COUNT) {
debug("MP4: Maximum atom count exceeded");
d->length = 0;
file->seek(0, File::End);
return;
}
d->children.setAutoDelete(true);
ByteVector header = file->readBlock(8);
@@ -124,7 +144,7 @@ MP4::Atom::Atom(File *file, int depth)
file->seek(0, File::End);
return;
}
auto child = new MP4::Atom(file, depth + 1);
auto child = new MP4::Atom(file, depth + 1, atomCount);
d->children.append(child);
if(child->d->length == 0)
return;
@@ -232,12 +252,13 @@ MP4::Atoms::Atoms(File *file) :
d(std::make_unique<AtomsPrivate>())
{
d->atoms.setAutoDelete(true);
unsigned int atomCount = 0;
file->seek(0, File::End);
offset_t end = file->tell();
file->seek(0);
while(file->tell() + 8 <= end) {
auto atom = new MP4::Atom(file);
auto atom = new MP4::Atom(file, 0, atomCount);
d->atoms.append(atom);
if (atom->length() == 0)
break;
+3
View File
@@ -112,9 +112,12 @@ namespace TagLib {
protected:
Atom(File *file, int depth);
Atom(File *file, int depth, unsigned int &atomCount);
private:
friend class Atoms;
class AtomPrivate;
void read(File *file, int depth, unsigned int &atomCount);
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<AtomPrivate> d;
};