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.
This commit is contained in:
Acts1631
2026-08-09 16:14:41 +02:00
committed by GitHub
parent 0836f213e7
commit 2cace8e7aa
4 changed files with 41 additions and 2 deletions
+2 -1
View File
@@ -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;
@@ -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;
+29
View File
@@ -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();
+8
View File
@@ -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;