[PR #1838] [MERGED] fix(teletext): Prevent double-free crash in teletext cleanup #2597

Open
opened 2026-01-29 17:23:00 +00:00 by claunia · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/CCExtractor/ccextractor/pull/1838
Author: @cfsmp3
Created: 12/17/2025
Status: Merged
Merged: 12/17/2025
Merged by: @cfsmp3

Base: masterHead: fix/teletext-double-free-crash


📝 Commits (1)

  • 9cf96b1 fix(teletext): Prevent double-free crash in teletext cleanup

📊 Changes

1 file changed (+14 additions, -0 deletions)

View changed files

📝 src/lib_ccx/general_loop.c (+14 -0)

📄 Description

Summary

This PR fixes a critical double-free bug that caused CCExtractor to crash with exit code 134 (SIGABRT) when processing teletext streams. The crash occurred during cleanup after processing completed.

Problem

The teletext context (TeletextCtx) pointer was shared between two structures:

  • dec_ctx->private_data (decoder context)
  • cinfo->codec_private_data (capture info in cinfo_tree)

Crash sequence:

  1. general_loop() calls telxcc_close(&dec_ctx->private_data) → frees TeletextCtx, NULLs dec_ctx->private_data
  2. But cinfo->codec_private_data still points to the freed memory (not NULLed!)
  3. During cleanup, dinit_cap() sees non-NULL cinfo->codec_private_data
  4. dinit_cap() calls telxcc_close() again → double-free crash

Root Cause

This bug was exposed by commit 7e1a01447 ("fix(ocr): Improve DVB subtitle OCR quality") which added cleanup code to dinit_cap() to free codec_private_data. The telxcc_close() call in general_loop() has existed since 2015, but the double-free only became possible after the new cleanup code was added.

Solution

After telxcc_close() frees the teletext context in general_loop(), iterate through all cinfo entries and NULL out any that shared the same pointer:

if (dec_ctx->codec == CCX_CODEC_TELETEXT)
{
    void *saved_private_data = dec_ctx->private_data;
    telxcc_close(&dec_ctx->private_data, &dec_ctx->dec_sub);
    // NULL out any cinfo entries that shared this private_data pointer
    if (saved_private_data && ctx->demux_ctx)
    {
        struct cap_info *cinfo_iter;
        list_for_each_entry(cinfo_iter, &ctx->demux_ctx->cinfo_tree.all_stream, ...)
        {
            if (cinfo_iter->codec_private_data == saved_private_data)
                cinfo_iter->codec_private_data = NULL;
        }
    }
}

Test Plan

Tests Validated (27 total)

All tests that were failing with exit code 134 now pass:

Teletext Section (21 tests):

Test ID Sample Arguments Result
63 e639e54550 --datapid 2310 --autoprogram --out=srt --latin1 PASS
64 4e56e88ba4 --autoprogram --out=srt --latin1 PASS
65 c0d2fba8c0 --autoprogram --out=ttxt --latin1 PASS
66 006fdc391a --autoprogram --out=ttxt --latin1 PASS
67 e92a1d4d2a --autoprogram --out=ttxt --latin1 PASS
68 b37ce60eb9 --autoprogram --out=ttxt --latin1 PASS
69 7e4ebf7fd7 --autoprogram --out=ttxt --latin1 PASS
70 9256a60e4b --autoprogram --out=ttxt --latin1 PASS
71 27d7a43dd6 --autoprogram --out=ttxt --latin1 PASS
72 297a44921a --autoprogram --out=ttxt --latin1 PASS
73 efbe129086 --autoprogram --out=ttxt --latin1 PASS
74 eae0077731 --autoprogram --out=ttxt --latin1 PASS
75 e2e2b501e0 --autoprogram --out=ttxt --latin1 PASS
76 8c1615c1a8 --autoprogram --out=ttxt --latin1 PASS
77 c6407fb294 --autoprogram --out=ttxt --latin1 PASS
78 dcada745de --autoprogram --out=ttxt --latin1 --datets PASS
79 5d5838bde9 --autoprogram --out=srt --latin1 --tpage 398 PASS
80 44c45593fb --autoprogram --out=srt --latin1 --tpage 299 PASS
81 b8c55aa2e9 --autoprogram --out=srt --latin1 --tpage 299 PASS
82 3b276ad8bf --autoprogram --out=srt --latin1 --teletext --tpage 398 PASS
83 b236a0590b --autoprogram --out=ttxt --latin1 PASS

DVB Section (2 tests):

Test ID Sample Arguments Result
18 f1422b8bfe --autoprogram --out=srt --latin1 PASS
19 85c7fc1ad7 --datapid 5603 --autoprogram --out=srt --latin1 --teletext PASS

Other Teletext Tests (4 tests):

Test ID Sample Arguments Result
224 96efd279cf --xmltv=3 --out=null PASS (exit 10)
234 4e56e88ba4 --tpage 801 PASS
235 4e56e88ba4 --tverbose PASS
236 4e56e88ba4 --teletext PASS

Valgrind Verification

Confirmed no double-free or invalid free errors with valgrind:

$ valgrind ccextractor ... | grep -E "(Invalid free|double free)"
(no output - no errors)

CI Reference

Failing CI run: https://sampleplatform.ccextractor.org/test/6880

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/CCExtractor/ccextractor/pull/1838 **Author:** [@cfsmp3](https://github.com/cfsmp3) **Created:** 12/17/2025 **Status:** ✅ Merged **Merged:** 12/17/2025 **Merged by:** [@cfsmp3](https://github.com/cfsmp3) **Base:** `master` ← **Head:** `fix/teletext-double-free-crash` --- ### 📝 Commits (1) - [`9cf96b1`](https://github.com/CCExtractor/ccextractor/commit/9cf96b18996a7bcfa616dc86e05272167ee120aa) fix(teletext): Prevent double-free crash in teletext cleanup ### 📊 Changes **1 file changed** (+14 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/lib_ccx/general_loop.c` (+14 -0) </details> ### 📄 Description ## Summary This PR fixes a critical double-free bug that caused CCExtractor to crash with exit code 134 (SIGABRT) when processing teletext streams. The crash occurred during cleanup after processing completed. ## Problem The teletext context (`TeletextCtx`) pointer was shared between two structures: - `dec_ctx->private_data` (decoder context) - `cinfo->codec_private_data` (capture info in `cinfo_tree`) **Crash sequence:** 1. `general_loop()` calls `telxcc_close(&dec_ctx->private_data)` → frees TeletextCtx, NULLs `dec_ctx->private_data` 2. But `cinfo->codec_private_data` still points to the freed memory (not NULLed!) 3. During cleanup, `dinit_cap()` sees non-NULL `cinfo->codec_private_data` 4. `dinit_cap()` calls `telxcc_close()` again → **double-free crash** ## Root Cause This bug was exposed by commit 7e1a01447 ("fix(ocr): Improve DVB subtitle OCR quality") which added cleanup code to `dinit_cap()` to free `codec_private_data`. The `telxcc_close()` call in `general_loop()` has existed since 2015, but the double-free only became possible after the new cleanup code was added. ## Solution After `telxcc_close()` frees the teletext context in `general_loop()`, iterate through all `cinfo` entries and NULL out any that shared the same pointer: ```c if (dec_ctx->codec == CCX_CODEC_TELETEXT) { void *saved_private_data = dec_ctx->private_data; telxcc_close(&dec_ctx->private_data, &dec_ctx->dec_sub); // NULL out any cinfo entries that shared this private_data pointer if (saved_private_data && ctx->demux_ctx) { struct cap_info *cinfo_iter; list_for_each_entry(cinfo_iter, &ctx->demux_ctx->cinfo_tree.all_stream, ...) { if (cinfo_iter->codec_private_data == saved_private_data) cinfo_iter->codec_private_data = NULL; } } } ``` ## Test Plan ### Tests Validated (27 total) All tests that were failing with exit code 134 now pass: **Teletext Section (21 tests):** | Test ID | Sample | Arguments | Result | |---------|--------|-----------|--------| | 63 | e639e54550 | `--datapid 2310 --autoprogram --out=srt --latin1` | ✅ PASS | | 64 | 4e56e88ba4 | `--autoprogram --out=srt --latin1` | ✅ PASS | | 65 | c0d2fba8c0 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 66 | 006fdc391a | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 67 | e92a1d4d2a | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 68 | b37ce60eb9 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 69 | 7e4ebf7fd7 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 70 | 9256a60e4b | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 71 | 27d7a43dd6 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 72 | 297a44921a | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 73 | efbe129086 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 74 | eae0077731 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 75 | e2e2b501e0 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 76 | 8c1615c1a8 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 77 | c6407fb294 | `--autoprogram --out=ttxt --latin1` | ✅ PASS | | 78 | dcada745de | `--autoprogram --out=ttxt --latin1 --datets` | ✅ PASS | | 79 | 5d5838bde9 | `--autoprogram --out=srt --latin1 --tpage 398` | ✅ PASS | | 80 | 44c45593fb | `--autoprogram --out=srt --latin1 --tpage 299` | ✅ PASS | | 81 | b8c55aa2e9 | `--autoprogram --out=srt --latin1 --tpage 299` | ✅ PASS | | 82 | 3b276ad8bf | `--autoprogram --out=srt --latin1 --teletext --tpage 398` | ✅ PASS | | 83 | b236a0590b | `--autoprogram --out=ttxt --latin1` | ✅ PASS | **DVB Section (2 tests):** | Test ID | Sample | Arguments | Result | |---------|--------|-----------|--------| | 18 | f1422b8bfe | `--autoprogram --out=srt --latin1` | ✅ PASS | | 19 | 85c7fc1ad7 | `--datapid 5603 --autoprogram --out=srt --latin1 --teletext` | ✅ PASS | **Other Teletext Tests (4 tests):** | Test ID | Sample | Arguments | Result | |---------|--------|-----------|--------| | 224 | 96efd279cf | `--xmltv=3 --out=null` | ✅ PASS (exit 10) | | 234 | 4e56e88ba4 | `--tpage 801` | ✅ PASS | | 235 | 4e56e88ba4 | `--tverbose` | ✅ PASS | | 236 | 4e56e88ba4 | `--teletext` | ✅ PASS | ### Valgrind Verification Confirmed no double-free or invalid free errors with valgrind: ``` $ valgrind ccextractor ... | grep -E "(Invalid free|double free)" (no output - no errors) ``` ## CI Reference Failing CI run: https://sampleplatform.ccextractor.org/test/6880 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
claunia added the pull-request label 2026-01-29 17:23:00 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/ccextractor#2597