CCExtractor version: master (907be05), built from source
Necessary information
- Is this a regression (i.e. did it work before)? Unknown — the uninitialised field predates the
--timestamp-map option, so it has likely never worked on these input paths.
- What platform did you use? Linux (Debian bookworm in Docker, x86-64). Platform-independent — nothing here is OS-specific.
- What were the used arguments?
--out=webvtt --timestamp-map
Video links
The reproducer is 153 bytes, so it is inline below rather than on a file host. Save it with:
base64 -d > sample.rcwt <<'EOF'
zMztzABQAAEAAADoAwAAAAAAAAsABJQgBJSuBJRwBEZJBFLTBFQgBEPBBNBUBElPBM6ABJQvuAsA
AAAAAAALAASUIASUrgSUcATTRQRDTwTOxAQgQwTB0ARUSQRPzgSUL4gTAAAAAAAACwAElCAElK4E
lHAEVMgESVIExCAEQ8EE0FQESU8EzoAElC9YGwAAAAAAAAEABJQs
EOF
sha256: 1e7e3f58151190124b113facc7543ac5ee6d7e230830cda88744de19cc17250c
It is a hand-built RCWT file (per docs/BINARY_FILE_FORMAT.TXT) holding three EIA-608 CC1 pop-on captions at 1 s, 3 s and 5 s. It decodes correctly on master (--out=srt etc. all work), so it is a valid input, not a fuzz artefact. Happy to have it in the regression suite.
Additional information
Symptom
$ ccextractor sample.rcwt --out=webvtt --timestamp-map -o out.vtt
...
Analyzing data in CCExtractor's binary format
Segmentation fault
$ echo $?
139
Without --timestamp-map the same file processes fine (exit 0). With --out=srt --timestamp-map it is also fine. The crash needs webvtt + --timestamp-map.
Backtrace
Program received signal SIGSEGV, Segmentation fault.
#0 write_webvtt_header ()
#1 write_cc_buffer_as_webvtt ()
#2 encode_sub ()
#3 rcwt_loop ()
#4 start_ccx ()
#5 main ()
Root cause
init_encoder() allocates with malloc and never assigns ctx->timing:
https://github.com/CCExtractor/ccextractor/blob/master/src/lib_ccx/ccx_encoders_common.c#L765
struct encoder_ctx *ctx = malloc(sizeof(struct encoder_ctx));
encoder_ctx.timing is assigned in only four places, and none of them covers this path:
| Location |
Condition |
general_loop.c:1534 (general_loop) |
always |
general_loop.c:697 (raw_loop) |
only when write_format == CCX_OF_MCC |
general_loop.c:1357 |
DVB path |
mp4.c:892 |
MP4 path |
So rcwt_loop(), process_hex(), and raw_loop() for every non-MCC output format leave ctx->timing holding indeterminate heap contents.
write_webvtt_header() then does:
https://github.com/CCExtractor/ccextractor/blob/master/src/lib_ccx/ccx_encoders_webvtt.c#L215
if (ccx_options.timestamp_map && context->timing != NULL && context->timing->sync_pts2fts_set)
The != NULL guard is the right check, but an indeterminate value is almost never NULL, so it passes and ->sync_pts2fts_set dereferences a wild pointer. Reading an indeterminate value is undefined behaviour in the first place (CWE-457), and the dereference is CWE-824.
This also explains why it is --timestamp-map-specific: that flag is the only thing that makes timing be read at all on this path.
Scope
init_encoder() assigns 60 of the struct's 74 members. Nine are never assigned:
timing, list, dtvcc_writers, dvb_lang, cdp_hdr_seq, next_caption_time, force_dropframe, header_printed_flag, write_previous
Some are filled in by callers, but any that are not are indeterminate. timing is the one currently reachable as a crash.
Suggested fix
Allocate with calloc instead of malloc. One line, no behaviour change for the 60 explicitly-assigned members, and it makes the existing != NULL guard work as intended — with timing == NULL, RCWT input simply skips X-TIMESTAMP-MAP (correct, since sync_pts2fts_set would be 0 anyway).
Verification
Built master and a calloc build from identical source in the same Docker image:
- crash:
exit 139 → exit 0
- output across 13 formats (webvtt, srt, sami, ttxt, txt, scc, ssa, smptett, g608, mcc, spupng, simple_xml, rcwt): byte-identical exit codes and SHA-256 hashes before and after
--timestamp-map output now matches plain --out=webvtt, i.e. the header is correctly omitted rather than crashing
PR follows.
CCExtractor version: master (907be05), built from source
Necessary information
--timestamp-mapoption, so it has likely never worked on these input paths.--out=webvtt --timestamp-mapVideo links
The reproducer is 153 bytes, so it is inline below rather than on a file host. Save it with:
sha256:
1e7e3f58151190124b113facc7543ac5ee6d7e230830cda88744de19cc17250cIt is a hand-built RCWT file (per
docs/BINARY_FILE_FORMAT.TXT) holding three EIA-608 CC1 pop-on captions at 1 s, 3 s and 5 s. It decodes correctly on master (--out=srtetc. all work), so it is a valid input, not a fuzz artefact. Happy to have it in the regression suite.Additional information
Symptom
Without
--timestamp-mapthe same file processes fine (exit 0). With--out=srt --timestamp-mapit is also fine. The crash needs webvtt + --timestamp-map.Backtrace
Root cause
init_encoder()allocates withmallocand never assignsctx->timing:https://github.com/CCExtractor/ccextractor/blob/master/src/lib_ccx/ccx_encoders_common.c#L765
encoder_ctx.timingis assigned in only four places, and none of them covers this path:general_loop.c:1534(general_loop)general_loop.c:697(raw_loop)write_format == CCX_OF_MCCgeneral_loop.c:1357mp4.c:892So
rcwt_loop(),process_hex(), andraw_loop()for every non-MCC output format leavectx->timingholding indeterminate heap contents.write_webvtt_header()then does:https://github.com/CCExtractor/ccextractor/blob/master/src/lib_ccx/ccx_encoders_webvtt.c#L215
The
!= NULLguard is the right check, but an indeterminate value is almost neverNULL, so it passes and->sync_pts2fts_setdereferences a wild pointer. Reading an indeterminate value is undefined behaviour in the first place (CWE-457), and the dereference is CWE-824.This also explains why it is
--timestamp-map-specific: that flag is the only thing that makestimingbe read at all on this path.Scope
init_encoder()assigns 60 of the struct's 74 members. Nine are never assigned:timing,list,dtvcc_writers,dvb_lang,cdp_hdr_seq,next_caption_time,force_dropframe,header_printed_flag,write_previousSome are filled in by callers, but any that are not are indeterminate.
timingis the one currently reachable as a crash.Suggested fix
Allocate with
callocinstead ofmalloc. One line, no behaviour change for the 60 explicitly-assigned members, and it makes the existing!= NULLguard work as intended — withtiming == NULL, RCWT input simply skipsX-TIMESTAMP-MAP(correct, sincesync_pts2fts_setwould be 0 anyway).Verification
Built master and a
callocbuild from identical source in the same Docker image:exit 139→exit 0--timestamp-mapoutput now matches plain--out=webvtt, i.e. the header is correctly omitted rather than crashingPR follows.