CVE-2020-15260: Improper Certificate Validation (CWE-295)
value: PJSIP is a free and open source multimedia communication library written in C language implementing standard based protocols such as SIP, SDP, RTP, STUN, TURN, and ICE. In version 2.10 and earlier, PJSIP transport can be reused if they have the same IP address + port + protocol. However, this is insufficient for secure transport since it lacks remote hostname authentication. Suppose we have created a TLS connection to `sip.foo.com`, which has an IP address `100.1.1.1`. If we want to create a TLS connection to another hostname, say `sip.bar.com`, which has the same IP address, then it will reuse that existing connection, even though `100.1.1.1` does not have certificate to authenticate as `sip.bar.com`. The vulnerability allows for an insecure interaction without user awareness. It affects users who need access to connections to different destinations that translate to the same address, and allows man-in-the-middle attack if attacker can route a connection to another destination such as in the case of DNS spoofing.
CWE description: The product does not validate, or incorrectly validates, a certificate.
CVSS v3 base score: 6.8
Published: 2021-03-10T23:15Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjsip/include/pjsip/sip_dialog.h, pjsip/src/pjsip/sip_dialog.c, pjsip/src/pjsip/sip_transport.c, pjsip/src/pjsip/sip_util.c
Commit date: 2021-03-08 17:39:36 +0800
Commit message: Merge pull request from GHSA-8hcp-hm38-mfph

* Check hostname during TLS transport selection

* revision based on feedback

* remove the code in create_request that has been moved
Diff: diff --git a/pjsip/include/pjsip/sip_dialog.h b/pjsip/include/pjsip/sip_dialog.h
index a0214d28c..e314c2ece 100644
--- a/pjsip/include/pjsip/sip_dialog.h
+++ b/pjsip/include/pjsip/sip_dialog.h
@@ -163,10 +163,11 @@ struct pjsip_dialog
     pj_bool_t		add_allow;  /**< Add Allow header in requests?	    */
     pjsip_cid_hdr      *call_id;    /**< Call-ID header.		    */
     pjsip_route_hdr	route_set;  /**< Route set.			    */
     pj_bool_t		route_set_frozen; /**< Route set has been set.	    */
     pjsip_auth_clt_sess	auth_sess;  /**< Client authentication session.	    */
+    pj_str_t		initial_dest;/**< Initial destination host.  	    */
 
     /** Session counter. */
     int			sess_count; /**< Number of sessions.		    */
 
     /** Transaction counter. */
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
index 27530e4f2..9571b5a35 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -465,10 +465,14 @@ pj_status_t create_uas_dialog( pjsip_user_agent *ua,
     } else
 	tmp.slen = len;
 
     /* Save the remote info. */
     pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
+    
+    /* Save initial destination host from transport's info */
+    pj_strdup(dlg->pool, &dlg->initial_dest,
+    	      &rdata->tp_info.transport->remote_name.host);
 
 
     /* Init remote's contact from Contact header.
      * Iterate the Contact URI until we find sip: or sips: scheme.
      */
@@ -1190,10 +1194,16 @@ static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg,
 	status = pjsip_auth_clt_init_req( &dlg->auth_sess, tdata );
 	if (status != PJ_SUCCESS)
 	    return status;
     }
 
+    /* Copy the initial destination host to tdata. This information can be
+     * used later by transport for transport selection.
+     */
+    if (dlg->initial_dest.slen)
+    	pj_strdup(tdata->pool, &tdata->dest_info.name, &dlg->initial_dest);
+
     /* Done. */
     *p_tdata = tdata;
 
     return PJ_SUCCESS;
 }
