Skip to content

Fix use-after-free in switch_output_file on basename failure#2287

Closed
amitmishra11 wants to merge 1 commit into
CCExtractor:masterfrom
amitmishra11:fix/switch-output-file-uaf-2273
Closed

Fix use-after-free in switch_output_file on basename failure#2287
amitmishra11 wants to merge 1 commit into
CCExtractor:masterfrom
amitmishra11:fix/switch-output-file-uaf-2273

Conversation

@amitmishra11

Copy link
Copy Markdown

Why

switch_output_file() in src/lib_ccx/ccx_encoders_common.c frees the previous enc_ctx->out->filename unconditionally, then only reassigns it inside if (basename != NULL):

if (enc_ctx->out->filename != NULL)
{
    free(enc_ctx->out->filename);
    close(enc_ctx->out->fh);
}
...
char *basename = get_basename(enc_ctx->out->original_filename);
if (basename != NULL)
{
    enc_ctx->out->filename = create_outfilename(basename, suffix, ext);
    ...
}

write_subtitle_file_header(enc_ctx, enc_ctx->out);  // unconditional

If get_basename() returns NULL (allocation failure, or a NULL/unusual original_filename), enc_ctx->out->filename is left pointing at memory that was just freed, and write_subtitle_file_header() is called regardless — its call chain (write_subtitle_file_header -> write_spumux_header -> spunpg_init) dereferences out->filename (strlen(out->filename), memcpy(... out->filename ...)), a use-after-free.

Fix

  • enc_ctx->out->filename = NULL; immediately after freeing it, so the pointer is never left dangling even momentarily.
  • Return early (after an mprint warning, matching the existing logging convention in this file) when basename is NULL, instead of falling through to use a context with no valid filename/handle — there's nothing sensible to write a header for at that point anyway.

Testing

