fix(memory): Fix use-after-free in Teletext and uninitialized variables

This commit fixes several Valgrind-detected memory issues:

1. Use-after-free in Teletext during PAT changes:
   - When parse_PAT() calls dinit_cap() to reinitialize stream info,
     it freed the Teletext context but dec_ctx->private_data still
     pointed to the freed memory
   - Fixed by NULLing out dec_ctx->private_data in dinit_cap() when
     freeing shared codec private data
   - Also added NULL check in process_data() before calling teletext
     functions to gracefully handle freed contexts

2. Uninitialized variables in general_loop():
   - stream_mode, get_more_data, ret, and program_iter were declared
     without initialization
   - While logically set before use, Valgrind tracked them as
     potentially uninitialized through complex control flow
   - Fixed by initializing all variables at declaration

These fixes eliminate millions of Valgrind errors in teletext tests
(tests 78, 80) and uninitialized value warnings (tests 67, 84, 86).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Carlos Fernandez
2025-12-17 13:44:13 +01:00
parent 3d5d8e2a0a
commit 7b1a169b8f
2 changed files with 43 additions and 19 deletions

View File

@@ -669,24 +669,33 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str
{
// telxcc_update_gt(dec_ctx->private_data, ctx->demux_ctx->global_timestamp);
/* Process Teletext packets even when no encoder context exists (e.g. -out=report).
This enables tlt_process_pes_packet() to detect subtitle pages by populating
the seen_sub_page[] array inside the teletext decoder. */
int sentence_cap = enc_ctx ? enc_ctx->sentence_cap : 0;
/* Check if teletext context is still valid (may have been freed by dinit_cap
during PAT change while stream was being processed) */
if (!dec_ctx->private_data)
{
got = data_node->len; // Skip processing, context was freed
}
else
{
/* Process Teletext packets even when no encoder context exists (e.g. -out=report).
This enables tlt_process_pes_packet() to detect subtitle pages by populating
the seen_sub_page[] array inside the teletext decoder. */
int sentence_cap = enc_ctx ? enc_ctx->sentence_cap : 0;
ret = tlt_process_pes_packet(
dec_ctx,
data_node->buffer,
data_node->len,
dec_sub,
sentence_cap);
ret = tlt_process_pes_packet(
dec_ctx,
data_node->buffer,
data_node->len,
dec_sub,
sentence_cap);
/* If Teletext decoding fails with invalid data, abort processing */
if (ret == CCX_EINVAL)
return ret;
/* If Teletext decoding fails with invalid data, abort processing */
if (ret == CCX_EINVAL)
return ret;
/* Mark processed byte count */
got = data_node->len;
/* Mark processed byte count */
got = data_node->len;
}
}
else if (data_node->bufferdatatype == CCX_PRIVATE_MPEG2_CC)
{
@@ -1030,11 +1039,11 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx,
int general_loop(struct lib_ccx_ctx *ctx)
{
struct lib_cc_decode *dec_ctx = NULL;
enum ccx_stream_mode_enum stream_mode;
enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND;
struct demuxer_data *datalist = NULL;
struct demuxer_data *data_node = NULL;
int (*get_more_data)(struct lib_ccx_ctx *c, struct demuxer_data **d);
int ret;
int (*get_more_data)(struct lib_ccx_ctx *c, struct demuxer_data **d) = NULL;
int ret = 0;
int caps = 0;
uint64_t min_pts = UINT64_MAX;
@@ -1115,7 +1124,7 @@ int general_loop(struct lib_ccx_ctx *ctx)
else
{
struct cap_info *cinfo = NULL;
struct cap_info *program_iter;
struct cap_info *program_iter = NULL;
struct cap_info *ptr = &ctx->demux_ctx->cinfo_tree;
struct encoder_ctx *enc_ctx = NULL;
list_for_each_entry(program_iter, &ptr->pg_stream, pg_stream, struct cap_info)

View File

@@ -266,6 +266,8 @@ int update_capinfo(struct ccx_demuxer *ctx, int pid, enum ccx_stream_type stream
void dinit_cap(struct ccx_demuxer *ctx)
{
struct cap_info *iter;
struct lib_ccx_ctx *lctx = (struct lib_ccx_ctx *)ctx->parent;
while (!list_empty(&ctx->cinfo_tree.all_stream))
{
iter = list_entry(ctx->cinfo_tree.all_stream.next, struct cap_info, all_stream);
@@ -275,12 +277,25 @@ void dinit_cap(struct ccx_demuxer *ctx)
// The pointer may have been NULLed by dinit_libraries if it was shared
if (iter->codec_private_data)
{
void *saved_private_data = iter->codec_private_data;
if (iter->codec == CCX_CODEC_DVB)
dvbsub_close_decoder(&iter->codec_private_data);
else if (iter->codec == CCX_CODEC_TELETEXT)
telxcc_close(&iter->codec_private_data, NULL);
else
free(iter->codec_private_data);
// Also NULL out any decoder contexts that shared this private_data pointer
// to prevent use-after-free when PAT changes during stream processing
if (lctx)
{
struct lib_cc_decode *dec_ctx;
list_for_each_entry(dec_ctx, &lctx->dec_ctx_head, list, struct lib_cc_decode)
{
if (dec_ctx->private_data == saved_private_data)
dec_ctx->private_data = NULL;
}
}
}
free(iter);
}