From 6b3682ed65244c6c23ca54ccca6d12b3dd2a8f77 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Mon, 29 Jun 2026 17:01:28 +0000 Subject: [PATCH] voice: validate outgoing and deflect call numbers Validate the number parameter in Voice.CreateCall and Call.Deflect D-Bus methods to ensure they do not contain control characters or semicolons. This prevents AT command injection when the number is later spliced into ATD or +CTFR commands. Introduce a common helper mm_utils_is_valid_dial_number() to validate dial numbers, and add unit tests for it. https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/work_items/1067 Signed-off-by: Aleksander Morgado --- src/mm-base-call.c | 7 ++++ src/mm-iface-modem-voice.c | 10 ++---- src/mm-modem-helpers.c | 22 +++++++++++++ src/mm-modem-helpers.h | 2 ++ src/tests/test-modem-helpers.c | 58 ++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/mm-base-call.c b/src/mm-base-call.c index bf13bedc..e1fd60af 100644 --- a/src/mm-base-call.c +++ b/src/mm-base-call.c @@ -440,6 +440,13 @@ handle_deflect_auth_ready (MMAuthProvider *authp, return; } + /* Validate input number */ + if (!mm_utils_is_valid_dial_number (ctx->number, &error)) { + mm_dbus_method_invocation_take_error (ctx->invocation, error); + handle_deflect_context_free (ctx); + return; + } + state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); /* We can only deflect incoming call in ringing or waiting state */ diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c index 69f53cdc..b3907e53 100644 --- a/src/mm-iface-modem-voice.c +++ b/src/mm-iface-modem-voice.c @@ -23,6 +23,7 @@ #include "mm-call-list.h" #include "mm-error-helpers.h" #include "mm-log-object.h" +#include "mm-modem-helpers.h" #define CALL_LIST_POLLING_CONTEXT_TAG "voice-call-list-polling-context-tag" #define IN_CALL_EVENT_CONTEXT_TAG "voice-in-call-event-context-tag" @@ -176,14 +177,9 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self, guint dtmf_tone_duration; /* Don't create CALL from properties if either number is missing */ - number = mm_call_properties_get_number (properties) ; - if (!number) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_INVALID_ARGS, - "Cannot create call: mandatory parameter 'number' is missing"); + number = mm_call_properties_get_number (properties); + if (!mm_utils_is_valid_dial_number (number, error)) return NULL; - } dtmf_tone_duration = mm_call_properties_get_dtmf_tone_duration (properties) ; diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index 8be5fb63..ef129db5 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -5992,3 +5992,25 @@ mm_utils_is_numeric (const gchar *str) } return TRUE; } + +gboolean +mm_utils_is_valid_dial_number (const gchar *str, + GError **error) +{ + const gchar *c; + + if (!str || !str[0]) { + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "missing number"); + return FALSE; + } + + for (c = str; *c; c++) { + if (((guint8)*c) < 0x20 || *c == ';') { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "invalid character '0x%02x' in number", (guint8)*c); + return FALSE; + } + } + return TRUE; +} diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h index e21ec7eb..482809e2 100644 --- a/src/mm-modem-helpers.h +++ b/src/mm-modem-helpers.h @@ -574,6 +574,8 @@ gboolean mm_parse_supl_address (const gchar *supl, void mm_utils_remove_control_characters (gchar *str); gboolean mm_utils_is_valid_fqdn (const gchar *fqdn); gboolean mm_utils_is_numeric (const gchar *str); +gboolean mm_utils_is_valid_dial_number (const gchar *str, + GError **error); gboolean mm_validate_cbs_channels (GArray *channels, GError **error); diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c index 556d9606..c8583c63 100644 --- a/src/tests/test-modem-helpers.c +++ b/src/tests/test-modem-helpers.c @@ -4923,6 +4923,63 @@ test_is_numeric (void *f, gpointer d) g_assert (!mm_utils_is_numeric ("+123")); } +static void +test_is_valid_dial_number (void *f, gpointer d) +{ + GError *error = NULL; + gboolean r; + + r = mm_utils_is_valid_dial_number ("123456", &error); + g_assert (r); + g_assert_no_error (error); + + r = mm_utils_is_valid_dial_number ("+123456", &error); + g_assert (r); + g_assert_no_error (error); + + r = mm_utils_is_valid_dial_number ("*123#", &error); + g_assert (r); + g_assert_no_error (error); + + r = mm_utils_is_valid_dial_number ("123,456", &error); + g_assert (r); + g_assert_no_error (error); + + r = mm_utils_is_valid_dial_number ("123W456", &error); + g_assert (r); + g_assert_no_error (error); + + r = mm_utils_is_valid_dial_number (NULL, &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); + + r = mm_utils_is_valid_dial_number ("", &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); + + r = mm_utils_is_valid_dial_number ("123;456", &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); + + r = mm_utils_is_valid_dial_number ("123\r456", &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); + + r = mm_utils_is_valid_dial_number ("123\n456", &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); + + r = mm_utils_is_valid_dial_number ("123\x01" "456", &error); + g_assert (!r); + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS); + g_clear_error (&error); +} + /*****************************************************************************/ typedef struct { @@ -5472,6 +5529,7 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_remove_control_characters, NULL)); g_test_suite_add (suite, TESTCASE (test_is_valid_fqdn, NULL)); g_test_suite_add (suite, TESTCASE (test_is_numeric, NULL)); + g_test_suite_add (suite, TESTCASE (test_is_valid_dial_number, NULL)); g_test_suite_add (suite, TESTCASE (test_cpol_response, NULL));