Commit 7b8339fb52 for asterisk.org

commit 7b8339fb522e67e8aebb019577c20bd317cf4152
Author: Alexis Hadjisotiriou <ah@gilawa.com>
Date:   Thu Feb 26 15:37:56 2026 +0000

    channel: Prevent crash during DTMF emulation when no timing module is loaded

    Description:
    When Asterisk is running without a timing module, attempting to process DTMF
    triggers a segmentation fault. This occurs because the system
    attempts to access a null timing file descriptor when setting up the
    DTMF emulation timer.

    This fix ensures that the system checks for a valid timing source before
    attempting to start the DTMF emulation timer. If no timing module is
    present, it logs a warning and skips the emulation instead of crashing
    the process.

    Changes:
    - Modified main/channel.c to add a safety check within the __ast_read function.
    - Implemented a graceful return path when no timing source is available
    - Added a LOG_WARNING to inform the administrator that DTMF emulation
      was skipped due to missing timing modules.

    Testing:
    - Disabled all timing_ modules in modules.conf and confirmed with
      'timing test'.
    - Reproduced the crash by modifying the dialplan with:
     exten => 707,1,NoOp(Starting DTMF - No Timing Mode)
     same => n,Answer()
     same => n,Background(demo-congrats)
     same => n,WaitExten(10)
     same => n,Hangup()
      And calling 707 followed by 1
    - Verified that with the fix applied, the system logs "No timing module
      loaded; skipping DTMF timer" and continues dialplan
      execution without crashing.
    - Confirmed stability during concurrent media sessions and DTMF input.

    Fixes: #566

diff --git a/main/channel.c b/main/channel.c
index 8121b5c3d7..76e7c3c787 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -2893,7 +2893,12 @@ void ast_deactivate_generator(struct ast_channel *chan)
 	deactivate_generator_nolock(chan);
 	if (should_trigger_dtmf_emulating(chan)) {
 		/* if in the middle of dtmf emulation keep 50 tick per sec timer on rolling */
-		ast_timer_set_rate(ast_channel_timer(chan), 50);
+		struct ast_timer *timer = ast_channel_timer(chan);
+		if (timer) {
+			ast_timer_set_rate(timer, 50);
+		} else {
+			ast_log(LOG_WARNING, "No timing module loaded, DTMF length may be inaccurate\n");
+		}
 	}
 	ast_channel_unlock(chan);
 }
@@ -3183,6 +3188,7 @@ int ast_settimeout_full(struct ast_channel *c, unsigned int rate, int (*func)(co
 {
 	int res;
 	unsigned int real_rate = rate, max_rate;
+	struct ast_timer *timer = ast_channel_timer(c);

 	ast_channel_lock(c);

@@ -3196,13 +3202,13 @@ int ast_settimeout_full(struct ast_channel *c, unsigned int rate, int (*func)(co
 		data = NULL;
 	}

-	if (rate && rate > (max_rate = ast_timer_get_max_rate(ast_channel_timer(c)))) {
+	if (rate && rate > (max_rate = ast_timer_get_max_rate(timer))) {
 		real_rate = max_rate;
 	}

 	ast_debug(3, "Scheduling timer at (%u requested / %u actual) timer ticks per second\n", rate, real_rate);

-	res = ast_timer_set_rate(ast_channel_timer(c), real_rate);
+	res = ast_timer_set_rate(timer, real_rate);

 	if (ast_channel_timingdata(c) && ast_test_flag(ast_channel_flags(c), AST_FLAG_TIMINGDATA_IS_AO2_OBJ)) {
 		ao2_ref(ast_channel_timingdata(c), -1);
@@ -3912,7 +3918,12 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio, int
 					 * timer events to generate null frames.
 					 */
 					if (!ast_channel_generator(chan)) {
-						ast_timer_set_rate(ast_channel_timer(chan), 50);
+						struct ast_timer *timer = ast_channel_timer(chan);
+						if (timer) {
+							ast_timer_set_rate(timer, 50);
+					    } else {
+							ast_log(LOG_WARNING, "No timing module loaded, DTMF length may be inaccurate\n");
+						}
 					}
 				}
 				if (ast_channel_audiohooks(chan)) {
@@ -3962,7 +3973,12 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio, int
 					 * timer events to generate null frames.
 					 */
 					if (!ast_channel_generator(chan)) {
-						ast_timer_set_rate(ast_channel_timer(chan), 50);
+						struct ast_timer *timer = ast_channel_timer(chan);
+						if (timer) {
+							ast_timer_set_rate(timer, 50);
+						} else {
+							ast_log(LOG_WARNING, "No timing module loaded, DTMF length may be inaccurate\n");
+						}
 					}
 				} else {
 					ast_log(LOG_DTMF, "DTMF end passthrough '%c' on %s\n", f->subclass.integer, ast_channel_name(chan));
@@ -3977,7 +3993,12 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio, int
 					 * timer events to generate null frames.
 					 */
 					if (!ast_channel_generator(chan)) {
-						ast_timer_set_rate(ast_channel_timer(chan), 50);
+						struct ast_timer *timer = ast_channel_timer(chan);
+						if (timer) {
+							ast_timer_set_rate(timer, 50);
+						} else {
+							ast_log(LOG_WARNING, "No timing module loaded, DTMF length may be inaccurate\n");
+						}
 					}
 				}
 				if (ast_channel_audiohooks(chan)) {
@@ -4039,7 +4060,12 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio, int
 					 * timer events to generate null frames.
 					 */
 					if (!ast_channel_generator(chan)) {
-						ast_timer_set_rate(ast_channel_timer(chan), 50);
+						struct ast_timer *timer = ast_channel_timer(chan);
+						if (timer) {
+							ast_timer_set_rate(timer, 50);
+						} else {
+							ast_log(LOG_WARNING, "No timing module loaded, DTMF length may be inaccurate\n");
+						}
 					}
 				}
 			}