Skip to content
Merged
25 changes: 25 additions & 0 deletions src/lib_ccx/ccx_decoders_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,31 @@ void dinit_cc_decode(struct lib_cc_decode **ctx)
dinit_timing_ctx(&lctx->timing);
free_decoder_context(lctx->prev);
free_subtitle(lctx->dec_sub.prev);
/* Free the embedded dec_sub's data field (allocated by write_cc_buffer) */
if (lctx->dec_sub.datatype == CC_DATATYPE_DVB)
{
struct cc_bitmap *bitmap = (struct cc_bitmap *)lctx->dec_sub.data;
if (bitmap)
{
freep(&bitmap->data0);
freep(&bitmap->data1);
}
}
/* Free any leftover XDS strings that weren't processed by the encoder */
if (lctx->dec_sub.type == CC_608 && lctx->dec_sub.data)
{
struct eia608_screen *data = (struct eia608_screen *)lctx->dec_sub.data;
for (int i = 0; i < lctx->dec_sub.nb_data; i++, data++)
{
if (data->format == SFORMAT_XDS && data->xds_str)
{
freep(&data->xds_str);
}
}
}
freep(&lctx->dec_sub.data);
/* Note: xds_ctx is freed in general_loop.c, mp4.c etc. during normal processing.
Don't free it here as it may cause double-free if already freed elsewhere. */
freep(ctx);
}

Expand Down
2 changes: 2 additions & 0 deletions src/lib_ccx/ccx_decoders_xds.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, con
if (n > -1 && n < size)
{
write_xds_string(sub, ctx, p, n);
/* Note: Don't free(p) here - the pointer is stored in data->xds_str
and will be freed by the encoder or decoder cleanup code */
return;
}
/* Else try again with more space. */
Expand Down
2 changes: 1 addition & 1 deletion src/lib_ccx/ccx_demuxer.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ void ccx_demuxer_delete(struct ccx_demuxer **ctx)
struct ccx_demuxer *init_demuxer(void *parent, struct demuxer_cfg *cfg)
{
int i;
struct ccx_demuxer *ctx = malloc(sizeof(struct ccx_demuxer));
struct ccx_demuxer *ctx = calloc(1, sizeof(struct ccx_demuxer));
if (!ctx)
return NULL;

Expand Down
5 changes: 5 additions & 0 deletions src/lib_ccx/ccx_encoders_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,11 @@ int encode_sub(struct encoder_ctx *context, struct cc_subtitle *sub)
// After adding delay, if start/end time is lower than 0, then continue with the next subtitle
if (data->start_time < 0 || data->end_time <= 0)
{
// Free XDS string if skipping to avoid memory leak
if (data->format == SFORMAT_XDS && data->xds_str)
{
freep(&data->xds_str);
}
continue;
}

Expand Down
4 changes: 1 addition & 3 deletions src/lib_ccx/ccx_encoders_spupng.c
Original file line number Diff line number Diff line change
Expand Up @@ -570,10 +570,8 @@ int write_image(struct pixel_t *buffer, FILE *fp, int width, int height)
png_write_end(png_ptr, NULL);

finalise:
if (info_ptr != NULL)
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL)
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
png_destroy_write_struct(&png_ptr, &info_ptr);
if (row != NULL)
free(row);

Expand Down
55 changes: 33 additions & 22 deletions src/lib_ccx/general_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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;

/* Mark processed byte count */
got = data_node->len;
/* 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);

/* If Teletext decoding fails with invalid data, abort processing */
if (ret == CCX_EINVAL)
return ret;

/* Mark processed byte count */
got = data_node->len;
}
}
else if (data_node->bufferdatatype == CCX_PRIVATE_MPEG2_CC)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1439,6 +1448,8 @@ int rcwt_loop(struct lib_ccx_ctx *ctx)
} // end while(1)

