CVE-2021-32686 (C, published 2021-07-23T22:15Z)
Description: 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 PJSIP before version 2.11.1, there are a couple of issues found in the SSL socket. First, a race condition between callback and destroy, due to the accepted socket having no group lock. Second, the SSL socket parent/listener may get destroyed during handshake. Both issues were reported to happen intermittently in heavy load TLS connections. They cause a crash, resulting in a denial of service. These are fixed in version 2.11.1.
Severity: MEDIUM (CVSS v3 base score: 5.9)
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
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.
Repository: https://github.com/pjsip/pjproject
Files changed:
pjlib/src/pj/ssl_sock_imp_common.c
pjlib/src/pj/ssl_sock_ossl.c
pjsip/src/pjsip/sip_transport_tls.c
Commit message: Merge pull request from GHSA-cv8x-p47p-99wr

* - Avoid SSL socket parent/listener getting destroyed during handshake by increasing parent's reference count.
- Add missing SSL socket close when the newly accepted SSL socket is discarded in SIP TLS transport.

* - Fix silly mistake: accepted active socket created without group lock in SSL socket.
- Replace assertion with normal validation check of SSL socket instance in OpenSSL verification callback (verify_cb()) to avoid crash, e.g: if somehow race condition with SSL socket destroy happens or OpenSSL application data index somehow gets corrupted.

Vulnerable code:
	    pj_bool_t ret;
	    if (ret == PJ_FALSE)
		return PJ_FALSE;	
	    pj_bool_t ret;
	    if (ret == PJ_FALSE)
		return PJ_FALSE;
    ssock->parent = ssock_parent;
    if (!ssock->asock_rbuf)
        return PJ_ENOMEM;
        if (!ssock->asock_rbuf[i])
            return PJ_ENOMEM;
	asock_cfg.grp_lock = ssock->param.grp_lock = glock;
    ssock->last_err = err;
    ssock->last_err = ssl_err;
/* SSL password callback. */
    pj_ssl_sock_t *ssock;
    SSL *ossl_ssl;
    pj_assert(ossl_ssl);
    pj_assert(ssock);
	if (listener && listener->tls_setting.on_accept_fail_cb) {

Fixed code:
	pj_bool_t ret = PJ_TRUE;

	    /* Decrement ref count of parent */
	    if (ssock->parent->param.grp_lock) {
		pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
		ssock->parent = NULL;
	    }



	/* Decrement ref count of parent and reset parent (we don't need it
	 * anymore, right?).
	 */
	if (ssock->parent->param.grp_lock) {
	    pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
	    ssock->parent = NULL;
	}

	if (ret == PJ_FALSE)
	    return PJ_FALSE;
    /* Set parent and add ref count (avoid parent destroy during handshake) */
    ssock->parent = ssock_parent;
    if (ssock->parent->param.grp_lock)
	pj_grp_lock_add_ref(ssock->parent->param.grp_lock);

    if (!ssock->asock_rbuf) {
	status = PJ_ENOMEM;
	goto on_return;
    }
	if (!ssock->asock_rbuf[i]) {
	    status = PJ_ENOMEM;
	    goto on_return;
	}
	ssock->param.grp_lock = glock;
    asock_cfg.grp_lock = ssock->param.grp_lock;
    if (ssock)
	ssock->last_err = err;
    if (ssock)
	ssock->last_err = ssl_err;
    if (sslsock_idx == -1) {
	status = STATUS_FROM_SSL_ERR2("Init", NULL, -1, ERR_get_error(), 0);
	PJ_LOG(1,(THIS_FILE,
	       "Fatal error: failed to get application data index for "
	       "SSL socket"));
	return status;
    }
/* SSL certificate verification result callback.
 * Note that this callback seems to be always called from library worker
 * thread, e.g: active socket on_read_complete callback, which should have
 * already been equipped with race condition avoidance mechanism (should not
 * be destroyed while callback is being invoked).
 */
    pj_ssl_sock_t *ssock = NULL;
    SSL *ossl_ssl = NULL;
    if (!ossl_ssl) {
	PJ_LOG(1,(THIS_FILE,
		  "SSL verification callback failed to get SSL instance"));
	goto on_return;
    }
    if (!ssock) {
	/* SSL socket may have been destroyed */
	PJ_LOG(1,(THIS_FILE,
		  "SSL verification callback failed to get SSL socket "
		  "instance (sslsock_idx=%d).", sslsock_idx));
	goto on_return;
    }
on_return:

    /* Detach from SSL instance */
    if (ossock->ossl_ssl) {
	SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL);
    }

    if (!listener) {
	/* Listener already destroyed, e.g: after TCP accept but before SSL
	 * handshake is completed.
	 */
	if (new_ssock && accept_status == PJ_SUCCESS) {
	    /* Close the SSL socket if the accept op is successful */
	    PJ_LOG(4,(THIS_FILE,
		      "Incoming TLS connection from %s (sock=%d) is discarded "
		      "because listener is already destroyed",
		      pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
		      new_ssock));

	    pj_ssl_sock_close(new_ssock);
	}

	return PJ_FALSE;
    }
	if (listener->tls_setting.on_accept_fail_cb) {
	pj_ssl_sock_close(new_ssock);

	pj_ssl_sock_close(new_ssock);