From 5f869755c8b6bc2dcdd36a11323ae45d3a851913 Mon Sep 17 00:00:00 2001 From: Acts1631 <69813585+acts-1631@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:25:21 -0400 Subject: [PATCH] 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. --- taglib/matroska/ebml/ebmlmasterelement.cpp | 13 +++++++++-- taglib/matroska/ebml/ebmlmasterelement.h | 1 + taglib/mp4/mp4atom.cpp | 25 ++++++++++++++++++++-- taglib/mp4/mp4atom.h | 3 +++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index 87f3dfd6..61e18110 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -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; 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(element.get())) { - if(!master->read(file, depth + 1)) { + if(!master->read(file, depth + 1, elementCount)) { debug("EBML: Invalid MasterElement"); continue; } diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index 3b77094a..175fc7b5 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -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; diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index d0b2f379..3781b75a 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -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(file->tell())) { + unsigned int atomCount = 0; + read(file, depth, atomCount); +} + +MP4::Atom::Atom(File *file, int depth, unsigned int &atomCount) + : d(std::make_unique(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()) { 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; diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h index 7ecd6f00..339bb0c6 100644 --- a/taglib/mp4/mp4atom.h +++ b/taglib/mp4/mp4atom.h @@ -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 d; };