From 11fe3352237ee5080c5155da69096f1ac8186dad Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sun, 8 Nov 2026 19:04:41 -0500 Subject: [PATCH 1/2] Apply suggested fix to src/utils/webrtc_candidates.c from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/utils/webrtc_candidates.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/webrtc_candidates.c b/src/utils/webrtc_candidates.c index 26d1cce89..87d4de859 100644 --- a/src/utils/webrtc_candidates.c +++ b/src/utils/webrtc_candidates.c @@ -101,6 +101,13 @@ static bool candidate_address(const char *e, size_t n, int default_port, int go2rtc_parse_external_candidates(const char *list, int port, char out[][GO2RTC_CANDIDATE_SIZE], int max) { if (!list) return 0; + + const char *scan = list; + while (*scan == ',' || *scan == ';' || *scan == ' ' || *scan == '\t' || *scan == '\n' || *scan == '\r') scan++; + if (!*scan) return 0; + + if (!out || max <= 0) return -1; + if (port <= 0 || port > 65535) port = 8555; int count = 0; const char *p = list; From 1e342e0ae483999b78a6d9e207f937ae4d09b0a8 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sun, 8 Nov 2026 19:08:52 -0500 Subject: [PATCH 2/2] fix(webrtc): guard candidate output in the parser loop --- include/utils/webrtc_candidates.h | 3 +- src/utils/webrtc_candidates.c | 7 +---- .../test_go2rtc_process_config_generation.c | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/include/utils/webrtc_candidates.h b/include/utils/webrtc_candidates.h index 1f29fbf1d..822d55076 100644 --- a/include/utils/webrtc_candidates.h +++ b/include/utils/webrtc_candidates.h @@ -22,7 +22,8 @@ * Duplicates collapse. Output strings are safe inside a double-quoted YAML scalar. * * @return number of candidates written (0 when the list is empty or NULL), - * or -1 when any entry is invalid or there are more than max entries. + * or -1 when any entry is invalid, there are more than max entries, + * or a nonempty list is given with NULL output or nonpositive max. */ int go2rtc_parse_external_candidates(const char *list, int port, char out[][GO2RTC_CANDIDATE_SIZE], int max); diff --git a/src/utils/webrtc_candidates.c b/src/utils/webrtc_candidates.c index 87d4de859..1fa5dfa26 100644 --- a/src/utils/webrtc_candidates.c +++ b/src/utils/webrtc_candidates.c @@ -102,18 +102,13 @@ int go2rtc_parse_external_candidates(const char *list, int port, char out[][GO2RTC_CANDIDATE_SIZE], int max) { if (!list) return 0; - const char *scan = list; - while (*scan == ',' || *scan == ';' || *scan == ' ' || *scan == '\t' || *scan == '\n' || *scan == '\r') scan++; - if (!*scan) return 0; - - if (!out || max <= 0) return -1; - if (port <= 0 || port > 65535) port = 8555; int count = 0; const char *p = list; for (;;) { while (*p == ',' || *p == ';' || *p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; if (!*p) break; + if (!out || max <= 0) return -1; const char *start = p; while (*p && *p != ',' && *p != ';' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++; size_t n = (size_t)(p - start); diff --git a/tests/unit/test_go2rtc_process_config_generation.c b/tests/unit/test_go2rtc_process_config_generation.c index f2eb10f67..88ae1fd5c 100644 --- a/tests/unit/test_go2rtc_process_config_generation.c +++ b/tests/unit/test_go2rtc_process_config_generation.c @@ -244,6 +244,34 @@ void test_external_candidates_parse_valid_and_reject_invalid(void) { } } +void test_external_candidates_output_bounds(void) { + char out[2][GO2RTC_CANDIDATE_SIZE]; + memset(out, 'X', sizeof(out)); + char original[sizeof(out)]; + memcpy(original, out, sizeof(out)); + + /* No candidates means no output storage is needed. */ + const char *empty[] = {NULL, "", " ,;\t\n\r "}; + for (size_t i = 0; i < sizeof(empty) / sizeof(empty[0]); i++) { + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates(empty[i], 8555, NULL, 1)); + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates(empty[i], 8555, NULL, 0)); + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates(empty[i], 8555, out, -1)); + } + + TEST_ASSERT_EQUAL_INT(-1, go2rtc_parse_external_candidates(" ,; 203.0.113.7", 8555, NULL, 1)); + TEST_ASSERT_EQUAL_INT(-1, go2rtc_parse_external_candidates("203.0.113.7", 8555, NULL, 0)); + TEST_ASSERT_EQUAL_INT(-1, go2rtc_parse_external_candidates("203.0.113.7", 8555, out, 0)); + TEST_ASSERT_EQUAL_INT(-1, go2rtc_parse_external_candidates("203.0.113.7", 8555, out, -1)); + TEST_ASSERT_EQUAL_MEMORY(original, out, sizeof(out)); + + /* A duplicate at capacity is valid, but a new entry cannot overwrite the sentinel. */ + TEST_ASSERT_EQUAL_INT(1, go2rtc_parse_external_candidates("203.0.113.7,203.0.113.7:8555", 8555, out, 1)); + TEST_ASSERT_EQUAL_STRING("203.0.113.7:8555", out[0]); + TEST_ASSERT_EQUAL_MEMORY(original + GO2RTC_CANDIDATE_SIZE, out[1], GO2RTC_CANDIDATE_SIZE); + TEST_ASSERT_EQUAL_INT(-1, go2rtc_parse_external_candidates("203.0.113.7,203.0.113.8", 8555, out, 1)); + TEST_ASSERT_EQUAL_MEMORY(original + GO2RTC_CANDIDATE_SIZE, out[1], GO2RTC_CANDIDATE_SIZE); +} + void test_generate_startup_config_writes_db_override_file(void) { load_default_config(&g_config); g_config.go2rtc_api_port = 31984; @@ -620,6 +648,7 @@ int main(int argc, char **argv) { RUN_TEST(test_generate_startup_config_uses_saved_webrtc_settings); RUN_TEST(test_generate_startup_config_writes_one_candidate_per_external_ip); RUN_TEST(test_external_candidates_parse_valid_and_reject_invalid); + RUN_TEST(test_external_candidates_output_bounds); RUN_TEST(test_generate_startup_config_writes_db_override_file); RUN_TEST(test_override_publication_preserves_old_file_on_io_failure_and_recovers); RUN_TEST(test_audio_policy_keeps_db_override_dormant);