CVE-2022-23608: Use After Free (CWE-416)
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 versions up to and including 2.11.1 when in a dialog set (or forking) scenario, a hash key shared by multiple UAC dialogs can potentially be prematurely freed when one of the dialogs is destroyed . The issue may cause a dialog set to be registered in the hash table multiple times (with different hash keys) leading to undefined behavior such as dialog list collision which eventually leading to endless loop. A patch is available in commit db3235953baa56d2fb0e276ca510fefca751643f which will be included in the next release. There are no known workarounds for this issue.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 9.8
Published: 2022-02-22T20:15Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjsip/src/pjsip/sip_ua_layer.c
Commit date: 2022-02-21 07:24:52 +0800
Commit message: Merge pull request from GHSA-ffff-m5fm-qm62

* Update pjsip_ua_unregister_dlg():
- update the hash key if the dialog being unregistered is used as hash key.
- add an assertion check to make sure that the dlg_set to be removed is valid (can be found in the hash table).

* Change hash key string comparison method.
Diff: diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
index 59c2524ba6..5d79882a19 100644
--- a/pjsip/src/pjsip/sip_ua_layer.c
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -63,10 +63,13 @@ struct dlg_set
     PJ_DECL_LIST_MEMBER(struct dlg_set);
 
     /* This is the buffer to store this entry in the hash table. */
     pj_hash_entry_buf ht_entry;
 
+    /* Entry key in the hash table */
+    pj_str_t ht_key;
+
     /* List of dialog in this dialog set. */
     struct dlg_set_head  dlg_list;
 };
 
 
@@ -325,36 +328,38 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
 	} else {
 	    /* This is the first dialog in the dialog set. 
 	     * Create the dialog set and add this dialog to it.
 	     */
 	    dlg_set = alloc_dlgset_node();
+	    dlg_set->ht_key = dlg->local.info->tag;
 	    pj_list_init(&dlg_set->dlg_list);
 	    pj_list_push_back(&dlg_set->dlg_list, dlg);
 
 	    dlg->dlg_set = dlg_set;
 
 	    /* Register the dialog set in the hash table. */
 	    pj_hash_set_np_lower(mod_ua.dlg_table, 
-			         dlg->local.info->tag.ptr,
-                                 (unsigned)dlg->local.info->tag.slen,
+			         dlg_set->ht_key.ptr,
+                                 (unsigned)dlg_set->ht_key.slen,
 			         dlg->local.tag_hval, dlg_set->ht_entry,
                                  dlg_set);
 	}
 
     } else {
 	/* For UAS, create the dialog set with a single dialog as member. */
 	struct dlg_set *dlg_set;
 
 	dlg_set = alloc_dlgset_node();
+	dlg_set->ht_key = dlg->local.info->tag;
 	pj_list_init(&dlg_set->dlg_list);
 	pj_list_push_back(&dlg_set->dlg_list, dlg);
 
 	dlg->dlg_set = dlg_set;
 
 	pj_hash_set_np_lower(mod_ua.dlg_table, 
-		             dlg->local.info->tag.ptr,
-                             (unsigned)dlg->local.info->tag.slen,
+		             dlg_set->ht_key.ptr,
+                             (unsigned)dlg_set->ht_key.slen,
 		             dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
     }
 
     /* Unlock user agent. */
     pj_mutex_unlock(mod_ua.mutex);
@@ -395,16 +400,47 @@ PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua,
     /* Remove this dialog from the list. */
     pj_list_erase(dlg);
 
     /* If dialog list is empty, remove the dialog set from the hash table. */
     if (pj_list_empty(&dlg_set->dlg_list)) {
-	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
-		          (unsigned)dlg->local.info->tag.slen, 
+
+	/* Verify that the dialog set is valid */
+	pj_assert(pj_hash_get_lower(mod_ua.dlg_table, dlg_set->ht_key.ptr,
+				    (unsigned)dlg_set->ht_key.slen,
+				    &dlg->local.tag_hval) == dlg_set);
+
+	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
+		          (unsigned)dlg_set->ht_key.slen,
 			  dlg->local.tag_hval, NULL);
 
 	/* Return dlg_set to free nodes. */
 	pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
+    } else {
+	/* If the just unregistered dialog is being used as hash key,
+	 * reset the dlg_set entry with a new key (i.e: from the first dialog
+	 * in dlg_set).
+	 */
+	if (dlg_set->ht_key.ptr  == dlg->local.info->tag.ptr &&
+	    dlg_set->ht_key.slen == dlg->local.info->tag.slen)
+	{
+	    pjsip_dialog* key_dlg = dlg_set->dlg_list.next;
+
+	    /* Verify that the old & new keys share the hash value */
+	    pj_assert(key_dlg->local.tag_hval == dlg->local.tag_hval);
+
+	    pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
+			      (unsigned)dlg_set->ht_key.slen,
+			      dlg->local.tag_hval, NULL);
+
+	    dlg_set->ht_key = key_dlg->local.info->tag;
+
+	    pj_hash_set_np_lower(mod_ua.dlg_table,
+				 dlg_set->ht_key.ptr,
+				 (unsigned)dlg_set->ht_key.slen,
+				 key_dlg->local.tag_hval, dlg_set->ht_entry,
+				 dlg_set);
+	}
     }
 
     /* Unlock user agent. */
     pj_mutex_unlock(mod_ua.mutex);
 

Vulnerable code: 			         dlg->local.info->tag.ptr,
                                 (unsigned)dlg->local.info->tag.slen,
		             dlg->local.info->tag.ptr,
                             (unsigned)dlg->local.info->tag.slen,
	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
		          (unsigned)dlg->local.info->tag.slen, 
Fixed code:     /* Entry key in the hash table */
    pj_str_t ht_key;

	    dlg_set->ht_key = dlg->local.info->tag;
			         dlg_set->ht_key.ptr,
                                 (unsigned)dlg_set->ht_key.slen,
	dlg_set->ht_key = dlg->local.info->tag;
		             dlg_set->ht_key.ptr,
                             (unsigned)dlg_set->ht_key.slen,

	/* Verify that the dialog set is valid */
	pj_assert(pj_hash_get_lower(mod_ua.dlg_table, dlg_set->ht_key.ptr,
				    (unsigned)dlg_set->ht_key.slen,
				    &dlg->local.tag_hval) == dlg_set);

	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
		          (unsigned)dlg_set->ht_key.slen,
    } else {
	/* If the just unregistered dialog is being used as hash key,
	 * reset the dlg_set entry with a new key (i.e: from the first dialog
	 * in dlg_set).
	 */
	if (dlg_set->ht_key.ptr  == dlg->local.info->tag.ptr &&
	    dlg_set->ht_key.slen == dlg->local.info->tag.slen)
	{
	    pjsip_dialog* key_dlg = dlg_set->dlg_list.next;

	    /* Verify that the old & new keys share the hash value */
	    pj_assert(key_dlg->local.tag_hval == dlg->local.tag_hval);

	    pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
			      (unsigned)dlg_set->ht_key.slen,
			      dlg->local.tag_hval, NULL);

	    dlg_set->ht_key = key_dlg->local.info->tag;

	    pj_hash_set_np_lower(mod_ua.dlg_table,
				 dlg_set->ht_key.ptr,
				 (unsigned)dlg_set->ht_key.slen,
				 key_dlg->local.tag_hval, dlg_set->ht_entry,
				 dlg_set);
	}