From 2cace8e7aa6c8762cffaffedfbc71e451c04c912 Mon Sep 17 00:00:00 2001 From: Acts1631 <69813585+acts-1631@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:14:41 -0400 Subject: [PATCH] ID3v2: limit embedded frame nesting (#1405) Limit recursive CHAP and CTOC embedded frame parsing to 64 levels. This prevents crafted ID3v2 tags from exhausting the parser stack. Use one thread-local guard shared by both paths so mixed CHAP and CTOC nesting is bounded as well. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 3 +- .../id3v2/frames/tableofcontentsframe.cpp | 3 +- taglib/mpeg/id3v2/id3v2framefactory.cpp | 29 +++++++++++++++++++ taglib/mpeg/id3v2/id3v2framefactory.h | 8 +++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index afe76dae..d7588736 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -30,6 +30,7 @@ #include "tbytevectorlist.h" #include "tdebug.h" #include "tpropertymap.h" +#include "id3v2framefactory.h" #include "unknownframe.h" using namespace TagLib; @@ -254,7 +255,7 @@ void ChapterFrame::parseFields(const ByteVector &data) return; while(embPos < size - header()->size()) { - Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + Frame *frame = FrameFactory::createEmbeddedFrame(data.mid(pos + embPos), d->tagHeader); if(!frame) return; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 805ae849..cac8f7f3 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -29,6 +29,7 @@ #include "tpropertymap.h" #include "tdebug.h" +#include "id3v2framefactory.h" #include "unknownframe.h" using namespace TagLib; @@ -264,7 +265,7 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) return; while(embPos < size - header()->size()) { - Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + Frame *frame = FrameFactory::createEmbeddedFrame(data.mid(pos + embPos), d->tagHeader); if(!frame) return; diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 6f612383..9d176003 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -55,6 +55,23 @@ using namespace ID3v2; namespace { + constexpr unsigned int MAX_EMBEDDED_FRAME_DEPTH = 64; + thread_local unsigned int embeddedFrameDepth = 0; + + class EmbeddedFrameDepth + { + public: + EmbeddedFrameDepth() + { + ++embeddedFrameDepth; + } + + ~EmbeddedFrameDepth() + { + --embeddedFrameDepth; + } + }; + void updateGenre(TextIdentificationFrame *frame) { StringList fields = frame->fieldList(); @@ -190,6 +207,18 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, return createFrame(data, header, tagHeader); } +Frame *FrameFactory::createEmbeddedFrame(const ByteVector &origData, + const Header *tagHeader) +{ + if(embeddedFrameDepth >= MAX_EMBEDDED_FRAME_DEPTH) { + debug("ID3v2: Maximum embedded frame nesting depth exceeded"); + return nullptr; + } + + EmbeddedFrameDepth depth; + return FrameFactory::instance()->createFrame(origData, tagHeader); +} + Frame *FrameFactory::createFrame(const ByteVector &data, Frame::Header *header, const Header *tagHeader) const { ByteVector frameID = header->frameID(); diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index 4f3919c8..c605fe08 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -36,6 +36,8 @@ namespace TagLib { namespace ID3v2 { class TextIdentificationFrame; + class ChapterFrame; + class TableOfContentsFrame; //! A factory for creating ID3v2 frames during parsing @@ -176,6 +178,12 @@ namespace TagLib { const Header *tagHeader) const; private: + static Frame *createEmbeddedFrame(const ByteVector &origData, + const Header *tagHeader); + + friend class ChapterFrame; + friend class TableOfContentsFrame; + static FrameFactory factory; class FrameFactoryPrivate;