Skip to content

[FIX] webvtt: don't leak basefilename or skip the header flag when CSS creation fails - #2339

Open
pranayr710 wants to merge 1 commit into
CCExtractor:masterfrom
pranayr710:fix/webvtt-css-header-error-path
Open

[FIX] webvtt: don't leak basefilename or skip the header flag when CSS creation fails#2339
pranayr710 wants to merge 1 commit into
CCExtractor:masterfrom
pranayr710:fix/webvtt-css-header-error-path

Conversation

@pranayr710

Copy link
Copy Markdown

Fixes #2338

Reason for this PR:

  • This PR fixes a bug (see the verification note at the bottom — I'm being explicit about what I ran and what I didn't).

Sanity check:

  • I have read and understood the contributors guide.
  • I have checked that another pull request for this purpose does not exist.
  • Bug fix, so NOT added to the changelog.
  • Not adding new C code — this restructures an existing error path and adds the missing free.

What was wrong

write_webvtt_header() is called once per cue (from write_stringz_as_webvtt, write_cc_bitmap_as_webvtt and write_cc_buffer_as_webvtt) and guards re-entry with context->wrote_webvtt_header, which it sets on its final line:

context->wrote_webvtt_header = 1; // Do it even if couldn't write the header, because it won't be possible anyway

The --webvtt-create-css path returned early when the .css file could not be opened, skipping that assignment:

if (f == NULL)
{
        mprint("Warning: Error creating the file %s\n", css_file_name);
        free(css_file_name);
        return;              // <-- flag never set
}

So on a CSS-creation failure the header block gets written again before every later cue, and the output stops being valid WebVTT. The comment quoted above already says the flag was meant to be set regardless.

Separately, basefilename came from get_basename(), which mallocs, and was never freed on any path. Every other caller in the tree frees it (ccx_encoders_common.c:607, :636, :1369, :1437). Alone that's one small leak per output file; combined with the early return it became a leak per cue.

The change

  • returnelse branch, so wrote_webvtt_header = 1 is always reached and there is still only one assignment to it.
  • free(css_file_name) moved after the branch so both paths release it.
  • free(basefilename) added once the name has been formatted.
  • get_basename() NULL return guarded, mirroring ccx_encoders_common.c:636-641.

No signature, struct, option or output-format change. Behaviour on the success path is unchanged.

On the NULL guard

I want to be straight rather than oversell it: I could not establish that first_input_file is ever actually NULL at runtime — parser.rs:1618-1620 leaves it as an empty string, so get_basename() returns "", not NULL. I included the guard because the identical call in ccx_encoders_common.c already has one and the crash would be a plain strlen(NULL), but it is hardening, not a fixed crash. Say the word and I'll drop it, leaving just the leak and the flag.

Verification

…failure

