Skip to content

Release 1.8.7 - #227

Merged
hed0rah merged 23 commits into
mainfrom
release/1.8.7
Sep 29, 2026
Merged

hed0rah merged 23 commits into
mainfrom
release/1.8.7

Conversation

@hed0rah

@hed0rah hed0rah commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Bug fixes only, backported from next and adapted to 1.8's plain warnings. No new features.

  • inspect/od crashed on every SoundFont
  • every valid RF64 failed validate; validate advertised a fix repair would refuse
  • WAV RF64 reservation (Ableton/Logic) reported as damage; appended bytes read as chunks
  • zip formats (.xpn/.labx/.multisample) reported as polyglots
  • Max for Live instruments, .vitalskin files, cut BRSTM, SigMF meta, 12-bit AIFF/WAV, McGill AU/AIFF, PDX short tables, cut MDX modules, MP4 multi-track repair and timestamps, audit ROM labels

Full test tiers pass locally with and without the real-file corpora; see CHANGELOG.md [1.8.7].

The SF2 walker's preset and instrument tree has no offset, and the table,
--full, --json and od formatted it as a number: inspect on any SoundFont
exited with an internal error, in 1.8.6 too. Unplaced nodes print '-'.
test_render_every_seed runs every render mode over every seed.
A Sun/NeXT file declaring more audio than it holds kept a data chunk past
the end of the file; the chunk now owns what is there and the overrun is a
size.overrun finding. An AIFF-C APPL chunk read its first data byte as a
Pascal-string length without checking it fits, so a 12-byte chunk grew a
71-byte name field; the name is decoded only when it fits.
Some writers emit only the slots they fill (26 is common), so the first
sample begins before 768 and a whole-bank read takes sample data for slots.
The short table is accepted only when it accounts for the file exactly:
at least two live slots, all inside the file, laid end to end to the last
byte (or one before it). 76 real banks in the corpus now walk; none of
333,922 non-PDX files passes.
Identification required every offset to land inside the file, so a truncated
rip was reported as unrecognised. The header alone identifies an MDX; the
walker already reports each offset that dangles. 16 real modules now sniff;
no non-MDX file in the corpus has a header that parses.
Both formats pad a sample point to whole bytes, but the size checks took
bits // 8, so 12-bit came out as one byte. Every 12-bit AIFF warned that
SSND held twice the audio COMM implied, and every 12-bit PCM WAV that a
correct block_align was wrong. The same round-down sat in the AIFF
frames-past-EOF check, the grammar's WAV check and the TUI's play
duration; all now round up.

Found on two 1991 Prosonus AIFFs in the specimen library.
The RSTM header declares the whole file's size at 0x08 and the walker never
read it, so a stream in FFmpeg's test suite holding 200 KB of a declared
9.1 MB walked as whole. The size is now a field, and a file shorter than
it declares is a size.overrun.
The McGill fix was written against 2.0's finding codes; 1.8 has none, so
the two defect() calls would have raised NameError on the first file that
reached them.
Opened by its .sigmf-meta, the walk laid the data file's regions over the
JSON, so a real 1,045-byte sidecar came back with three invalid chunks and
every byte unaccounted for. From the meta, the walk now places one sidecar
chunk; captures and annotations are listed, unplaced, with the file that
holds them. The tests that asserted byte regions from the meta now walk the
data half.
The MP4 repairer located mdat before its guarded run, and locating it raised
on a real two-track video, so constraints.repair broke its no-raise promise.
mdat is now compared only when there is a rewrite to guard.
Deprecated from Python 3.12; the warning failed the hunt sweep on a real
MP4 with a creation time. Output is unchanged.
A SNES or N64 ROM sniffs as one, yet audit printed [unknown] and suggested
locate. Exit 2 still says nothing was checked.
Every real .xpn, .labx and .multisample was a polyglot carrying embedded
media: the archive's own end record read as an appended zip, its stored
members as smuggled files. An SF3's Ogg samples tripped the same rule. The
appended-zip scan skips a file that starts with a local header, embedded
media skips an archive's members and an SF3's Ogg, and a multisample places
its central directory (zip_directory_extent).
A Max instrument says 'iiii' and was a magic mismatch. Every audio effect
measured carries 'aaaa' and every instrument 'iiii'; the field is named
device_type, and an unmeasured value is a notice.
A .vitalskin carries synth_version too, so its theme keys came back as
unknown preset keys with carrier alerts. A skin has no settings; the
extension and a whole file without settings are refused. The golden sniff
of a 16-byte vital head moves from vital to unknown.
The 28-byte JUNK after the RIFF header is the ds64 reservation. 174 of
6,000 real WAVs fill it on purpose and were reported as something
overwritten in place: the ds64 sizes (stale when chunks were added later,
or in a 64-byte reservation), a 28-character quote Ableton Live writes
there, or the RIFF size over such a quote. Each is now named; the quote is
an Ableton Live provenance tell.
The walker read every 8 bytes past the declared end as a chunk header:
2 MB of appended zeros became 262,144 empty chunks, the Document took
seconds to build, and the TUI mount test ran past its 60 s budget on the
slowest CI job (under coverage) five runs in a row. A chunk id is four
ASCII characters, so the walk stops at the first that is not; the bytes stay
past the container end, where the scan names them as trailing data.

The TUI tests built their huge chunk count from those phantom chunks; they
now use 65,536 real empty JUNK chunks inside the RIFF. The wav-findings
golden shot loses its five phantom hex chunks and keeps the polyglot alert.
Both were written against 2.0's Source layer: the SigMF sidecar view read
a Source object and the vitalskin check called input_name, neither of which
1.8 has, so opening a .sigmf-meta or sniffing any Vital preset raised
NameError. The RF64-reservation notes are dropped, 1.8 having no info kind;
the summary line says what the reservation holds.
AudioGuardError is how repair declines a file whose audio it cannot locate;
the repair verb reports it as nothing written. The corpus sweep counted it
as a crash because no real file had tripped the guard until a malformed
AIFC from McGill's public set (its COMM chunk is four bytes short).
The hunt sweep counted Unsupported as a walker raising, so the first SNES
ROM in the corpus failed it. A ROM is recognised and deliberately not walked;
the refusal is an answer.
…not repairable

RF64 keeps its sizes in ds64 and writes 0xFFFFFFFF in the 32-bit fields;
the structure model read them literally, so every valid RF64 (one libsndfile
wrote, here) failed check with the data chunk overrunning the file and an
88-byte RIFF size proposed. check declines RF64 with a note, as edit does.

A real CCRMA AIFC declares COMM as 18 bytes but writes the AIFC layout, so
SSND is never reached; check proposed shrinking FORM and printed 'fix with
check --fix', which repair then refused. analyze now marks nothing
repairable when apply would refuse, and says why.
The lost-audio branch ran first, and an overrunning data chunk also sits
outside the parse tree, so a crash-truncated WAV lost its 'outside the
container' diagnosis. The orphan case is checked first again.
@hed0rah
hed0rah merged commit 72987d1 into main Sep 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant