Serial-ATA / Serial-ATA/lofty-rs

[AI] Batch of bugs found by an autonomous scanner

Open
#703 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Rust
Stars
359
Forks
77
Avg merge
12h 54m
Merged PRs (30d)
11

Description

I'm experimenting with autonomous bug finding to speed up the process of finding bugs. It works by running and diagnosing real code instead of just analyzing the source code (just like I did in https://github.com/Serial-ATA/lofty-rs/issues/656), so it produces quite good results.

Reproducible projects, patches and bug explanations - lofty_rs_bughunt_20260826_080738.zip

# Class Candidate Fix lines Root cause
001 logic iff/chunk.rs Chunks::next() chunk-size header bounds check +4/-1 Off-by-8 chunk-size bounds check let truncated/oversized chunks slip past validation and hit a hard EOF Err instead of BestAttempt degrading gracefully
002 crash jpeg SOF0/SOF2 precision * components multiplication width +1/-1 JPEG SOF0/SOF2 precision * components multiplied as u8 before widening, overflowing when the product exceeds 255
003 crash mp4 ALS branch channel-count u8 truncation before +1 +6/-1 MP4 ALS branch's channel count stsd.read_u16()? as u8 + 1 truncates to u8 before adding 1, overflowing when the value's low byte is 0xFF
005 logic iff/chunk.rs Chunks::next() chunk-size header bounds check (AIFF) +4/-1 Same shared Chunks::next() off-by-8 bug as 001, independently confirmed for AIFF via AiffFile::read_from
007 crash musepack SV7 true_gapless total_samples divide-by-zero guard +5/-0 SV7 true_gapless branch's total_samples computation hits exactly 0 when frame_count==1 and last_frame_length==0 (both individually valid), causing an unconditional integer divide-by-zero in the bitrate calc; unlike the ruled-out SV4-6 case this is integer (not saturating float) division, and unlike sibling SV8 this branch had no total_samples == 0 guard
011 crash mp4 FLAC-in-MP4 dfLa atom len subtract-before-check guard +3/-1 MP4 FLAC-in-MP4 branch's if dfla.len - 12 < 18 subtracts before checking, underflowing a u64 whenever a real dfLa atom's on-disk len is 8-11 (only >= 8 is guaranteed by AtomInfo::read); same subtract-before-check shape as candidate 003, via the fLaC/dfLa codec path
013 crash jpeg from_jpeg marker-segment section_len - 2 checked subtraction +6/-1 from_jpeg's per-marker-segment section_len - 2 skip-seek is an unchecked u16 subtraction (unlike the first segment's checked_sub-guarded copy of the same computation), underflowing whenever a non-SOS/SOF marker declares a length of 0 or 1
015 crash iff write_to_chunk_file ID3v2 tag-grew stream-len subtraction sign fix +1/-1 WAV/AIFF ID3v2 write path's "tag grew" branch subtracts (updated_stream_len -= tag_chunk_size - existing_tag_len) when it should add, underflowing whenever a real caller replaces a small existing trailing tag with a larger one
016 crash iff write_to_chunk_file ID3v2 tag-grew stream-len subtraction sign fix (AIFF) +1/-1 Same bug as 015, independently confirmed for the AIFF (BigEndian) instantiation of the same shared write_to_chunk_file function via AiffFile::save_to
018 tooling ASAN cargo fuzz run hang-scope sweep across the larger *file_read_from fuzz targets n/a Default-ASAN cargo fuzz run hangs on the project's larger *file_read_from targets too (not just 009's small picture_from_* ones), tied to a target's first empty-corpus NEW_FUNC symbolization event - not a lofty source bug
019 tooling corpus-seeding ASAN-hang workaround sweep across all fuzz targets n/a Swept across all 19 fuzz targets: seeding the corpus with one file does NOT reliably avoid the ASAN hang (only fixed 1/18 hanging targets); mp4file_read_from still hung 6/6 runs with a single-file seed, refuting candidate 018's one-off control observation as a general workaround
020 logic iff chunk_file JUNK-padding write size-field off-by-header fix +9/-2 WAV JUNK-padding write branch declares its size field as remainder instead of remainder - IFF_CHUNK_HEADER_SIZE, claiming 8 bytes it never reserved and silently eating into whatever real chunk data physically follows on disk whenever an existing ID3v2 tag shrinks
023 logic mp4 ilst atom_tree matched-index Option<usize> tracking fix +23/-20 atom_tree's matched-index bookkeeping can't distinguish "matched at index 0" from "never matched", so a real meta atom with children but no ilst among them has its first real child (e.g. hdlr) silently mistaken for the nonexistent ilst atom and destroyed on every write
024 tooling ASAN symbolizer-fork RLIMIT_NOFILE close-loop root-cause + ulimit fix n/a Root cause of the ASAN hang (009/018/019): this sandbox's RLIMIT_NOFILE is ~2.1 billion, and ASAN's forked llvm-symbolizer-launcher child loops close()-ing every fd up to that limit before exec, never finishing; lowering RLIMIT_NOFILE (ulimit -Sn) before an ASAN run fixes it while keeping ASAN active, unlike --sanitizer none
025 logic mp4 ilst atom_tree duplicate-sibling first-match write selection fix +1/-1 atom_tree picks the LAST of multiple real ilst siblings to overwrite on write, but the read path always returns the FIRST one, so a tag edit through the real public API silently never becomes visible again on the next read
026 crash mp4 AtomInfo::read_inner atom-size-vs-budget off-by-header-length check +4/-2 AtomInfo::read_inner's atom-size-vs-budget check is off by ATOM_HEADER_LEN (8 bytes), letting moov (and any atom) declare a size 8 bytes past the real EOF undetected; mp4/ilst/write.rs's "insert new ilst" path then splices at the resulting out-of-buffer meta_end offset and panics
027 crash mp4 update_offsets co64/tfhd checked i64 conversion +28/-10 update_offsets's co64/tfhd offset rewrite reinterprets an unvalidated, file-controlled u64 chunk offset as i64 via a raw as cast instead of a checked conversion, so adding the (small) size-change delta overflows i64 and panics on a crafted file with an out-of-range offset
028 tooling corpus-saturation threshold characterization for the ASAN hang n/a The ASAN hang (009/018/019/024) only clears once a corpus's composition (not just its size) already covers every function libFuzzer's mutations hit that run - the needed fraction is highly target-dependent (~45-50% for mp4file_read_from, ~70-90%+ for speexfile_read_from) and not reliably predictable in advance, reinforcing candidate 024's ulimit-based fix over corpus tuning
030 logic mp4 Moov::parse trak->mdia find_child_atom header-inclusive budget fix +6/-2 Moov::parse's trak->mdia find_child_atom call uses a header-inclusive budget, letting a crafted mdia size overrun into the next moov sibling and silently drop the real, untouched udta/ilst tag with no error
031 crash lands candidate 026's AtomInfo::read_inner off-by-header-length fix for real +4/-2 Landed candidate 026's already-verified AtomInfo::read_inner off-by-ATOM_HEADER_LEN fix for real (previously only ever applied temporarily); confirmed the bug independently causes candidate 030's silent-tag-loss symptom even with 030's own separate bug fixed in isolation, though this fix alone doesn't close 030's own call site
032 logic lands candidate 030's Moov::parse trak->mdia header-budget fix for real +6/-2 Landed candidate 030's already-verified Moov::parse trak->mdia find_child_atom header-inclusive-budget fix (atom.len - 8) for real (previously only ever applied temporarily); reconfirmed fresh against current mainline before landing
033 logic mp4 Minf::parse minf->stbl find_child_atom header-inclusive budget fix +1/-1 Minf::parse's minf->stbl find_child_atom call uses a header-inclusive budget (same shape as 030), letting a crafted stbl size overrun past minf's real end - either silently tolerated or, when the overrun lands on a plausible stsd-looking atom, overwriting real audio properties with garbage and spuriously failing the whole parse
034 logic lands candidate 033's Minf::parse minf->stbl header-budget fix for real +1/-1 Landed candidate 033's already-verified Minf::parse minf->stbl find_child_atom header-inclusive-budget fix (len - 8) for real (previously only ever applied temporarily); reconfirmed fresh against current mainline before landing
036 logic mp4 update_offsets stco checked u32 conversion +10/-5 update_offsets's stco (32-bit) offset rewrite casts i64::from(read_offset) + difference back to u32 with an unchecked as cast, silently wrapping (no panic, no error) instead of erroring whenever the true sum falls outside 0..=u32::MAX, corrupting the on-disk chunk offset

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the candidate table and the reproducible-projects archive linked in the issue, then inspect the named parsing or fuzzing entry points for one unlanded candidate. Confirm the reported behavior against the supplied reproduction and current main. Done means addressing one clearly scoped candidate and verifying that its specific crash, corruption, or tooling behavior no longer occurs.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, testing-qa, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.