write_webvtt_header() is called once per cue and guards re-entry with
context->wrote_webvtt_header, which it sets on its last line. The
--webvtt-create-css path returned early when the .css file could not be
created, bypassing that assignment, so the whole header block was written
again before every subsequent cue and the resulting .vtt was not valid
WebVTT. The existing trailing comment ("Do it even if couldn't write the
header, because it won't be possible anyway") already stated the intent.

Replace the early return with an else branch so the flag is always
reached, and move free(css_file_name) after it so both paths release it.

basefilename came from get_basename(), which allocates, but was never
freed on any path. Every other caller in the tree frees it. On its own
that is one small leak per output file; together with the early return it
became a leak per cue.

Also guard get_basename() returning NULL, matching the existing pattern in
ccx_encoders_common.c:636-641. This one is hardening rather than a
demonstrated crash: the Rust parser leaves first_input_file as an empty
string rather than NULL, so get_basename() currently returns "".

Refs CCExtractor#2338
@ccextractor-bot

Copy link
Copy Markdown
Collaborator
CCExtractor CI platform finished running the test files on windows. 171/237 tests matched the approved output:
Report Name Tests Passed
Broken 9/13
CEA-708 2/14
DVB 0/7
DVD 3/3
DVR-MS 2/2
General 25/27
Hardsubx 1/1
Hauppage 3/3
MP4 3/3
NoCC 10/10
Options 69/86
Teletext 0/21
WTV 12/13
XDS 32/34

66 tests do not match the approved output. That is the pass/fail verdict. Whether this branch caused it is a separate question, answered below.


Compared with the tip of mastertest 9525, commit 3875e84:

  • 0 pass there and fail here
  • 0 fail there and pass here
  • 0 fail on both, with different output
  • 66 fail on both, byte for byte the same

Compared with the commit this branch was cut from: the same run as the tip of master (test 9525), so the comparison above already covers it.


No test changes behaviour relative to the tip of master: every failure above fails there too, byte for byte. The approved output for those tests is out of date, which is a baseline to review rather than a regression in this branch.

@ccextractor-bot

Copy link
Copy Markdown
Collaborator
CCExtractor CI platform finished running the test files on linux. 171/237 tests matched the approved output:
Report Name Tests Passed
Broken 9/13
CEA-708 2/14
DVB 0/7
DVD 3/3
DVR-MS 2/2
General 25/27
Hardsubx 1/1
Hauppage 3/3
MP4 3/3
NoCC 10/10
Options 69/86
Teletext 0/21
WTV 12/13
XDS 32/34

66 tests do not match the approved output. That is the pass/fail verdict. Whether this branch caused it is a separate question, answered below.


Compared with the tip of mastertest 9528, commit 205cfb0:

  • 0 pass there and fail here
  • 0 fail there and pass here
  • 0 fail on both, with different output
  • 66 fail on both, byte for byte the same

Compared with the commit this branch was cut from: the same run as the tip of master (test 9528), so the comparison above already covers it.


No test changes behaviour relative to the tip of master: every failure above fails there too, byte for byte. The approved output for those tests is out of date, which is a baseline to review rather than a regression in this branch.

@pranayr710

Copy link
Copy Markdown
Author

Update: I've now executed the failure path, so the caveat in the PR description can go. It reproduces.

I built master in Docker and ran it against a 153-byte RCWT input with three cues, with the .css target on a read-only mount so fopen fails:

base64 -d > sample.rcwt <<'EOF'
zMztzABQAAEAAADoAwAAAAAAAAsABJQgBJSuBJRwBEZJBFLTBFQgBEPBBNBUBElPBM6ABJQvuAsA
AAAAAAALAASUIASUrgSUcATTRQRDTwTOxAQgQwTB0ARUSQRPzgSUL4gTAAAAAAAACwAElCAElK4E
lHAEVMgESVIExCAEQ8EE0FQESU8EzoAElC9YGwAAAAAAAAEABJQs
EOF
docker run --rm -v $PWD/ro:/ro:ro -v $PWD/out:/out ccx:master     /ro/sample.rcwt --out=webvtt --webvtt-create-css -o /out/ev.vtt
Warning: Error creating the file /ro/sample.css
Warning: Error creating the file /ro/sample.css
Warning: Error creating the file /ro/sample.css
Warning: Error creating the file /ro/sample.css

Four warnings for a three-cue file — one per cue plus one from write_subtitle_file_footer(). write_webvtt_header() is re-entering every time, which is only possible because wrote_webvtt_header is never set on the early-return path. That is the defect this PR fixes.

The visible damage in the output, with the blank lines marked:

WEBVTT

00:00:01.001 --> 00:00:02.999 line:84.66%
FIRST CAPTION
                      <-- extra
                      <-- blank line
00:00:03.001 --> 00:00:04.999 line:84.66%
SECOND CAPTION
                      <-- extra
                      <-- blank line
00:00:05.001 --> 00:00:06.999 line:84.66%

Each re-entry runs the else branch ("Must have another newline if X-TIMESTAMP-MAP is not used") and injects an extra CRLF before the next cue. With --webvtt-create-css --timestamp-map it would be a repeated X-TIMESTAMP-MAP line instead of a blank one — I could not demonstrate that particular variant, because --timestamp-map on this input path segfaults for an unrelated reason, which I've filed separately as #2341.

One correction to my own report while I'm here: I originally described the corruption as the header block repeating. On the default path the header block is just a newline, so what a user actually sees is doubled blank lines rather than a repeated WEBVTT/X-TIMESTAMP-MAP line. The mechanism is exactly as described; the visible symptom is milder than my wording implied.

The basefilename leak in the same function is unchanged and unaffected by any of this — it is an allocation with no matching free on any path.

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.

[BUG] write_webvtt_header(): --webvtt-create-css leaks basefilename, and re-emits the header before every cue when the CSS file can't be created

2 participants