CVE-2023-38703: Use After Free (CWE-416)
value: PJSIP is a free and open source multimedia communication library written in C with high level API in C, C++, Java, C#, and Python languages. SRTP is a higher level media transport which is stacked upon a lower level media transport such as UDP and ICE. Currently a higher level transport is not synchronized with its lower level transport that may introduce use-after-free issue. This vulnerability affects applications that have SRTP capability (`PJMEDIA_HAS_SRTP` is set) and use underlying media transport other than UDP. This vulnerability’s impact may range from unexpected application termination to control flow hijack/memory corruption. The patch is available as a commit in the master branch.

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: 2023-10-06T14:15Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjmedia/include/pjmedia/transport.h, pjmedia/src/pjmedia/transport_adapter_sample.c, pjmedia/src/pjmedia/transport_ice.c, pjmedia/src/pjmedia/transport_loop.c, pjmedia/src/pjmedia/transport_srtp.c, pjmedia/src/pjmedia/transport_srtp_dtls.c, pjmedia/src/pjmedia/transport_udp.c
Commit date: 2023-10-03 10:59:03 +0800
Commit message: Merge pull request from GHSA-f76w-fh7c-pc66

* Add group lock to media transport

* Also add group lock to SRTP-DTLS

* Put lock protection to avoid race condition between destroy() & dtls_on_recv()
Diff: diff --git a/pjmedia/include/pjmedia/transport.h b/pjmedia/include/pjmedia/transport.h
index ff7544366b..923593dc0f 100644
--- a/pjmedia/include/pjmedia/transport.h
+++ b/pjmedia/include/pjmedia/transport.h
@@ -511,10 +511,13 @@ struct pjmedia_transport
     /** Transport's "virtual" function table. */
     pjmedia_transport_op    *op;
 
     /** Application/user data */
     void                    *user_data;
+
+    /** Group lock, for synchronization between destroy() & callbacks. */
+    pj_grp_lock_t           *grp_lock;
 };
 
 /**
  * This structure describes storage buffer of transport specific info.
  * The actual transport specific info contents will be defined by transport
diff --git a/pjmedia/src/pjmedia/transport_adapter_sample.c b/pjmedia/src/pjmedia/transport_adapter_sample.c
index e150292bed..96a2fa48e5 100644
--- a/pjmedia/src/pjmedia/transport_adapter_sample.c
+++ b/pjmedia/src/pjmedia/transport_adapter_sample.c
@@ -104,10 +104,13 @@ struct tp_adapter
     /* Add your own member here.. */
     pjmedia_transport   *slave_tp;
 };
 
 
+static void adapter_on_destroy(void *arg);
+
+
 /*
  * Create the adapter.
  */
 PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
                                                const char *name,
@@ -133,10 +136,19 @@ PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
 
     /* Save the transport as the slave transport */
     adapter->slave_tp = transport;
     adapter->del_base = del_base;
 
+    /* Setup group lock handler for destroy and callback synchronization */
+    if (transport && transport->grp_lock) {
+        pj_grp_lock_t *grp_lock = transport->grp_lock;
+
+        adapter->base.grp_lock = grp_lock;
+        pj_grp_lock_add_ref(grp_lock);
+        pj_grp_lock_add_handler(grp_lock, pool, adapter, &adapter_on_destroy);
+    }
+
     /* Done */
     *p_tp = &adapter->base;
     return PJ_SUCCESS;
 }
 
@@ -419,10 +431,18 @@ static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
 {
     struct tp_adapter *adapter = (struct tp_adapter*)tp;
     return pjmedia_transport_simulate_lost(adapter->slave_tp, dir, pct_lost);
 }
 
+
+static void adapter_on_destroy(void *arg)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)arg;
+
+    pj_pool_release(adapter->pool);
+}
+
 /*
  * destroy() is called when the transport is no longer needed.
  */
 static pj_status_t transport_destroy  (pjmedia_transport *tp)
 {
@@ -431,12 +451,15 @@ static pj_status_t transport_destroy  (pjmedia_transport *tp)
     /* Close the slave transport */
     if (adapter->del_base) {
         pjmedia_transport_close(adapter->slave_tp);
     }
 
-    /* Self destruct.. */
-    pj_pool_release(adapter->pool);
+    if (adapter->base.grp_lock) {
+        pj_grp_lock_dec_ref(adapter->base.grp_lock);
+    } else {
+        adapter_on_destroy(tp);
+    }
 
     return PJ_SUCCESS;
 }
 
 
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
index 8edf1d7106..c8d1ae7516 100644
--- a/pjmedia/src/pjmedia/transport_ice.c
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -336,10 +336,11 @@ PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt,
     /* Sync to ICE */
     {
         pj_grp_lock_t *grp_lock = pj_ice_strans_get_grp_lock(tp_ice->ice_st);
         pj_grp_lock_add_ref(grp_lock);
         pj_grp_lock_add_handler(grp_lock, pool, tp_ice, &tp_ice_on_destroy);
+        tp_ice->base.grp_lock = grp_lock;
     }
 
     /* Done */
     return PJ_SUCCESS;
 }
@@ -2734,20 +2735,24 @@ static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
 }
 
 static void tp_ice_on_destroy(void *arg)
 {
     struct transport_ice *tp_ice = (struct transport_ice*)arg;
+
+    PJ_LOG(4, (tp_ice->base.name, "ICE transport destroyed"));
     pj_pool_safe_release(&tp_ice->pool);
 }
 
 /*
  * Destroy ICE media transport.
  */
 static pj_status_t transport_destroy(pjmedia_transport *tp)
 {
     struct transport_ice *tp_ice = (struct transport_ice*)tp;
 
+    PJ_LOG(4, (tp_ice->base.name, "Destroying ICE transport"));
+
     /* Reset callback and user data */
     pj_bzero(&tp_ice->cb, sizeof(tp_ice->cb));
     tp_ice->base.user_data = NULL;
     tp_ice->rtp_cb = NULL;
     tp_ice->rtp_cb2 = NULL;
diff --git a/pjmedia/src/pjmedia/transport_loop.c b/pjmedia/src/pjmedia/transport_loop.c
index a1f0ebbd4a..37e6f18cd5 100644
--- a/pjmedia/src/pjmedia/transport_loop.c
+++ b/pjmedia/src/pjmedia/transport_loop.c
@@ -128,10 +128,11 @@ static pjmedia_transport_op transport_udp_op =
     &transport_simulate_lost,
     &transport_destroy,
     &transport_attach2
 };
 
+static void tp_loop_on_destroy(void *arg);
 
 /**
  * Initialize loopback media transport setting with its default values.
  */
 PJ_DEF(void) pjmedia_loop_tp_setting_default(pjmedia_loop_tp_setting *opt)
@@ -162,10 +163,12 @@ pjmedia_transport_loop_create2(pjmedia_endpt *endpt,
                                const pjmedia_loop_tp_setting *opt,
                                pjmedia_transport **p_tp)
 {
     struct transport_loop *tp;
     pj_pool_t *pool;
+    pj_grp_lock_t *grp_lock;
+    pj_status_t status;
 
     /* Sanity check */
     PJ_ASSERT_RETURN(endpt && p_tp, PJ_EINVAL);
 
     /* Create transport structure */
@@ -177,10 +180,18 @@ pjmedia_transport_loop_create2(pjmedia_endpt *endpt,
     tp->pool = pool;
     pj_memcpy(tp->base.name, tp->pool->obj_name, PJ_MAX_OBJ_NAME);
     tp->base.op = &transport_udp_op;
     tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
 
+    /* Create group lock */
+    status = pj_grp_lock_create(pool, NULL, &grp_lock);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    pj_grp_lock_add_ref(grp_lock);
+    pj_grp_lock_add_handler(grp_lock, pool, tp, &tp_loop_on_destroy);
+
     if (opt) {
         tp->setting = *opt;
     } else {
         pjmedia_loop_tp_setting_default(&tp->setting);
     }
@@ -220,21 +231,29 @@ PJ_DEF(pj_status_t) pjmedia_transport_loop_disable_rx( pjmedia_transport *tp,
     }
     pj_assert(!"Invalid stream user");
     return PJ_ENOTFOUND;
 }
 
+
+static void tp_loop_on_destroy(void *arg)
+{
+    struct transport_loop *loop = (struct transport_loop*) arg;
+
+    PJ_LOG(4, (loop->base.name, "Loop transport destroyed"));
+    pj_pool_release(loop->pool);
+}
+
+
 /**
  * Close loopback transport.
  */
 static pj_status_t transport_destroy(pjmedia_transport *tp)
 {
-    struct transport_loop *loop = (struct transport_loop*) tp;
-
     /* Sanity check */
     PJ_ASSERT_RETURN(tp, PJ_EINVAL);
 
-    pj_pool_release(loop->pool);
+    pj_grp_lock_dec_ref(tp->grp_lock);
 
     return PJ_SUCCESS;
 }
 
 
@@ -376,10 +395,12 @@ static pj_status_t transport_send_rtp( pjmedia_transport *tp,
                       "simulation"));
             return PJ_SUCCESS;
         }
     }
 
+    pj_grp_lock_add_ref(tp->grp_lock);
+
     /* Distribute to users */
     for (i=0; i<loop->user_cnt; ++i) {
         if (loop->users[i].rx_disabled) continue;
         if (loop->users[i].rtp_cb2) {
             pjmedia_tp_cb_param param;
@@ -393,10 +414,12 @@ static pj_status_t transport_send_rtp( pjmedia_transport *tp,
             (*loop->users[i].rtp_cb)(loop->users[i].user_data, (void*)pkt, 
                                      size);
         }
     }
 
+    pj_grp_lock_dec_ref(tp->grp_lock);
+
     return PJ_SUCCESS;
 }
 
 /* Called by application to send RTCP packet */
 static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
@@ -418,17 +441,21 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
     unsigned i;
 
     PJ_UNUSED_ARG(addr_len);
     PJ_UNUSED_ARG(addr);
 
+    pj_grp_lock_add_ref(tp->grp_lock);
+
     /* Distribute to users */
     for (i=0; i<loop->user_cnt; ++i) {
         if (!loop->users[i].rx_disabled && loop->users[i].rtcp_cb)
             (*loop->users[i].rtcp_cb)(loop->users[i].user_data, (void*)pkt,
                                       size);
     }
 
+    pj_grp_lock_dec_ref(tp->grp_lock);
+
     return PJ_SUCCESS;
 }
 
 
 static pj_status_t transport_media_create(pjmedia_transport *tp,
diff --git a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c
index f95941581d..fc1478a175 100644
--- a/pjmedia/src/pjmedia/transport_srtp.c
+++ b/pjmedia/src/pjmedia/transport_srtp.c
@@ -432,10 +432,12 @@ static pj_status_t create_srtp_ctx(transport_srtp *srtp,
                                    const pjmedia_srtp_crypto *tx,
                                    const pjmedia_srtp_crypto *rx);
 /* Destroy SRTP context */
 static void destroy_srtp_ctx(transport_srtp *p_srtp, srtp_context *ctx);
 
+/* SRTP destroy handler */
+static void srtp_on_destroy(void *arg);
 
 /* This function may also be used by other module, e.g: pjmedia/errno.c,
  * it should have C compatible declaration.
  */
 PJ_BEGIN_DECL
@@ -803,10 +805,17 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
     srtp->base.user_data = srtp->setting.user_data;
 
     /* Set underlying transport */
     srtp->member_tp = tp;
 
+    /* Setup group lock handler for destroy and callback synchronization */
+    if (tp && tp->grp_lock) {
+        srtp->base.grp_lock = tp->grp_lock;
+        pj_grp_lock_add_ref(tp->grp_lock);
+        pj_grp_lock_add_handler(tp->grp_lock, pool, srtp, &srtp_on_destroy);
+    }
+
     /* Initialize peer's SRTP usage mode. */
     srtp->peer_use = srtp->setting.use;
 
     /* If keying count set to zero, setup default keying count & priorities */
     if (srtp->setting.keying_count == 0) {
@@ -837,10 +846,12 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
     }
 
     /* Done */
     *p_tp = &srtp->base;
 
+    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport created"));
+
     return PJ_SUCCESS;
 }
 
 
 /*
@@ -1457,18 +1468,33 @@ static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
     PJ_ASSERT_RETURN(tp, PJ_EINVAL);
 
     return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost);
 }
 
+
+/* SRTP real destroy */
+static void srtp_on_destroy(void *arg)
+{
+    transport_srtp *srtp = (transport_srtp*)arg;
+
+    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport destroyed"));
+
+    pj_lock_destroy(srtp->mutex);
+    pj_pool_safe_release(&srtp->pool);
+}
+
+
 static pj_status_t transport_destroy  (pjmedia_transport *tp)
 {
     transport_srtp *srtp = (transport_srtp *) tp;
     pj_status_t status;
     unsigned i;
 
     PJ_ASSERT_RETURN(tp, PJ_EINVAL);
 
+    PJ_LOG(4, (srtp->pool->obj_name, "Destroying SRTP transport"));
+
     /* Close all keying. Note that any keying should not be destroyed before
      * SRTP transport is destroyed as re-INVITE may initiate new keying method
      * without destroying SRTP transport.
      */
     for (i=0; i < srtp->all_keying_cnt; i++)
@@ -1479,16 +1505,29 @@ static pj_status_t transport_destroy  (pjmedia_transport *tp)
         pjmedia_transport_close(srtp->member_tp);
     }
 
     status = pjmedia_transport_srtp_stop(tp);
 
-    /* In case mutex is being acquired by other thread */
-    pj_lock_acquire(srtp->mutex);
-    pj_lock_release(srtp->mutex);
+    if (srtp->base.grp_lock) {
+        pj_grp_lock_dec_ref(srtp->base.grp_lock);
+    } else {
+        /* Only get here when the underlying transport does not have
+         * a group lock, race condition with callbacks may occur.
+         * Currently UDP, ICE, and loop have a group lock already.
+         */
+        PJ_LOG(4,(srtp->pool->obj_name,
+                  "Warning: underlying transport does not have group lock"));
 
-    pj_lock_destroy(srtp->mutex);
-    pj_pool_release(srtp->pool);
+        /* In case mutex is being acquired by other thread.
+         * An effort to synchronize destroy() & callbacks when the underlying
+         * transport does not provide a group lock.
+         */
+        pj_lock_acquire(srtp->mutex);
+        pj_lock_release(srtp->mutex);
+
+        srtp_on_destroy(srtp);
+    }
 
     return status;
 }
 
 /*
diff --git a/pjmedia/src/pjmedia/transport_srtp_dtls.c b/pjmedia/src/pjmedia/transport_srtp_dtls.c
index 9a374d71fb..183d16e008 100644
--- a/pjmedia/src/pjmedia/transport_srtp_dtls.c
+++ b/pjmedia/src/pjmedia/transport_srtp_dtls.c
@@ -78,10 +78,12 @@ static pj_status_t dtls_on_recv_rtcp  (pjmedia_transport *tp,
 static void on_ice_complete2(pjmedia_transport *tp,
                              pj_ice_strans_op op,
                              pj_status_t status,
                              void *user_data);
 
+static void dtls_on_destroy(void *arg);
+
 
 static pjmedia_transport_op dtls_op =
 {
     NULL,
     NULL,
@@ -132,10 +134,11 @@ typedef struct dtls_srtp
     pj_sockaddr          rem_addr;          /* Remote address (from SDP/RTP)*/
     pj_sockaddr          rem_rtcp;          /* Remote RTCP address (SDP)    */
     pj_bool_t            pending_start;     /* media_start() invoked but DTLS
                                                nego not done yet, so start
                                                the SRTP once the nego done  */
+    pj_bool_t            is_destroying;     /* DTLS being destroyed?        */
     pj_bool_t            got_keys;          /* DTLS nego done & keys ready  */
     pjmedia_srtp_crypto  tx_crypto[NUM_CHANNEL];
     pjmedia_srtp_crypto  rx_crypto[NUM_CHANNEL];
 
     char                 buf[NUM_CHANNEL][PJMEDIA_MAX_MTU];
