From fa4c623ab17dfee5fc92b003eec0888fc2682713 Mon Sep 17 00:00:00 2001 From: Acts1631 <69813585+acts-1631@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:27:26 -0400 Subject: [PATCH] Bound ASF header-extension child parsing (#1426) ASF header extensions trusted their nested data size and accepted zero-sized child objects. A crafted file could make parsing continue outside the enclosing object while retaining one object per child header, exhausting process memory when opened. Bound nested data to the enclosing object, require a complete child header, and cap the nested object count. --- taglib/asf/asffile.cpp | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 9f40df31..33a11d8b 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -91,6 +91,7 @@ namespace const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); + constexpr unsigned int MAX_ASF_HEADER_EXTENSION_OBJECT_COUNT = 50000; bool attributeDataFits(File *file, long long size, offset_t &end) { @@ -435,20 +436,39 @@ ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const return headerExtensionGuid; } -void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, long long /*size*/) +void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, long long size) { + // The object header is 24 bytes. The extension header contains an 18-byte + // reserved field and a 4-byte data size before the child objects. + if(size < 46) { + file->setValid(false); + return; + } + file->seek(18, File::Current); - long long dataSize = readDWORD(file); + bool ok; + const long long dataSize = readDWORD(file, &ok); + if(!ok || dataSize > size - 46) { + file->setValid(false); + return; + } + long long dataPos = 0; + unsigned int objectCount = 0; while(dataPos < dataSize) { + if(objectCount++ >= MAX_ASF_HEADER_EXTENSION_OBJECT_COUNT) { + debug("ASF::HeaderExtensionObject::parse(): Maximum child object count exceeded."); + file->setValid(false); + break; + } + ByteVector uid = file->readBlock(16); if(uid.size() != 16) { file->setValid(false); break; } - bool ok; - long long size = readQWORD(file, &ok); - if(!ok || size < 0 || size > dataSize - dataPos) { + const long long childSize = readQWORD(file, &ok); + if(!ok || childSize < 24 || childSize > dataSize - dataPos) { file->setValid(false); break; } @@ -464,9 +484,9 @@ void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, long else { obj = new UnknownObject(uid); } - obj->parse(file, size); + obj->parse(file, childSize); objects.append(obj); - dataPos += size; + dataPos += childSize; } }