Robustness fixes for malformed ID3v2 tags - #2
Open
avwarez wants to merge 3 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three robustness fixes in the v2 parser, found while feeding id3lib deliberately malformed tags. Each one concerns a length or a nesting level that is taken from the file and used without a sanity check. None of them changes how a well-formed tag is parsed.
Branched off
v3.9.1(48c49ba). All three cases are present inmasteras well, so they come from id3lib-devel rather than from anything you changed.1. Extended header flag bytes —
src/header_tag.cppParseExtended()reads the number of extended flag bytes from the file and loops that many times filling anID3_Flags*[1]. ID3v2.4 defines exactly one flag byte, and only that one is ever examined afterwards, but the counter is a full byte, so a tag can declare up to 255 and the loop writes one pointer per declared byte into the one-element array. The array is replaced by a singleID3_Flagsvalue; any further declared bytes are consumed and discarded. This also removes the allocations that were never freed.2. Nested compressed frames —
src/tag_parse.cppAn ID3v2.2.1 CDM frame contains frames, and
parseFrames()recurses into it. Nothing forbids those inner frames from being CDM frames in turn, so the nesting depth is whatever the file says it is, and each level costs a decompression buffer and a stack frame. A depth limit of 16 is added; past it the frame is skipped with a warning instead of being decompressed and descended into. Real tags do not nest these at all.3. Declared uncompressed size —
src/io_decorators.cppCompressedReaderallocates the uncompressed size the frame declares — four bytes straight from the file — before looking at how much compressed data is actually available, so the declared size can be arbitrarily larger than anything those bytes could produce. Deflate cannot expand by more than 1032:1, so the declared size is now clamped to that ceiling for the compressed bytes at hand; the arithmetic is done wider and narrowed afterwards, sinceuLongis 32-bit on Windows. The return value ofuncompress()is also checked, and the buffer is sized to what it actually produced, so a truncated stream no longer leaves an uninitialised tail for the parser to read.Built and tested on Windows with MSVC (x64, Win32, ARM64) as part of an eMule build; ordinary tags round-trip unchanged.
I've kept the branch on my side named
v3.9.2, but the PR necessarily targetsv3.9.1— feel free to merge wherever suits you, or to put it on a new branch if you'd rather leavev3.9.1as published. Happy to rebase or split it differently if that helps review.