@@ -267,11 +270,11 @@ static void dtls_deinit()
 static pj_status_t dtls_create(transport_srtp *srtp,
                                pjmedia_transport **p_keying)
 {
     dtls_srtp *ds;
     pj_pool_t *pool;
-	pj_status_t status;
+    pj_status_t status;
 
     pool = pj_pool_create(srtp->pool->factory, "dtls%p",
                           2000, 256, NULL);
     ds = PJ_POOL_ZALLOC_T(pool, dtls_srtp);
     ds->pool = pool;
@@ -280,21 +283,48 @@ static pj_status_t dtls_create(transport_srtp *srtp,
     ds->base.type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_DTLS_SRTP;
     ds->base.op = &dtls_op;
     ds->base.user_data = srtp;
     ds->srtp = srtp;
 
-    status = pj_lock_create_simple_mutex(ds->pool, "dtls_ssl_lock%p",
-                                         &ds->ossl_lock);
-    if (status != PJ_SUCCESS)
-        return status;
+    /* Setup group lock handler for destroy and callback synchronization */
+    if (srtp->base.grp_lock) {
+        pj_grp_lock_t *grp_lock = srtp->base.grp_lock;
+
+        ds->base.grp_lock = grp_lock;
+        pj_grp_lock_add_ref(grp_lock);
+        pj_grp_lock_add_handler(grp_lock, pool, ds, &dtls_on_destroy);
+    } else {
+        status = pj_lock_create_simple_mutex(ds->pool, "dtls_ssl_lock%p",
+                                             &ds->ossl_lock);
+        if (status != PJ_SUCCESS)
+            return status;
+    }
 
     *p_keying = &ds->base;
     PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying DTLS-SRTP created"));
     return PJ_SUCCESS;
 }
 
 
+/* Lock/unlock for DTLS states access protection */
+
+static void DTLS_LOCK(dtls_srtp *ds) {
+    if (ds->base.grp_lock)
+        pj_grp_lock_acquire(ds->base.grp_lock);
+    else
+        pj_lock_acquire(ds->ossl_lock);
+}
+
+
+static void DTLS_UNLOCK(dtls_srtp *ds) {
+    if (ds->base.grp_lock)
+        pj_grp_lock_release(ds->base.grp_lock);
+    else
+        pj_lock_release(ds->ossl_lock);
+}
+
+
 /**
  * Mapping from OpenSSL error codes to pjlib error space.
  */
 #define PJ_SSL_ERRNO_START              (PJ_ERRNO_START_USER + \
                                          PJ_ERRNO_SPACE_SIZE*6)