@@ -1820,10 +1830,15 @@ static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata)
 
     /* Only update route set if this message belongs to the same
      * transaction as the initial transaction that establishes dialog.
      */
     if (dlg->role == PJSIP_ROLE_UAC) {
+    	/* Save initial destination host from transport's info. */
+    	if (!dlg->initial_dest.slen) {
+    	    pj_strdup(dlg->pool, &dlg->initial_dest,
+    	      	      &rdata->tp_info.transport->remote_name.host);
+    	}
 
 	/* Ignore subsequent request from remote */
 	if (msg->type != PJSIP_RESPONSE_MSG)
 	    return;
 
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
index bef6d24fe..177274b08 100644
--- a/pjsip/src/pjsip/sip_transport.c
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -2333,10 +2333,23 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
 		do {
 		    /* Don't use transport being shutdown/destroyed */
 		    if (!tp_iter->tp->is_shutdown &&
 			!tp_iter->tp->is_destroying)
 		    {
+			if ((type & PJSIP_TRANSPORT_SECURE) && tdata) {
+			    /* For secure transport, make sure tdata's
+			     * destination host matches the transport's
+			     * remote host.
+			     */
+			    if (pj_stricmp(&tdata->dest_info.name,
+				  	   &tp_iter->tp->remote_name.host))
+			    {
+			    	tp_iter = tp_iter->next;
+			    	continue;
+			    }
+			}
+
 			if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
 			    sel->u.listener)
 			{
 			    /* Match listener if selector is set */
 			    if (tp_iter->tp->factory == sel->u.listener) {
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index a1bf878ea..cf916805d 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -1415,11 +1415,14 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt,
      * destination info must have been copied from the original INVITE so
      * proceed to sending the request directly.
      */
     if (tdata->dest_info.addr.count == 0) {
 	/* Copy the destination host name to TX data */
-	pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+	if (!tdata->dest_info.name.slen) {
+	    pj_strdup(tdata->pool, &tdata->dest_info.name,
+	    	      &dest_info.addr.host);
+	}
 
 	pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
 			     &stateless_send_resolver_callback);
     } else {
 	PJ_LOG(5,(THIS_FILE, "%s: skipping target resolution because "
@@ -1808,12 +1811,14 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt,
 	    pjsip_transport_dec_ref(send_state->cur_transport);
 	    return status;
 	}
     } else {
 	/* Copy the destination host name to TX data */
-	pj_strdup(tdata->pool, &tdata->dest_info.name, 
-		  &res_addr->dst_host.addr.host);
+	if (!tdata->dest_info.name.slen) {
+	    pj_strdup(tdata->pool, &tdata->dest_info.name, 
+		      &res_addr->dst_host.addr.host);
+	}
 
 	pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, 
 			    send_state, &send_response_resolver_cb);
 	return PJ_SUCCESS;
     }

Vulnerable code: 	pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
	pj_strdup(tdata->pool, &tdata->dest_info.name, 
		  &res_addr->dst_host.addr.host);
Fixed code:     pj_str_t		initial_dest;/**< Initial destination host.  	    */
    
    /* Save initial destination host from transport's info */
    pj_strdup(dlg->pool, &dlg->initial_dest,
    	      &rdata->tp_info.transport->remote_name.host);
    /* Copy the initial destination host to tdata. This information can be
     * used later by transport for transport selection.
     */
    if (dlg->initial_dest.slen)
    	pj_strdup(tdata->pool, &tdata->dest_info.name, &dlg->initial_dest);

    	/* Save initial destination host from transport's info. */
    	if (!dlg->initial_dest.slen) {
    	    pj_strdup(dlg->pool, &dlg->initial_dest,
    	      	      &rdata->tp_info.transport->remote_name.host);
    	}
			if ((type & PJSIP_TRANSPORT_SECURE) && tdata) {
			    /* For secure transport, make sure tdata's
			     * destination host matches the transport's
			     * remote host.
			     */
			    if (pj_stricmp(&tdata->dest_info.name,
				  	   &tp_iter->tp->remote_name.host))
			    {
			    	tp_iter = tp_iter->next;
			    	continue;
			    }
			}

	if (!tdata->dest_info.name.slen) {
	    pj_strdup(tdata->pool, &tdata->dest_info.name,
	    	      &dest_info.addr.host);
	}
	if (!tdata->dest_info.name.slen) {
	    pj_strdup(tdata->pool, &tdata->dest_info.name, 
		      &res_addr->dst_host.addr.host);
	}