GHSA-f4rq-2259-hv29: Denial of service via non-terminating SYLT frame parsing loop in tinytag
Summary`tinytag` `2.2.0` allows an attacker who can supply MP3 files for parsing to trigger a non-terminating loop while the library parses an ID3v2 `SYLT` (synchronized lyrics) frame. In server-side deployments that automatically parse attacker-supplied files, a single `498`-byte MP3 can cause the parsing operation to stop making progress and remain busy until the worker or process is terminated.DetailsIn tag `2.2.0` (`6f1d3060f393743c2ec34d07c0855cceed827244`), the reachable call path is:`TinyTag.get` in [`tinytag/tinytag.py#L144-L154`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L144-L154)`_load` in [`tinytag/tinytag.py#L259-L266`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L259-L266)`_parse_tag` and `_parse_id3v2` in [`tinytag/tinytag.py#L1059-L1092`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L1059-L1092)`_parse_frame` for `SYLT` / `SLT` in [`tinytag/tinytag.py#L1316-L1318`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L1316-L1318)`_parse_synced_lyrics` and `_find_string_end_pos` in [`tinytag/tinytag.py#L1219-L1248`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L1219-L1248) and [`tinytag/tinytag.py#L1340-L1352`](https://github.com/tinytag/tinytag/blob/6f1d3060f393743c2ec34d07c0855cceed827244/tinytag/tinytag.py#L1340-L1352)The root cause is that `_parse_synced_lyrics` assumes `_find_string_end_pos` always returns a position greater than the current `offset`. That assumption is false when no string terminator is present in the remaining frame content.For single-byte encodings, `_find_string_end_pos` does:If no terminator exists, `content.find(...)` returns `-1`, so the function returns `0`. `_parse_synced_lyrics` then does `offset = end_pos`, which resets `offset` to `0` inside:Because `offset` is reset to `0`, the loop condition remains true and the parser stops making forward progress. The UTF-16 branch in `_find_string_end_pos` has the same shape: if no `b'\x00\x00'` terminator is found, it also returns `0`, so the same non-progress condition applies there.`SYLT` parsing support was introduced by commit [`4d649b9c314ada8ff8a74e0469e9aadb3acb252a`](https://github.com/tinytag/tinytag/commit/4d649b9c314ada8ff8a74e0469e9aadb3acb252a) (`ID3: Make synced lyrics available in 'other.lyrics' (LRC format) (#270)`), which first shipped in `2.2.0`. I confirmed that `2.1.2` does not contain `_parse_synced_lyrics`, so `2.2.0` is the only confirmed affected release at this time.Test environment:MacBook Air (Apple M2), macOS `26.3` / Darwin `arm64`Python `3.14.3`Confirmed affected release: `tinytag 2.2.0` (`6f1d3060f393743c2ec34d07c0855cceed827244`)Also reproduced on current `main` commit `1d23f6fe169c92c070a265f9108e295577141383`PoCThe following self-contained PoC generates a malformed `SYLT` frame and passes it to `TinyTag.get`:Observed output on `2.2.0` in the environment above:ImpactAn attacker who can supply MP3 files for parsing can cause tinytag to enter a non-terminating loop in its own parser. This is a library-level availability issue in the documented parsing path.In server-side processing of attacker-supplied files, a single request can tie up a worker or process that performs metadata extraction. In local or desktop integrations, opening a malicious file can hang the parsing task until it is interrupted.PatchesFixed in the following commits:https://github.com/tinytag/tinytag/commit/5cd321521ff097e41724b601d7e3d7adc7e53402https://github.com/tinytag/tinytag/commit/44e496310f7ced8077e9087e3774acbaa324b18a
CVSS v3.1: 6.5