@@ -543,11 +573,11 @@ static pj_status_t ssl_create(dtls_srtp *ds, unsigned idx)
 
 
 /* Destroy SSL context and instance */
 static void ssl_destroy(dtls_srtp *ds, unsigned idx)
 {
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     /* Destroy SSL instance */
     if (ds->ossl_ssl[idx]) {
         /**
          * Avoid calling SSL_shutdown() if handshake wasn't completed.
@@ -568,22 +598,22 @@ static void ssl_destroy(dtls_srtp *ds, unsigned idx)
     if (ds->ossl_ctx[idx]) {
         SSL_CTX_free(ds->ossl_ctx[idx]);
         ds->ossl_ctx[idx] = NULL;
     }
 
-    pj_lock_release(ds->ossl_lock);
+    DTLS_UNLOCK(ds);
 }
 
 static pj_status_t ssl_get_srtp_material(dtls_srtp *ds, unsigned idx)
 {
     unsigned char material[SRTP_MAX_KEY_LEN * 2];
     SRTP_PROTECTION_PROFILE *profile;
     int rc, i, crypto_idx = -1;
     pjmedia_srtp_crypto *tx, *rx;
     pj_status_t status = PJ_SUCCESS;
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     if (!ds->ossl_ssl[idx]) {
         status = PJ_EGONE;
         goto on_return;
     }
@@ -650,11 +680,11 @@ static pj_status_t ssl_get_srtp_material(dtls_srtp *ds, unsigned idx)
         pj_memcpy(k2+key_len, p, salt_len);
         ds->got_keys = PJ_TRUE;
     }
 
 on_return:
-    pj_lock_release(ds->ossl_lock);
+    DTLS_UNLOCK(ds);
     return status;
 }
 
 /* Match remote fingerprint: SDP vs actual */
 static pj_status_t ssl_match_fingerprint(dtls_srtp *ds, unsigned idx)
@@ -674,20 +704,20 @@ static pj_status_t ssl_match_fingerprint(dtls_srtp *ds, unsigned idx)
         PJ_LOG(4,(ds->base.name, "Hash algo specified in remote SDP for "
                   "its DTLS certificate fingerprint is not supported"));
         return PJ_ENOTSUP;
     }
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
     if (!ds->ossl_ssl[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_EGONE;
     }
 
     /* Get remote cert & calculate the hash */
     rem_cert = SSL_get_peer_certificate(ds->ossl_ssl[idx]);
 
-    pj_lock_release(ds->ossl_lock);
+    DTLS_UNLOCK(ds);
 
     if (!rem_cert)
         return PJMEDIA_SRTP_DTLS_EPEERNOCERT;
 
     status = ssl_get_fingerprint(rem_cert, is_sha256, buf, &buf_len);
@@ -746,56 +776,56 @@ static pj_status_t udp_member_transport_media_start(dtls_srtp *ds)
 static pj_status_t ssl_flush_wbio(dtls_srtp *ds, unsigned idx)
 {
     pj_size_t len;
     pj_status_t status = PJ_SUCCESS;
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     if (!ds->ossl_wbio[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_EGONE;
     }
 
     /* Check whether there is data to send */
     if (BIO_ctrl_pending(ds->ossl_wbio[idx]) > 0) {
         /* Yes, get and send it */
         len = BIO_read(ds->ossl_wbio[idx], ds->buf[idx], sizeof(ds->buf));
         if (len > 0) {
-            pj_lock_release(ds->ossl_lock);
+            DTLS_UNLOCK(ds);
 
             status = send_raw(ds, idx, ds->buf[idx], len);
             if (status != PJ_SUCCESS) {
 #if DTLS_DEBUG
                 pj_perror(2, ds->base.name, status, "Send error");
 #endif
                 /* This error should be recoverable, remote will retransmit
                  * its packet when not receiving from us.
                  */
             }
-            pj_lock_acquire(ds->ossl_lock);
+            DTLS_LOCK(ds);
         }
     }
 
     if (!ds->ossl_ssl[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_EGONE;
     }
 
     /* Just return if handshake completion procedure (key parsing, fingerprint
      * verification, etc) has been done or handshake is still in progress.
      */
     if (ds->nego_completed[idx] || !SSL_is_init_finished(ds->ossl_ssl[idx])) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_SUCCESS;
     }
 
     /* Yes, SSL handshake is done! */
     ds->nego_completed[idx] = PJ_TRUE;
     PJ_LOG(2,(ds->base.name, "DTLS-SRTP negotiation for %s completed!",
                              CHANNEL_TO_STRING(idx)));
 
-    pj_lock_release(ds->ossl_lock);
+    DTLS_UNLOCK(ds);
 
     /* Stop the retransmission clock. Note that the clock may not be stopped
      * if this function is called from clock thread context. We'll try again
      * later in socket context.
      */
@@ -865,44 +895,44 @@ static void clock_cb(const pj_timestamp *ts, void *user_data)
     dtls_srtp *ds = ds_ch->dtls_srtp;
     unsigned idx = ds_ch->channel;
 
     PJ_UNUSED_ARG(ts);
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     if (!ds->ossl_ssl[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return;
     }
 
     if (DTLSv1_handle_timeout(ds->ossl_ssl[idx]) > 0) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         ssl_flush_wbio(ds, idx);
     } else {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
     }
 }
 
 
 /* Asynchronous handshake */
 static pj_status_t ssl_handshake_channel(dtls_srtp *ds, unsigned idx)
 {
     pj_status_t status;
     int err;
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     /* Init DTLS (if not yet) */
     status = ssl_create(ds, idx);
     if (status != PJ_SUCCESS) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return status;
     }
 
     /* Check if handshake has been initiated or even completed */
     if (ds->nego_started[idx] || SSL_is_init_finished(ds->ossl_ssl[idx])) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_SUCCESS;
     }
 
     /* Perform SSL handshake */
     if (ds->setup == DTLS_SETUP_ACTIVE) {
@@ -912,11 +942,11 @@ static pj_status_t ssl_handshake_channel(dtls_srtp *ds, unsigned idx)
     }
     err = SSL_do_handshake(ds->ossl_ssl[idx]);
     if (err < 0) {
         err = SSL_get_error(ds->ossl_ssl[idx], err);
 
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
 
         if (err == SSL_ERROR_WANT_READ) {
             status = ssl_flush_wbio(ds, idx);
             if (status != PJ_SUCCESS)
                 goto on_return;
@@ -925,11 +955,11 @@ static pj_status_t ssl_handshake_channel(dtls_srtp *ds, unsigned idx)
             status = STATUS_FROM_SSL_ERR(ds, err);
             pj_perror(2, ds->base.name, status, "SSL_do_handshake() error");
             goto on_return;
         }
     } else {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
     }
 
     /* Create and start clock @4Hz for retransmission */
     if (!ds->clock[idx]) {
         ds->channel[idx].dtls_srtp = ds;
@@ -1133,14 +1163,14 @@ static pj_status_t ssl_on_recv_packet(dtls_srtp *ds, unsigned idx,
                                       const void *data, pj_size_t len)
 {
     char tmp[128];
     pj_size_t nwritten;
 
-    pj_lock_acquire(ds->ossl_lock);
+    DTLS_LOCK(ds);
 
     if (!ds->ossl_rbio[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_EGONE;
     }
 
     nwritten = BIO_write(ds->ossl_rbio[idx], data, (int)len);
     if (nwritten < len) {
@@ -1148,16 +1178,16 @@ static pj_status_t ssl_on_recv_packet(dtls_srtp *ds, unsigned idx,
         pj_status_t status;
         status = GET_SSL_STATUS(ds);
 #if DTLS_DEBUG
         pj_perror(2, ds->base.name, status, "BIO_write() error");
 #endif
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return status;
     }
 
     if (!ds->ossl_ssl[idx]) {
-        pj_lock_release(ds->ossl_lock);
+        DTLS_UNLOCK(ds);
         return PJ_EGONE;
     }
 
     /* Consume (and ignore) the packet */
     while (1) {
@@ -1170,11 +1200,11 @@ static pj_status_t ssl_on_recv_packet(dtls_srtp *ds, unsigned idx,
 #endif
             break;
         }
     }
 
-    pj_lock_release(ds->ossl_lock);
+    DTLS_UNLOCK(ds);
 
     /* Flush anything pending in the write BIO */
     return ssl_flush_wbio(ds, idx);
 }
 
@@ -1209,18 +1239,22 @@ static void on_ice_complete2(pjmedia_transport *tp,
 static pj_status_t dtls_on_recv(pjmedia_transport *tp, unsigned idx,     
                                 const void *pkt, pj_size_t size)
 {
     dtls_srtp *ds = (dtls_srtp*)tp;
 
+    DTLS_LOCK(ds);
+
     /* Destroy the retransmission clock if handshake has been completed. */
     if (ds->clock[idx] && ds->nego_completed[idx]) {
         pjmedia_clock_destroy(ds->clock[idx]);
         ds->clock[idx] = NULL;
     }
 
-    if (size < 1 || !IS_DTLS_PKT(pkt, size))
+    if (size < 1 || !IS_DTLS_PKT(pkt, size) || ds->is_destroying) {
+        DTLS_UNLOCK(ds);
         return PJ_EIGNORED;
+    }
 
 #if DTLS_DEBUG
     PJ_LOG(2,(ds->base.name, "DTLS-SRTP %s receiving %lu bytes",
                              CHANNEL_TO_STRING(idx), size));
 #endif
@@ -1256,12 +1290,14 @@ static pj_status_t dtls_on_recv(pjmedia_transport *tp, unsigned idx,
                 pj_sockaddr_set_port(&ap.rem_rtcp,
                                      pj_sockaddr_get_port(&ds->rem_addr)+1);
             }
 
             status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
-            if (status != PJ_SUCCESS)
+            if (status != PJ_SUCCESS) {
+                DTLS_UNLOCK(ds);
                 return status;
+            }
 
 #if DTLS_DEBUG
             {
                 char addr[PJ_INET6_ADDRSTRLEN];
                 PJ_LOG(2,(ds->base.name, "Re-attached transport to update "
@@ -1281,16 +1317,21 @@ static pj_status_t dtls_on_recv(pjmedia_transport *tp, unsigned idx,
         (ds->setup == DTLS_SETUP_ACTPASS || ds->setup == DTLS_SETUP_PASSIVE))
     {
         pj_status_t status;
         ds->setup = DTLS_SETUP_PASSIVE;
         status = ssl_handshake_channel(ds, idx);
-        if (status != PJ_SUCCESS)
+        if (status != PJ_SUCCESS) {
+            DTLS_UNLOCK(ds);
             return status;
+        }
     }
 
     /* Send it to OpenSSL */
     ssl_on_recv_packet(ds, idx, pkt, size);
+
+    DTLS_UNLOCK(ds);
+
     return PJ_SUCCESS;
 }
 
 /*
  * This callback is called by SRTP transport when incoming rtp is received.
@@ -1819,27 +1860,41 @@ static void dtls_destroy_channel(dtls_srtp *ds, unsigned idx)
         ds->clock[idx] = NULL;
     }
     ssl_destroy(ds, idx);
 }
 
+static void dtls_on_destroy(void *arg) {
+    dtls_srtp *ds = (dtls_srtp *)arg;
+
+    if (ds->ossl_lock)
+        pj_lock_destroy(ds->ossl_lock);
+
+    pj_pool_safe_release(&ds->pool);
+}
+
 static pj_status_t dtls_destroy(pjmedia_transport *tp)
 {
     dtls_srtp *ds = (dtls_srtp *)tp;
 
 #if DTLS_DEBUG
     PJ_LOG(2,(ds->base.name, "dtls_destroy()"));
 #endif
 
+    ds->is_destroying = PJ_TRUE;
+
+    DTLS_LOCK(ds);
+
     dtls_destroy_channel(ds, RTP_CHANNEL);
     dtls_destroy_channel(ds, RTCP_CHANNEL);
 
-    if (ds->ossl_lock) {
-        pj_lock_destroy(ds->ossl_lock);
-        ds->ossl_lock = NULL;
-    }
+    DTLS_UNLOCK(ds);
 
-    pj_pool_safe_release(&ds->pool);
+    if (ds->base.grp_lock) {
+        pj_grp_lock_dec_ref(ds->base.grp_lock);
+    } else {
+        dtls_on_destroy(tp);
+    }
 
     return PJ_SUCCESS;
 }
 
 
diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c
index 9727ac0455..cf3471ced9 100644
--- a/pjmedia/src/pjmedia/transport_udp.c
+++ b/pjmedia/src/pjmedia/transport_udp.c
@@ -296,10 +296,11 @@ PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
 {
     struct transport_udp *tp;
     pj_pool_t *pool;
     pj_ioqueue_t *ioqueue;
     pj_ioqueue_callback rtp_cb, rtcp_cb;
+    pj_grp_lock_t *grp_lock;
     pj_status_t status;
 
 
     /* Sanity check */
     PJ_ASSERT_RETURN(endpt && si && p_tp, PJ_EINVAL);
@@ -346,22 +347,33 @@ PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
         pj_memcpy(pj_sockaddr_get_addr(&tp->rtcp_addr_name),
                   pj_sockaddr_get_addr(&tp->rtp_addr_name),
                   pj_sockaddr_get_addr_len(&tp->rtp_addr_name));
     }
 
+    /* Create group lock */
+    status = pj_grp_lock_create(pool, NULL, &grp_lock);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    pj_grp_lock_add_ref(grp_lock);
+    tp->base.grp_lock = grp_lock;
+
     /* Setup RTP socket with the ioqueue */
     pj_bzero(&rtp_cb, sizeof(rtp_cb));
     rtp_cb.on_read_complete = &on_rx_rtp;
     rtp_cb.on_write_complete = &on_rtp_data_sent;
 
-    status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtp_sock, tp,
-                                      &rtp_cb, &tp->rtp_key);
+    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtp_sock, grp_lock,
+                                       tp, &rtp_cb, &tp->rtp_key);
     if (status != PJ_SUCCESS)
         goto on_error;
     
     /* Disallow concurrency so that detach() and destroy() are
      * synchronized with the callback.
+     *
+     * Note that we still need this even after group lock is added to
+     * maintain the above behavior.
      */
     status = pj_ioqueue_set_concurrency(tp->rtp_key, PJ_FALSE);
     if (status != PJ_SUCCESS)
         goto on_error;
 
@@ -386,12 +398,12 @@ PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
 
     /* Setup RTCP socket with ioqueue */
     pj_bzero(&rtcp_cb, sizeof(rtcp_cb));
     rtcp_cb.on_read_complete = &on_rx_rtcp;
 
-    status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtcp_sock, tp,
-                                      &rtcp_cb, &tp->rtcp_key);
+    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtcp_sock, grp_lock,
+                                       tp, &rtcp_cb, &tp->rtcp_key);
     if (status != PJ_SUCCESS)
         goto on_error;
 
     status = pj_ioqueue_set_concurrency(tp->rtcp_key, PJ_FALSE);
     if (status != PJ_SUCCESS)
@@ -434,16 +446,17 @@ static pj_status_t transport_destroy(pjmedia_transport *tp)
     /* Sanity check */
     PJ_ASSERT_RETURN(tp, PJ_EINVAL);
 
     /* Must not close while application is using this */
     //PJ_ASSERT_RETURN(!udp->attached, PJ_EINVALIDOP);
-    
+
+    /* The following calls to pj_ioqueue_unregister() will block the execution
+     * if callback is still being called because allow_concurrent is false.
+     * So it is safe to release the pool immediately after.
+     */
 
     if (udp->rtp_key) {
-        /* This will block the execution if callback is still
-         * being called.
-         */
         pj_ioqueue_unregister(udp->rtp_key);
         udp->rtp_key = NULL;
         udp->rtp_sock = PJ_INVALID_SOCKET;
     } else if (udp->rtp_sock != PJ_INVALID_SOCKET) {
         pj_sock_close(udp->rtp_sock);
@@ -457,10 +470,12 @@ static pj_status_t transport_destroy(pjmedia_transport *tp)
     } else if (udp->rtcp_sock != PJ_INVALID_SOCKET) {
         pj_sock_close(udp->rtcp_sock);
         udp->rtcp_sock = PJ_INVALID_SOCKET;
     }
 
+    pj_grp_lock_dec_ref(tp->grp_lock);
+
     PJ_LOG(4,(udp->base.name, "UDP media transport destroyed"));
     pj_pool_release(udp->pool);
 
     return PJ_SUCCESS;
 }

Vulnerable code:     /* Self destruct.. */
    pj_pool_release(adapter->pool);
    struct transport_loop *loop = (struct transport_loop*) tp;

    pj_pool_release(loop->pool);
    /* In case mutex is being acquired by other thread */
    pj_lock_acquire(srtp->mutex);
    pj_lock_release(srtp->mutex);
    pj_lock_destroy(srtp->mutex);
    pj_pool_release(srtp->pool);
	pj_status_t status;
    status = pj_lock_create_simple_mutex(ds->pool, "dtls_ssl_lock%p",
                                         &ds->ossl_lock);
    if (status != PJ_SUCCESS)
        return status;
    pj_lock_acquire(ds->ossl_lock);
    pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
    pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
    pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
            pj_lock_release(ds->ossl_lock);
            pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
    pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
    pj_lock_acquire(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
        pj_lock_release(ds->ossl_lock);
    pj_lock_release(ds->ossl_lock);
    if (size < 1 || !IS_DTLS_PKT(pkt, size))
            if (status != PJ_SUCCESS)
        if (status != PJ_SUCCESS)
    if (ds->ossl_lock) {
        pj_lock_destroy(ds->ossl_lock);
        ds->ossl_lock = NULL;
    }
    pj_pool_safe_release(&ds->pool);
    status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtp_sock, tp,
                                      &rtp_cb, &tp->rtp_key);
    status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtcp_sock, tp,
                                      &rtcp_cb, &tp->rtcp_key);
    
        /* This will block the execution if callback is still
         * being called.
         */
Fixed code: 
    /** Group lock, for synchronization between destroy() & callbacks. */
    pj_grp_lock_t           *grp_lock;
static void adapter_on_destroy(void *arg);


    /* Setup group lock handler for destroy and callback synchronization */
    if (transport && transport->grp_lock) {
        pj_grp_lock_t *grp_lock = transport->grp_lock;

        adapter->base.grp_lock = grp_lock;
        pj_grp_lock_add_ref(grp_lock);
        pj_grp_lock_add_handler(grp_lock, pool, adapter, &adapter_on_destroy);
    }


static void adapter_on_destroy(void *arg)
{
    struct tp_adapter *adapter = (struct tp_adapter*)arg;

    pj_pool_release(adapter->pool);
}

    if (adapter->base.grp_lock) {
        pj_grp_lock_dec_ref(adapter->base.grp_lock);
    } else {
        adapter_on_destroy(tp);
    }
        tp_ice->base.grp_lock = grp_lock;

    PJ_LOG(4, (tp_ice->base.name, "ICE transport destroyed"));
    PJ_LOG(4, (tp_ice->base.name, "Destroying ICE transport"));

static void tp_loop_on_destroy(void *arg);
    pj_grp_lock_t *grp_lock;
    pj_status_t status;
    /* Create group lock */
    status = pj_grp_lock_create(pool, NULL, &grp_lock);
    if (status != PJ_SUCCESS)
        return status;

    pj_grp_lock_add_ref(grp_lock);
    pj_grp_lock_add_handler(grp_lock, pool, tp, &tp_loop_on_destroy);


static void tp_loop_on_destroy(void *arg)
{
    struct transport_loop *loop = (struct transport_loop*) arg;

    PJ_LOG(4, (loop->base.name, "Loop transport destroyed"));
    pj_pool_release(loop->pool);
}


    pj_grp_lock_dec_ref(tp->grp_lock);
    pj_grp_lock_add_ref(tp->grp_lock);

    pj_grp_lock_dec_ref(tp->grp_lock);

    pj_grp_lock_add_ref(tp->grp_lock);

    pj_grp_lock_dec_ref(tp->grp_lock);

/* SRTP destroy handler */
static void srtp_on_destroy(void *arg);
    /* Setup group lock handler for destroy and callback synchronization */
    if (tp && tp->grp_lock) {
        srtp->base.grp_lock = tp->grp_lock;
        pj_grp_lock_add_ref(tp->grp_lock);
        pj_grp_lock_add_handler(tp->grp_lock, pool, srtp, &srtp_on_destroy);
    }

    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport created"));


/* SRTP real destroy */
static void srtp_on_destroy(void *arg)
{
    transport_srtp *srtp = (transport_srtp*)arg;

    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport destroyed"));

    pj_lock_destroy(srtp->mutex);
    pj_pool_safe_release(&srtp->pool);
}


    PJ_LOG(4, (srtp->pool->obj_name, "Destroying SRTP transport"));

    if (srtp->base.grp_lock) {
        pj_grp_lock_dec_ref(srtp->base.grp_lock);
    } else {
        /* Only get here when the underlying transport does not have
         * a group lock, race condition with callbacks may occur.
         * Currently UDP, ICE, and loop have a group lock already.
         */
        PJ_LOG(4,(srtp->pool->obj_name,
                  "Warning: underlying transport does not have group lock"));
        /* In case mutex is being acquired by other thread.
         * An effort to synchronize destroy() & callbacks when the underlying
         * transport does not provide a group lock.
         */
        pj_lock_acquire(srtp->mutex);
        pj_lock_release(srtp->mutex);

        srtp_on_destroy(srtp);
    }
static void dtls_on_destroy(void *arg);

    pj_bool_t            is_destroying;     /* DTLS being destroyed?        */
    pj_status_t status;
    /* Setup group lock handler for destroy and callback synchronization */
    if (srtp->base.grp_lock) {
        pj_grp_lock_t *grp_lock = srtp->base.grp_lock;

        ds->base.grp_lock = grp_lock;
        pj_grp_lock_add_ref(grp_lock);
        pj_grp_lock_add_handler(grp_lock, pool, ds, &dtls_on_destroy);
    } else {
        status = pj_lock_create_simple_mutex(ds->pool, "dtls_ssl_lock%p",
                                             &ds->ossl_lock);
        if (status != PJ_SUCCESS)
            return status;
    }
/* Lock/unlock for DTLS states access protection */

static void DTLS_LOCK(dtls_srtp *ds) {
    if (ds->base.grp_lock)
        pj_grp_lock_acquire(ds->base.grp_lock);
    else
        pj_lock_acquire(ds->ossl_lock);
}


static void DTLS_UNLOCK(dtls_srtp *ds) {
    if (ds->base.grp_lock)
        pj_grp_lock_release(ds->base.grp_lock);
    else
        pj_lock_release(ds->ossl_lock);
}


    DTLS_LOCK(ds);
    DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
    DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
    DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
            DTLS_UNLOCK(ds);
            DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
    DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
        DTLS_UNLOCK(ds);
    DTLS_UNLOCK(ds);
    DTLS_LOCK(ds);

    if (size < 1 || !IS_DTLS_PKT(pkt, size) || ds->is_destroying) {
        DTLS_UNLOCK(ds);
    }
            if (status != PJ_SUCCESS) {
                DTLS_UNLOCK(ds);
            }
        if (status != PJ_SUCCESS) {
            DTLS_UNLOCK(ds);
        }

    DTLS_UNLOCK(ds);

static void dtls_on_destroy(void *arg) {
    dtls_srtp *ds = (dtls_srtp *)arg;

    if (ds->ossl_lock)
        pj_lock_destroy(ds->ossl_lock);

    pj_pool_safe_release(&ds->pool);
}

    ds->is_destroying = PJ_TRUE;

    DTLS_LOCK(ds);

    DTLS_UNLOCK(ds);
    if (ds->base.grp_lock) {
        pj_grp_lock_dec_ref(ds->base.grp_lock);
    } else {
        dtls_on_destroy(tp);
    }
    pj_grp_lock_t *grp_lock;
    /* Create group lock */
    status = pj_grp_lock_create(pool, NULL, &grp_lock);
    if (status != PJ_SUCCESS)
        goto on_error;

    pj_grp_lock_add_ref(grp_lock);
    tp->base.grp_lock = grp_lock;

    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtp_sock, grp_lock,
                                       tp, &rtp_cb, &tp->rtp_key);
     *
     * Note that we still need this even after group lock is added to
     * maintain the above behavior.
    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtcp_sock, grp_lock,
                                       tp, &rtcp_cb, &tp->rtcp_key);

    /* The following calls to pj_ioqueue_unregister() will block the execution
     * if callback is still being called because allow_concurrent is false.
     * So it is safe to release the pool immediately after.
     */
    pj_grp_lock_dec_ref(tp->grp_lock);