I couldn't get a full build of this project running in my sandbox (no cmake, and the available MinGW toolchain doesn't have the FFmpeg/GPAC/etc. dev libraries this project links against), so I verified the logic two ways instead of guessing:

  1. Confirmed the current master source matches the bug exactly as described (read switch_output_file and traced write_subtitle_file_header -> write_spumux_header -> spunpg_init's use of out->filename).
  2. Wrote a standalone, minimal C reproduction mirroring the exact pointer lifecycle (free → conditionally-skipped reassign → use), with an allocation between the free and the use to make the dangling read observably wrong instead of relying on undefined-but-coincidentally-OK behavior. Against the current logic this reliably shows the freed memory being read; against the patched logic it cleanly bails out with filename == NULL and never reads the dangling pointer.
  3. Pasted the exact patched function (verbatim, with the real helper signatures stubbed to match utility.h) into a standalone file and compiled/ran it through both the normal and basename-failure paths to confirm no syntax issues and unchanged behavior on the happy path across repeated calls.

Happy to add this as a real unit test if there's an existing harness/fixture I should hook into - I didn't see one already covering switch_output_file or get_basename failure paths.

Fixes #2273.

If get_basename(enc_ctx->out->original_filename) returns NULL, the
function left enc_ctx->out->filename pointing at memory it had just
freed a few lines above, then unconditionally passed enc_ctx->out to
write_subtitle_file_header(), which dereferences filename downstream
(write_subtitle_file_header -> write_spumux_header -> spunpg_init).

NULL the pointer immediately after freeing it, and return early (with
a warning) when no basename can be derived, instead of falling through
to use a context with no valid filename/handle.

Fixes CCExtractor#2273.
@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 2feb09a...:
Report Name Tests Passed
Broken 0/13
CEA-708 2/14
DVB 0/7
DVD 3/3
DVR-MS 0/2
General 15/27
Hardsubx 1/1
Hauppage 3/3
MP4 3/3
NoCC 10/10
Options 72/86
Teletext 20/21
WTV 12/13
XDS 31/34

Your PR breaks these cases:

  • ccextractor --autoprogram --out=srt --latin1 1d9731bd80...
  • ccextractor --out=sami --latin1 --autoprogram --no-goptime 5b4e0a6034...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla --xds 8e8229b88b...
  • ccextractor --autoprogram --out=ttxt --latin1 7236304cfc...
  • ccextractor --out=srt --latin1 06b3a9237d...
  • ccextractor --out=srt --latin1 83f8cceb74...
  • ccextractor --out=srt --latin1 611b4a9235...
  • ccextractor --out=srt --latin1 b46e9e8e3f...
  • ccextractor --out=srt --latin1 89e417e622...
  • ccextractor --out=srt --latin1 d59eadc4ed...
  • ccextractor --autoprogram --out=ttxt --latin1 1020459a86...
  • ccextractor --datapid 5603 --autoprogram --out=srt --latin1 --teletext 85c7fc1ad7...
  • ccextractor --autoprogram --out=srt --latin1 --quant 0 85271be4d2...
  • ccextractor --wtvconvertfix --autoprogram --out=srt --latin1 acf871cbfd...
  • ccextractor --wtvconvertfix --autoprogram --out=srt --latin1 5cbb21adb6...
  • ccextractor --autoprogram --out=ttxt --latin1 99e5eaafdc...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla dab1c1bd65...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla 15feae9133...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla 95dd33c6f1...
  • ccextractor --autoprogram --out=ttxt --latin1 01509e4d27...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla ab9cf8cfad...
  • ccextractor --autoprogram --out=srt --latin1 15feae9133...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla --output-field 2 5d3a29f9f8...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla --output-field 2 c41f73056a...
  • ccextractor --autoprogram --out=srt --latin1 --sentencecap c032183ef0...
  • ccextractor --autoprogram --out=bin --latin1 c032183ef0...
  • ccextractor --out=srt --latin1 --autoprogram 29e5ffd34b...
  • ccextractor --out=spupng c83f765c66...
  • ccextractor --dru c83f765c66...
  • ccextractor --startat 4 --endat 7 c83f765c66...
  • ccextractor --codec dvbsub --out=spupng 85271be4d2...
  • ccextractor --startcreditstext "CCextractor Start crdit Testing" c4dd893cb9...
  • ccextractor --startcreditsnotbefore 1 --startcreditstext "CCextractor Start crdit Testing" c4dd893cb9...
  • ccextractor --startcreditsnotafter 2 --startcreditstext "CCextractor Start crdit Testing" c4dd893cb9...
  • ccextractor --startcreditsforatleast 1 --startcreditstext "CCextractor Start crdit Testing" c4dd893cb9...
  • ccextractor --startcreditsforatmost 2 --startcreditstext "CCextractor Start crdit Testing" c4dd893cb9...
  • ccextractor --out=srt --latin1 f23a544ba8...
  • ccextractor --autoprogram --out=srt --latin1 --ucla d037c7509e...
  • ccextractor --autoprogram --out=srt --latin1 --ucla 7d3f25c32c...
  • ccextractor --autoprogram --out=ttxt --latin1 --ucla --xds 7f41299cc7...

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

Congratulations: Merging this PR would fix the following tests:

  • ccextractor --service 1 --out=ttxt da904de35d..., Last passed: Never
  • ccextractor --autoprogram --out=ttxt --latin1 132d7df7e9..., Last passed: Never
  • ccextractor --autoprogram --out=srt --latin1 b22260d065..., Last passed: Never

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.

@amitmishra11

Copy link
Copy Markdown
Author

Closing in favor of #2286, which was filed first (2026-06-26) and fixes the same root cause in switch_output_file (issue #2273) via a prepare-then-swap approach, with a real-sample-based test (multiprogram_spain.ts). I should have checked the open PRs list against this issue before submitting — apologies for the duplicate, and thanks for the CONTRIBUTING.md reminder to do that next time.

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] Use-After-Free in switch_output_file due to dangling pointer when get_basename fails

2 participants