CVE-2023-49786: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') (CWE-362)
value: Asterisk is an open source private branch exchange and telephony toolkit. In Asterisk prior to versions 18.20.1, 20.5.1, and 21.0.1; as well as certified-asterisk prior to 18.9-cert6; Asterisk is susceptible to a DoS due to a race condition in the hello handshake phase of the DTLS protocol when handling DTLS-SRTP for media setup. This attack can be done continuously, thus denying new DTLS-SRTP encrypted calls during the attack. Abuse of this vulnerability may lead to a massive Denial of Service on vulnerable Asterisk servers for calls that rely on DTLS-SRTP. Commit d7d7764cb07c8a1872804321302ef93bf62cba05 contains a fix, which is part of versions 18.20.1, 20.5.1, 21.0.1, amd 18.9-cert6.
CWE description: The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
CVSS v3 base score: 5.9
Published: 2023-12-14T20:15Z
Repository: https://github.com/asterisk/asterisk
Language: C
Files changed: res/res_rtp_asterisk.c
Commit date: 2023-12-14 18:48:17 +0000
Commit message: res_rtp_asterisk.c: Check DTLS packets against ICE candidate list

When ICE is in use, we can prevent a possible DOS attack by allowing
DTLS protocol messages (client hello, etc) only from sources that
are in the active remote candidates list.

Resolves: GHSA-hxj9-xwr8-w8pq

Diff: diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 92b89f8a7..2341c846f 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -3205,10 +3205,65 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
 			return -1;
 		}
 
 		ast_debug_dtls(3, "(%p) DTLS - __rtp_recvfrom rtp=%p - Got SSL packet '%d'\n", instance, rtp, *in);
 
+		/*
+		 * If ICE is in use, we can prevent a possible DOS attack
+		 * by allowing DTLS protocol messages (client hello, etc)
+		 * only from sources that are in the active remote
+		 * candidates list.
+		 */
+
+		if (rtp->ice) {
+			int pass_src_check = 0;
+			struct ao2_iterator i;
+			struct ast_rtp_engine_ice_candidate *candidate;
+			int cand_cnt = 0;
+
+			/*
+			 * You'd think that this check would cause a "deadlock"
+			 * because ast_rtp_ice_start_media calls dtls_perform_handshake
+			 * before it sets ice_media_started = 1 so how can we do a
+			 * handshake if we're dropping packets before we send them
+			 * to openssl.  Fortunately, dtls_perform_handshake just sets
+			 * up openssl to do the handshake and doesn't actually perform it
+			 * itself and the locking prevents __rtp_recvfrom from
+			 * running before the ice_media_started flag is set.  So only
+			 * unexpected DTLS packets can get dropped here.
+			 */
+			if (!rtp->ice_media_started) {
+				ast_log(LOG_WARNING, "%s: DTLS packet from %s dropped. ICE not completed yet.\n",
+					ast_rtp_instance_get_channel_id(instance),
+					ast_sockaddr_stringify(sa));
+				return 0;
+			}
+
+			/*
+			 * If we got this far, then ice_active_remote_candidates
+			 * can't be NULL.
+			 */
+			i = ao2_iterator_init(rtp->ice_active_remote_candidates, 0);
+			while ((candidate = ao2_iterator_next(&i)) && (cand_cnt < PJ_ICE_MAX_CAND)) {
+				res = ast_sockaddr_cmp_addr(&candidate->address, sa);
+				ao2_ref(candidate, -1);
+				if (res == 0) {
+					pass_src_check = 1;
+					break;
+				}
+				cand_cnt++;
+			}
+			ao2_iterator_destroy(&i);
+
+			if (!pass_src_check) {
+				ast_log(LOG_WARNING, "%s: DTLS packet from %s dropped. Source not in ICE active candidate list.\n",
+					ast_rtp_instance_get_channel_id(instance),
+					ast_sockaddr_stringify(sa));
+				return 0;
+			}
+		}
+
 		/*
 		 * A race condition is prevented between dtls_perform_handshake()
 		 * and this function because both functions have to get the
 		 * instance lock before they can do anything.  The
 		 * dtls_perform_handshake() function needs to start the timer

Fixed code: 		/*
		 * If ICE is in use, we can prevent a possible DOS attack
		 * by allowing DTLS protocol messages (client hello, etc)
		 * only from sources that are in the active remote
		 * candidates list.
		 */

		if (rtp->ice) {
			int pass_src_check = 0;
			struct ao2_iterator i;
			struct ast_rtp_engine_ice_candidate *candidate;
			int cand_cnt = 0;

			/*
			 * You'd think that this check would cause a "deadlock"
			 * because ast_rtp_ice_start_media calls dtls_perform_handshake
			 * before it sets ice_media_started = 1 so how can we do a
			 * handshake if we're dropping packets before we send them
			 * to openssl.  Fortunately, dtls_perform_handshake just sets
			 * up openssl to do the handshake and doesn't actually perform it
			 * itself and the locking prevents __rtp_recvfrom from
			 * running before the ice_media_started flag is set.  So only
			 * unexpected DTLS packets can get dropped here.
			 */
			if (!rtp->ice_media_started) {
				ast_log(LOG_WARNING, "%s: DTLS packet from %s dropped. ICE not completed yet.\n",
					ast_rtp_instance_get_channel_id(instance),
					ast_sockaddr_stringify(sa));
				return 0;
			}

			/*
			 * If we got this far, then ice_active_remote_candidates
			 * can't be NULL.
			 */
			i = ao2_iterator_init(rtp->ice_active_remote_candidates, 0);
			while ((candidate = ao2_iterator_next(&i)) && (cand_cnt < PJ_ICE_MAX_CAND)) {
				res = ast_sockaddr_cmp_addr(&candidate->address, sa);
				ao2_ref(candidate, -1);
				if (res == 0) {
					pass_src_check = 1;
					break;
				}
				cand_cnt++;
			}
			ao2_iterator_destroy(&i);

			if (!pass_src_check) {
				ast_log(LOG_WARNING, "%s: DTLS packet from %s dropped. Source not in ICE active candidate list.\n",
					ast_rtp_instance_get_channel_id(instance),
					ast_sockaddr_stringify(sa));
				return 0;
			}
		}