Skip to content

[FIX] WTV: resynchronise instead of trusting an implausible element length - #2323

Merged
cfsmp3 merged 1 commit into
masterfrom
fix/wtv-resync
Aug 13, 2026
Merged

[FIX] WTV: resynchronise instead of trusting an implausible element length#2323
cfsmp3 merged 1 commit into
masterfrom
fix/wtv-resync

Conversation

@cfsmp3

@cfsmp3 cfsmp3 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

A WTV recording crashes ccextractor, and the same defect silently costs a third of its captions.

What happens

get_data() reads a 32-byte element header and believes whatever 32-bit length it finds there. On 5f6dfe831e35…wtv the parser walks 182,902 elements correctly, then meets 8800 bytes it cannot interpret. The "length" it reads at that point is 0x8A9E4344 (2,325,627,716).

That value flows to skip_sized_buffer(uint32_t size), which passes it to buffered_seek(ctx, int offset). 2325627720 - 2^32 = -1969339576, so a forward skip becomes a 1.8 GB backward seek, and the read that follows walks off the buffer.

Why the parser gets there

Independently of ccextractor, the file's own structure shows the gap. Walking the element chain from the last good header:

236970600  WTV_DATA  rawlen 8224   -> next expected 236978824
236978824  <8800 bytes that are not an element header>
236987624  WTV_DATA  rawlen 8224   <- parsing is fine again from here

ffmpeg reads a packet at 236982768, inside that region, and decodes the whole file to completion, so the recording is sound — it is our element walk that cannot cross this.

Fix

Reject a length that cannot describe an element (smaller than the 32-byte header, or implausibly large) and hunt forward for the next header rather than trusting it.

Most GUIDs that open a timeline element share the same 15 trailing bytes and differ only in the first (data, sync, index, stream1, stream2…), which makes them a dependable anchor. The scan keeps a rolling 32-byte window, so when it re-anchors the caller is positioned exactly as it would be after a normal header read, and the existing code path continues unchanged.

Measured over all 45 local WTV samples

master this branch
captions gained 1 sample, +51 cues (100 → 151)
captions lost none
crashes 1 0
resyncs used 1 in the whole corpus

On the affected recording, coverage goes from 228 s to 356 s of a 358 s file. The resync fires after exactly 8800 bytes, matching the gap measured independently from the file structure — the two numbers were derived separately and agree.

The single resync across 45 files is the point worth noting: this is not a parser that now guesses its way through recordings, it is a guard that fires once, where the old code crashed.

Behaviour change worth flagging

Two samples (the same content, present twice locally) produced no captions before and no captions after, but now exit 10 ("no captions were found") instead of 0, because a failed resync ends the parse rather than running off the end. Exit 10 is the accurate code for a file with no captions, but it is a change, and one of those files is regression test rt7 — that test already fails today ("No output generated but there should be"), so it stays failing, just with a different reason.

Related

Found while investigating a segfault. #2322 hardens the buffer layer so a bogus size can no longer read out of bounds; this PR stops the bogus size being produced in the first place. They are independent and either is useful alone.

@ccextractor-bot

Copy link
Copy Markdown
Collaborator
CCExtractor CI platform finished running the test files on windows. Below is a summary of the test results, when compared to test for commit c328108...:
Report Name Tests Passed
Broken 7/13
CEA-708 2/14
DVB 0/7
DVD 3/3
DVR-MS 2/2
General 22/27
Hardsubx 1/1
Hauppage 3/3
MP4 3/3
NoCC 10/10
Options 69/86
Teletext 0/21
WTV 4/13
XDS 31/34

Your PR breaks these cases:

NOTE: The following tests have been failing on the master branch as well as the PR:


It seems that not all tests were passed completely. This is an indication that the output of some files is not as expected (but might be according to you).

Check the result page for more info.

…ngth

get_data() reads a 32-byte element header and believes whatever 32-bit length
it finds. On one recording the parser walks 182,902 elements correctly and then
meets 8800 bytes it cannot interpret; the "length" read there is 0x8A9E4344.
That value is passed to skip_sized_buffer(), which hands it to buffered_seek()
as an int, turning a forward skip into a 1.8 GB backward seek and a segfault.

Reject a length that cannot describe an element (below the 32-byte header, or
implausibly large) and hunt forward for the next header instead. Most GUIDs that
open a timeline element share the same 15 trailing bytes and differ only in the
first, which makes them a dependable anchor; the scan keeps a rolling 32-byte
window so the caller ends up positioned exactly as after a normal header read.

The end-of-file marker is exempt from the length test. It legitimately carries a
zero length and is handled further down, so treating it as garbage ended the
parse early and changed the last cue of every WTV recording.

On the affected file the parser resynchronises after 8800 bytes -- matching the
gap measured independently between the last valid element and the next one --
and carries on to the end of the recording.

Verified byte for byte over the 45 local WTV samples, master vs this branch:

  byte-identical output : 44
  differing             : 0
  crash fixed           : 1, cues 100 -> 151

Coverage on that recording goes from 228s to 356s of a 358s file, and the
resync fires exactly once across the whole corpus.
@ccextractor-bot

Copy link
Copy Markdown
Collaborator
CCExtractor CI platform finished running the test files on linux. Below is a summary of the test results, when compared to test for commit 6077cf5...:
Report Name Tests Passed
Broken 9/13
CEA-708 2/14
DVB 0/7
DVD 3/3
DVR-MS 2/2
General 22/27
Hardsubx 1/1
Hauppage 3/3
MP4 3/3
NoCC 10/10
Options 69/86
Teletext 0/21
WTV 12/13
XDS 31/34

Your PR breaks these cases:

NOTE: The following tests have been failing on the master branch as well as the PR:


It seems that not all tests were passed completely. This is an indication that the output of some files is not as expected (but might be according to you).

Check the result page for more info.

@cfsmp3
cfsmp3 merged commit b67effd into master Aug 13, 2026
46 of 48 checks passed
@cfsmp3
cfsmp3 deleted the fix/wtv-resync branch August 13, 2026 06:56
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.

2 participants