dbg_print(CCX_DMT_PARSE, "Processed %d bytes\n", bread);
/* Free XDS context - similar to cleanup in general_loop */
free(dec_ctx->xds_ctx);
free(parsebuf);
return caps;
}
3 changes: 2 additions & 1 deletion src/lib_ccx/lib_ccx.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ static struct ccx_decoders_common_settings_t *init_decoder_setting(
{
struct ccx_decoders_common_settings_t *setting;

setting = malloc(sizeof(struct ccx_decoders_common_settings_t));
setting = calloc(1, sizeof(struct ccx_decoders_common_settings_t));
if (!setting)
return NULL;

Expand All @@ -33,6 +33,7 @@ static struct ccx_decoders_common_settings_t *init_decoder_setting(
setting->hauppauge_mode = opt->hauppauge_mode;
setting->xds_write_to_file = opt->transcript_settings.xds;
setting->ocr_quantmode = opt->ocr_quantmode;
// program_number, codec, and private_data are zero-initialized by calloc

return setting;
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib_ccx/telxcc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1516,10 +1516,12 @@ int tlt_process_pes_packet(struct lib_cc_decode *dec_ctx, uint8_t *buffer, uint1
// Called only when teletext is detected or forced and it's going to be used for extraction.
void *telxcc_init(void)
{
struct TeletextCtx *ctx = malloc(sizeof(struct TeletextCtx));
// Use calloc to zero-initialize all fields, preventing uninitialized memory errors
struct TeletextCtx *ctx = calloc(1, sizeof(struct TeletextCtx));

if (!ctx)
return NULL;
// These memsets are now redundant but kept for clarity
memset(ctx->seen_sub_page, 0, MAX_TLT_PAGES * sizeof(short int));
memset(ctx->cc_map, 0, 256);

Expand Down
15 changes: 15 additions & 0 deletions src/lib_ccx/ts_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
92 changes: 74 additions & 18 deletions src/rust/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::ctorust::FromCType;
use crate::demuxer::common_types::{
CapInfo, CcxDemuxReport, CcxRational, PMTEntry, PSIBuffer, ProgramInfo,
};
use crate::utils::free_rust_c_string;
use crate::utils::null_pointer;
use crate::utils::replace_rust_c_string;
use crate::utils::string_to_c_char;
use crate::utils::string_to_c_chars;
use lib_ccxr::common::Decoder608Report;
Expand Down Expand Up @@ -108,7 +110,8 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
(*ccx_s_options).no_progress_bar = options.no_progress_bar as _;

if options.sentence_cap_file.try_exists().unwrap_or_default() {
(*ccx_s_options).sentence_cap_file = string_to_c_char(
(*ccx_s_options).sentence_cap_file = replace_rust_c_string(
(*ccx_s_options).sentence_cap_file,
options
.sentence_cap_file
.clone()
Expand All @@ -127,7 +130,8 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
.try_exists()
.unwrap_or_default()
{
(*ccx_s_options).filter_profanity_file = string_to_c_char(
(*ccx_s_options).filter_profanity_file = replace_rust_c_string(
(*ccx_s_options).filter_profanity_file,
options
.filter_profanity_file
.clone()
Expand Down Expand Up @@ -166,16 +170,20 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
(*ccx_s_options).hardsubx = options.hardsubx as _;
(*ccx_s_options).hardsubx_and_common = options.hardsubx_and_common as _;
if let Some(dvblang) = options.dvblang {
(*ccx_s_options).dvblang = string_to_c_char(dvblang.to_ctype().as_str());
(*ccx_s_options).dvblang =
replace_rust_c_string((*ccx_s_options).dvblang, dvblang.to_ctype().as_str());
}
if let Some(ref ocrlang) = options.ocrlang {
(*ccx_s_options).ocrlang = string_to_c_char(ocrlang.as_str());
// Cast const to mut for freeing - safe because we allocated this with string_to_c_char
(*ccx_s_options).ocrlang =
replace_rust_c_string((*ccx_s_options).ocrlang as *mut _, ocrlang.as_str());
}
(*ccx_s_options).ocr_oem = options.ocr_oem as _;
(*ccx_s_options).psm = options.psm as _;
(*ccx_s_options).ocr_quantmode = options.ocr_quantmode as _;
if let Some(mkvlang) = options.mkvlang {
(*ccx_s_options).mkvlang = string_to_c_char(mkvlang.to_ctype().as_str());
(*ccx_s_options).mkvlang =
replace_rust_c_string((*ccx_s_options).mkvlang, mkvlang.to_ctype().as_str());
}
(*ccx_s_options).analyze_video_stream = options.analyze_video_stream as _;
(*ccx_s_options).hardsubx_ocr_mode = options.hardsubx_ocr_mode.to_ctype();
Expand All @@ -193,34 +201,57 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
(*ccx_s_options).debug_mask = options.debug_mask.normal_mask().bits() as _;
(*ccx_s_options).debug_mask_on_debug = options.debug_mask.debug_mask().bits() as _;
if options.udpsrc.is_some() {
(*ccx_s_options).udpsrc = string_to_c_char(&options.udpsrc.clone().unwrap());
(*ccx_s_options).udpsrc =
replace_rust_c_string((*ccx_s_options).udpsrc, &options.udpsrc.clone().unwrap());
}
if options.udpaddr.is_some() {
(*ccx_s_options).udpaddr = string_to_c_char(&options.udpaddr.clone().unwrap());
(*ccx_s_options).udpaddr =
replace_rust_c_string((*ccx_s_options).udpaddr, &options.udpaddr.clone().unwrap());
}
(*ccx_s_options).udpport = options.udpport as _;
if options.tcpport.is_some() {
(*ccx_s_options).tcpport = string_to_c_char(&options.tcpport.unwrap().to_string());
(*ccx_s_options).tcpport = replace_rust_c_string(
(*ccx_s_options).tcpport,
&options.tcpport.unwrap().to_string(),
);
}
if options.tcp_password.is_some() {
(*ccx_s_options).tcp_password = string_to_c_char(&options.tcp_password.clone().unwrap());
(*ccx_s_options).tcp_password = replace_rust_c_string(
(*ccx_s_options).tcp_password,
&options.tcp_password.clone().unwrap(),
);
}
if options.tcp_desc.is_some() {
(*ccx_s_options).tcp_desc = string_to_c_char(&options.tcp_desc.clone().unwrap());
(*ccx_s_options).tcp_desc = replace_rust_c_string(
(*ccx_s_options).tcp_desc,
&options.tcp_desc.clone().unwrap(),
);
}
if options.srv_addr.is_some() {
(*ccx_s_options).srv_addr = string_to_c_char(&options.srv_addr.clone().unwrap());
(*ccx_s_options).srv_addr = replace_rust_c_string(
(*ccx_s_options).srv_addr,
&options.srv_addr.clone().unwrap(),
);
}
if options.srv_port.is_some() {
(*ccx_s_options).srv_port = string_to_c_char(&options.srv_port.unwrap().to_string());
(*ccx_s_options).srv_port = replace_rust_c_string(
(*ccx_s_options).srv_port,
&options.srv_port.unwrap().to_string(),
);
}
(*ccx_s_options).noautotimeref = options.noautotimeref as _;
(*ccx_s_options).input_source = options.input_source as _;
if options.output_filename.is_some() {
(*ccx_s_options).output_filename =
string_to_c_char(&options.output_filename.clone().unwrap());
(*ccx_s_options).output_filename = replace_rust_c_string(
(*ccx_s_options).output_filename,
&options.output_filename.clone().unwrap(),
);
}
if options.inputfile.is_some() {
// Only set inputfile if it's not already set (first call from ccxr_parse_parameters).
// Subsequent calls from ccxr_demuxer_open/close should NOT modify inputfile because
// C code holds references to those strings throughout processing.
// Freeing them would cause use-after-free and double-free errors.
if options.inputfile.is_some() && (*ccx_s_options).inputfile.is_null() {
(*ccx_s_options).inputfile = string_to_c_chars(options.inputfile.clone().unwrap());
(*ccx_s_options).num_input_files = options
.inputfile
Expand All @@ -231,7 +262,12 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
.count() as _;
}
(*ccx_s_options).demux_cfg = options.demux_cfg.to_ctype();
(*ccx_s_options).enc_cfg = options.enc_cfg.to_ctype();
// Only set enc_cfg on the first call (when output_filename is null).
// Subsequent calls from ccxr_demuxer_open/close should NOT modify enc_cfg
// because it causes memory leaks (strings allocated but never freed).
if (*ccx_s_options).enc_cfg.output_filename.is_null() {
(*ccx_s_options).enc_cfg = options.enc_cfg.to_ctype();
}
(*ccx_s_options).subs_delay = options.subs_delay.millis();
(*ccx_s_options).cc_to_stdout = options.cc_to_stdout as _;
(*ccx_s_options).pes_header_to_stdout = options.pes_header_to_stdout as _;
Expand All @@ -242,8 +278,10 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
#[cfg(feature = "with_libcurl")]
{
if options.curlposturl.is_some() {
(*ccx_s_options).curlposturl =
string_to_c_char(&options.curlposturl.as_ref().unwrap_or_default().as_str());
(*ccx_s_options).curlposturl = replace_rust_c_string(
(*ccx_s_options).curlposturl,
options.curlposturl.as_ref().unwrap_or_default().as_str(),
);
}
}
}
Expand Down Expand Up @@ -842,6 +880,24 @@ impl CType<[u32; 128]> for Vec<u32> {
}
}

/// Free all Rust-allocated strings in an encoder_cfg struct.
/// This must be called before overwriting enc_cfg with a new value to avoid memory leaks.
/// # Safety
/// The string pointers must have been allocated by Rust's `string_to_c_char` or be null.
pub unsafe fn free_encoder_cfg_strings(cfg: &encoder_cfg) {
free_rust_c_string(cfg.output_filename);
free_rust_c_string(cfg.start_credits_text);
free_rust_c_string(cfg.end_credits_text);
free_rust_c_string(cfg.first_input_file);
free_rust_c_string(cfg.render_font);
free_rust_c_string(cfg.render_font_italics);
free_rust_c_string(cfg.all_services_charset);
#[cfg(feature = "with_libcurl")]
free_rust_c_string(cfg.curlposturl);
// Note: services_charsets is a *mut *mut c_char (array of strings)
// which would need special handling, but it's typically null in practice
}

impl CType<encoder_cfg> for EncoderConfig {
unsafe fn to_ctype(&self) -> encoder_cfg {
encoder_cfg {
Expand Down
Loading
Loading