]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
bundled_pjproject: Backport security fixes from pjproject 2.13.1
authorGeorge Joseph <gjoseph@sangoma.com>
Wed, 5 Jul 2023 13:35:28 +0000 (07:35 -0600)
committerGeorge Joseph <gjoseph@sangoma.com>
Fri, 7 Jul 2023 15:52:21 +0000 (09:52 -0600)
Merge-pull-request-from-GHSA-9pfh-r8x4-w26w.patch
Merge-pull-request-from-GHSA-cxwq-5g9x-x7fr.patch
Locking-fix-so-that-SSL_shutdown-and-SSL_write-are-n.patch
Don-t-call-SSL_shutdown-when-receiving-SSL_ERROR_SYS.patch

Resolves: #188

third-party/pjproject/patches/0300-Merge-pull-request-from-GHSA-9pfh-r8x4-w26w.patch [new file with mode: 0644]
third-party/pjproject/patches/0301-Merge-pull-request-from-GHSA-cxwq-5g9x-x7fr.patch [new file with mode: 0644]
third-party/pjproject/patches/0302-Locking-fix-so-that-SSL_shutdown-and-SSL_write-are-n.patch [new file with mode: 0644]
third-party/pjproject/patches/0303-Don-t-call-SSL_shutdown-when-receiving-SSL_ERROR_SYS.patch [new file with mode: 0644]

diff --git a/third-party/pjproject/patches/0300-Merge-pull-request-from-GHSA-9pfh-r8x4-w26w.patch b/third-party/pjproject/patches/0300-Merge-pull-request-from-GHSA-9pfh-r8x4-w26w.patch
new file mode 100644 (file)
index 0000000..78e107a
--- /dev/null
@@ -0,0 +1,203 @@
+From 3ba8f3c0188fa05bb62d8bc9176ca7c7db79f8c0 Mon Sep 17 00:00:00 2001
+From: Nanang Izzuddin <nanang@teluu.com>
+Date: Tue, 20 Dec 2022 11:39:12 +0700
+Subject: [PATCH 300/303] Merge pull request from GHSA-9pfh-r8x4-w26w
+
+* Fix buffer overread in STUN message decoder
+
+* Updates based on comments
+---
+ pjnath/include/pjnath/stun_msg.h |  4 ++++
+ pjnath/src/pjnath/stun_msg.c     | 32 ++++++++++++++++++++------------
+ 2 files changed, 24 insertions(+), 12 deletions(-)
+
+diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h
+index 6b5fc0f21..e8f52db3c 100644
+--- a/pjnath/include/pjnath/stun_msg.h
++++ b/pjnath/include/pjnath/stun_msg.h
+@@ -436,20 +436,21 @@ typedef enum pj_stun_status
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+                                 Transaction ID
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+                                                                        |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    \endverbatim
+  */
++#pragma pack(1)
+ typedef struct pj_stun_msg_hdr
+ {
+     /**
+      * STUN message type, which the first two bits must be zeroes.
+      */
+     pj_uint16_t               type;
+     /**
+      * The message length is the size, in bytes, of the message not
+      * including the 20 byte STUN header.
+@@ -467,53 +468,56 @@ typedef struct pj_stun_msg_hdr
+      * The transaction ID is a 96 bit identifier.  STUN transactions are
+      * identified by their unique 96-bit transaction ID.  For request/
+      * response transactions, the transaction ID is chosen by the STUN
+      * client and MUST be unique for each new STUN transaction generated by
+      * that STUN client.  The transaction ID MUST be uniformly and randomly
+      * distributed between 0 and 2**96 - 1. 
+      */
+     pj_uint8_t                tsx_id[12];
+ } pj_stun_msg_hdr;
++#pragma pack()
+ /**
+  * This structre describes STUN attribute header. Each attribute is
+  * TLV encoded, with a 16 bit type, 16 bit length, and variable value.
+  * Each STUN attribute ends on a 32 bit boundary:
+  *
+  * \verbatim
+         0                   1                   2                   3
+         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |         Type                  |            Length             |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    \endverbatim
+  */
++#pragma pack(1)
+ typedef struct pj_stun_attr_hdr
+ {
+     /**
+      * STUN attribute type.
+      */
+     pj_uint16_t               type;
+     /**
+      * The Length refers to the length of the actual useful content of the
+      * Value portion of the attribute, measured in bytes. The value
+      * in the Length field refers to the length of the Value part of the
+      * attribute prior to padding - i.e., the useful content.
+      */
+     pj_uint16_t               length;
+ } pj_stun_attr_hdr;
++#pragma pack()
+ /**
+  * This structure describes STUN generic IP address attribute, used for
+  * example to represent STUN MAPPED-ADDRESS attribute.
+  *
+  * The generic IP address attribute indicates the transport address.
+  * It consists of an eight bit address family, and a sixteen bit port,
+  * followed by a fixed length value representing the IP address.  If the
+  * address family is IPv4, the address is 32 bits, in network byte
+diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
+index bd83351e6..fd15230bc 100644
+--- a/pjnath/src/pjnath/stun_msg.c
++++ b/pjnath/src/pjnath/stun_msg.c
+@@ -739,22 +739,22 @@ PJ_DEF(int) pj_stun_set_padding_char(int chr)
+     int old_pad = padding_char;
+     padding_char = chr;
+     return old_pad;
+ }
+ //////////////////////////////////////////////////////////////////////////////
+ #define INIT_ATTR(a,t,l)    (a)->hdr.type=(pj_uint16_t)(t), \
+-                          (a)->hdr.length=(pj_uint16_t)(l)
+-#define ATTR_HDR_LEN      4
++                            (a)->hdr.length=(pj_uint16_t)(l)
++#define ATTR_HDR_LEN        sizeof(pj_stun_attr_hdr)
+ static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos)
+ {
+     return (pj_uint16_t) ((buf[pos + 0] << 8) | \
+                         (buf[pos + 1] << 0));
+ }
+ /*unused PJ_INLINE(pj_uint16_t) GETVAL16N(const pj_uint8_t *buf, unsigned pos)
+ {
+     return pj_htons(GETVAL16H(buf,pos));
+@@ -2318,56 +2318,64 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
+     PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL);
+     PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG);
+     if (p_parsed_len)
+       *p_parsed_len = 0;
+     if (p_response)
+       *p_response = NULL;
+     /* Check if this is a STUN message, if necessary */
+     if (options & PJ_STUN_CHECK_PACKET) {
+-      status = pj_stun_msg_check(pdu, pdu_len, options);
+-      if (status != PJ_SUCCESS)
+-          return status;
++        status = pj_stun_msg_check(pdu, pdu_len, options);
++        if (status != PJ_SUCCESS)
++            return status;
++    } else {
++        /* For safety, verify packet length at least */
++        pj_uint32_t msg_len = GETVAL16H(pdu, 2) + 20;
++        if (msg_len > pdu_len ||
++            ((options & PJ_STUN_IS_DATAGRAM) && msg_len != pdu_len))
++        {
++            return PJNATH_EINSTUNMSGLEN;
++        }
+     }
+     /* Create the message, copy the header, and convert to host byte order */
+     msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
+     pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr));
+     msg->hdr.type = pj_ntohs(msg->hdr.type);
+     msg->hdr.length = pj_ntohs(msg->hdr.length);
+     msg->hdr.magic = pj_ntohl(msg->hdr.magic);
+     pdu += sizeof(pj_stun_msg_hdr);
+     /* pdu_len -= sizeof(pj_stun_msg_hdr); */
+     pdu_len = msg->hdr.length;
+     /* No need to create response if this is not a request */
+     if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
+       p_response = NULL;
+     /* Parse attributes */
+-    while (pdu_len >= 4) {
+-      unsigned attr_type, attr_val_len;
+-      const struct attr_desc *adesc;
++    while (pdu_len >= ATTR_HDR_LEN) {
++        unsigned attr_type, attr_val_len;
++        const struct attr_desc *adesc;
+       /* Get attribute type and length. If length is not aligned
+        * to 4 bytes boundary, add padding.
+        */
+       attr_type = GETVAL16H(pdu, 0);
+       attr_val_len = GETVAL16H(pdu, 2);
+       attr_val_len = (attr_val_len + 3) & (~3);
+-      /* Check length */
+-      if (pdu_len < attr_val_len) {
+-          pj_str_t err_msg;
+-          char err_msg_buf[80];
++        /* Check length */
++        if (pdu_len < attr_val_len + ATTR_HDR_LEN) {
++            pj_str_t err_msg;
++            char err_msg_buf[80];
+           err_msg.ptr = err_msg_buf;
+           err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf),
+                                           "Attribute %s has invalid length",
+                                           pj_stun_get_attr_name(attr_type));
+           PJ_LOG(4,(THIS_FILE, "Error decoding message: %.*s",
+                     (int)err_msg.slen, err_msg.ptr));
+           if (p_response) {
+-- 
+2.41.0
+
diff --git a/third-party/pjproject/patches/0301-Merge-pull-request-from-GHSA-cxwq-5g9x-x7fr.patch b/third-party/pjproject/patches/0301-Merge-pull-request-from-GHSA-cxwq-5g9x-x7fr.patch
new file mode 100644 (file)
index 0000000..7ca74aa
--- /dev/null
@@ -0,0 +1,81 @@
+From 02d2273f085943b7d8daf7814d9b316216cae26b Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Fri, 23 Dec 2022 15:05:28 +0800
+Subject: [PATCH 301/303] Merge pull request from GHSA-cxwq-5g9x-x7fr
+
+* Fixed heap buffer overflow when parsing STUN errcode attribute
+
+* Also fixed uint parsing
+---
+ pjnath/src/pjnath/stun_msg.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
+index fd15230bc..d3aaae5bf 100644
+--- a/pjnath/src/pjnath/stun_msg.c
++++ b/pjnath/src/pjnath/stun_msg.c
+@@ -1432,26 +1432,26 @@ static pj_status_t decode_uint_attr(pj_pool_t *pool,
+                                   void **p_attr)
+ {
+     pj_stun_uint_attr *attr;
+     PJ_UNUSED_ARG(msghdr);
+     /* Create the attribute */
+     attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
+     GETATTRHDR(buf, &attr->hdr);
+-    attr->value = GETVAL32H(buf, 4);
+-
+     /* Check that the attribute length is valid */
+     if (attr->hdr.length != 4)
+       return PJNATH_ESTUNINATTRLEN;
++    attr->value = GETVAL32H(buf, 4);
++
+     /* Done */
+     *p_attr = attr;
+     return PJ_SUCCESS;
+ }
+ static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, 
+                                   unsigned len, 
+                                   const pj_stun_msg_hdr *msghdr,
+@@ -1751,28 +1751,29 @@ static pj_status_t decode_errcode_attr(pj_pool_t *pool,
+ {
+     pj_stun_errcode_attr *attr;
+     pj_str_t value;
+     PJ_UNUSED_ARG(msghdr);
+     /* Create the attribute */
+     attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
+     GETATTRHDR(buf, &attr->hdr);
++    /* Check that the attribute length is valid */
++    if (attr->hdr.length < 4)
++        return PJNATH_ESTUNINATTRLEN;
++
+     attr->err_code = buf[6] * 100 + buf[7];
+     /* Get pointer to the string in the message */
+     value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
+     value.slen = attr->hdr.length - 4;
+-    /* Make sure the length is never negative */
+-    if (value.slen < 0)
+-      value.slen = 0;
+     /* Copy the string to the attribute */
+     pj_strdup(pool, &attr->reason, &value);
+     /* Done */
+     *p_attr = attr;
+     return PJ_SUCCESS;
+ }
+-- 
+2.41.0
+
diff --git a/third-party/pjproject/patches/0302-Locking-fix-so-that-SSL_shutdown-and-SSL_write-are-n.patch b/third-party/pjproject/patches/0302-Locking-fix-so-that-SSL_shutdown-and-SSL_write-are-n.patch
new file mode 100644 (file)
index 0000000..69f615b
--- /dev/null
@@ -0,0 +1,166 @@
+From 0a3af5f1a0f64fd30f35338b8328391283d88ecb Mon Sep 17 00:00:00 2001
+From: Matthew Fredrickson <mfredrickson@fluentstream.com>
+Date: Tue, 30 May 2023 04:33:05 -0500
+Subject: [PATCH 302/303] Locking fix so that SSL_shutdown and SSL_write are
+ not called at same time (#3583)
+
+---
+ pjlib/src/pj/ssl_sock_ossl.c | 82 ++++++++++++++++++++++--------------
+ 1 file changed, 51 insertions(+), 31 deletions(-)
+
+diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
+index ed441e3e2..5c8e67b76 100644
+--- a/pjlib/src/pj/ssl_sock_ossl.c
++++ b/pjlib/src/pj/ssl_sock_ossl.c
+@@ -1627,44 +1627,58 @@ static void ssl_destroy(pj_ssl_sock_t *ssock)
+     /* Potentially shutdown OpenSSL library if this is the last
+      * context exists.
+      */
+     shutdown_openssl();
+ }
+ /* Reset SSL socket state */
+ static void ssl_reset_sock_state(pj_ssl_sock_t *ssock)
+ {
++    int post_unlock_flush_circ_buf = 0;
++
+     ossl_sock_t *ossock = (ossl_sock_t *)ssock;
++    /* Must lock around SSL calls, particularly SSL_shutdown
++     * as it can modify the write BIOs and destructively
++     * interfere with any ssl_write() calls in progress
++     * above in a multithreaded environment */
++    pj_lock_acquire(ssock->write_mutex);
++
+     /* Detach from SSL instance */
+     if (ossock->ossl_ssl) {
+       SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL);
+     }
+     /**
+      * Avoid calling SSL_shutdown() if handshake wasn't completed.
+      * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
+      * SSL handshake, while previous versions always return 0.
+      */
+     if (ossock->ossl_ssl && SSL_in_init(ossock->ossl_ssl) == 0) {
+-      int ret = SSL_shutdown(ossock->ossl_ssl);
+-      if (ret == 0) {
+-          /* Flush data to send close notify. */
+-          flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0);
+-      }
++        int ret = SSL_shutdown(ossock->ossl_ssl);
++        if (ret == 0) {
++            /* SSL_shutdown will potentially trigger a bunch of
++             * data to dump to the socket */
++            post_unlock_flush_circ_buf = 1;
++        }
+     }
+-    pj_lock_acquire(ssock->write_mutex);
+     ssock->ssl_state = SSL_STATE_NULL;
++
+     pj_lock_release(ssock->write_mutex);
++    if (post_unlock_flush_circ_buf) {
++        /* Flush data to send close notify. */
++        flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0);
++    }
++
+     ssl_close_sockets(ssock);
+     /* Upon error, OpenSSL may leave any error description in the thread 
+      * error queue, which sometime may cause next call to SSL API returning
+      * false error alarm, e.g: in Linux, SSL_CTX_use_certificate_chain_file()
+      * returning false error after a handshake error (in different SSL_CTX!).
+      * For now, just clear thread error queue here.
+      */
+     ERR_clear_error();
+ }
+@@ -2330,52 +2344,58 @@ static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size)
+ {
+     ossl_sock_t *ossock = (ossl_sock_t *)ssock;
+     int size_ = *size;
+     int len = size_;
+     /* SSL_read() may write some data to write buffer when re-negotiation
+      * is on progress, so let's protect it with write mutex.
+      */
+     pj_lock_acquire(ssock->write_mutex);
+     *size = size_ = SSL_read(ossock->ossl_ssl, data, size_);
+-    pj_lock_release(ssock->write_mutex);
+     if (size_ <= 0) {
+       pj_status_t status;
+       int err = SSL_get_error(ossock->ossl_ssl, size_);
+-      /* SSL might just return SSL_ERROR_WANT_READ in 
+-       * re-negotiation.
+-       */
+-      if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ) {
+-          if (err == SSL_ERROR_SYSCALL && size_ == -1 &&
+-              ERR_peek_error() == 0 && errno == 0)
+-          {
+-              status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
+-                                            err, len);
+-              PJ_LOG(4,("SSL", "SSL_read() = -1, with "
+-                               "SSL_ERROR_SYSCALL, no SSL error, "
+-                               "and errno = 0 - skip BIO error"));
+-              /* Ignore these errors */
+-          } else {
+-              /* Reset SSL socket state, then return PJ_FALSE */
+-              status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
+-                                            err, len);
+-              ssl_reset_sock_state(ssock);
+-              return status;
+-          }
+-      }
+-      
+-      /* Need renegotiation */
+-      return PJ_EEOF;
++        /* SSL might just return SSL_ERROR_WANT_READ in 
++         * re-negotiation.
++         */
++        if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ) {
++            if (err == SSL_ERROR_SYSCALL && size_ == -1 &&
++                ERR_peek_error() == 0 && errno == 0)
++            {
++                status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
++                                              err, len);
++                PJ_LOG(4,("SSL", "SSL_read() = -1, with "
++                                 "SSL_ERROR_SYSCALL, no SSL error, "
++                                 "and errno = 0 - skip BIO error"));
++                /* Ignore these errors */
++            } else {
++                /* Reset SSL socket state, then return PJ_FALSE */
++                status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
++                                              err, len);
++                pj_lock_release(ssock->write_mutex);
++                /* Unfortunately we can't hold the lock here to reset all the state.
++                  * We probably should though.
++                  */
++                ssl_reset_sock_state(ssock);
++                return status;
++            }
++        }
++        
++        pj_lock_release(ssock->write_mutex);
++        /* Need renegotiation */
++        return PJ_EEOF;
+     }
++    pj_lock_release(ssock->write_mutex);
++
+     return PJ_SUCCESS;
+ }
+ /* Write plain data to SSL and flush write BIO. */
+ static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data,
+                            pj_ssize_t size, int *nwritten)
+ {
+     ossl_sock_t *ossock = (ossl_sock_t *)ssock;
+     pj_status_t status = PJ_SUCCESS;
+-- 
+2.41.0
+
diff --git a/third-party/pjproject/patches/0303-Don-t-call-SSL_shutdown-when-receiving-SSL_ERROR_SYS.patch b/third-party/pjproject/patches/0303-Don-t-call-SSL_shutdown-when-receiving-SSL_ERROR_SYS.patch
new file mode 100644 (file)
index 0000000..ca058cf
--- /dev/null
@@ -0,0 +1,123 @@
+From 0f7267f220be79e21cf9f96efa01929285e9aa55 Mon Sep 17 00:00:00 2001
+From: Riza Sulistyo <trengginas@users.noreply.github.com>
+Date: Wed, 5 Jul 2023 10:38:21 +0700
+Subject: [PATCH 303/303] Don't call SSL_shutdown() when receiving
+ SSL_ERROR_SYSCALL or SSL_ERROR_SSL (#3577)
+
+---
+ pjlib/src/pj/ssl_sock_imp_common.c |  1 +
+ pjlib/src/pj/ssl_sock_imp_common.h | 13 +++++++------
+ pjlib/src/pj/ssl_sock_ossl.c       | 17 ++++++++++++-----
+ 3 files changed, 20 insertions(+), 11 deletions(-)
+
+diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c
+index ae2f1136e..c825676c3 100644
+--- a/pjlib/src/pj/ssl_sock_imp_common.c
++++ b/pjlib/src/pj/ssl_sock_imp_common.c
+@@ -237,20 +237,21 @@ static void ssl_close_sockets(pj_ssl_sock_t *ssock)
+ #endif
+ /* When handshake completed:
+  * - notify application
+  * - if handshake failed, reset SSL state
+  * - return PJ_FALSE when SSL socket instance is destroyed by application.
+  */
+ static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock, 
+                                      pj_status_t status)
+ {
++    ssock->handshake_status = status;
+     /* Cancel handshake timer */
+     if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) {
+       pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer);
+       ssock->timer.id = TIMER_NONE;
+     }
+     /* Update certificates info on successful handshake */
+     if (status == PJ_SUCCESS)
+       ssl_update_certs_info(ssock);
+diff --git a/pjlib/src/pj/ssl_sock_imp_common.h b/pjlib/src/pj/ssl_sock_imp_common.h
+index cba28dbd3..8a63faa90 100644
+--- a/pjlib/src/pj/ssl_sock_imp_common.h
++++ b/pjlib/src/pj/ssl_sock_imp_common.h
+@@ -99,26 +99,27 @@ struct pj_ssl_sock_t
+                                     * information allocation. Don't use for 
+                                     * other purposes. */
+     pj_ssl_sock_t      *parent;
+     pj_ssl_sock_param   param;
+     pj_ssl_sock_param   newsock_param;
+     pj_ssl_cert_t      *cert;
+     
+     pj_ssl_cert_info    local_cert_info;
+     pj_ssl_cert_info    remote_cert_info;
+-    pj_bool_t           is_server;
+-    enum ssl_state      ssl_state;
+-    pj_ioqueue_op_key_t         handshake_op_key;
+-    pj_ioqueue_op_key_t         shutdown_op_key;
+-    pj_timer_entry      timer;
+-    pj_status_t                 verify_status;
++    pj_bool_t             is_server;
++    enum ssl_state        ssl_state;
++    pj_ioqueue_op_key_t   handshake_op_key;
++    pj_ioqueue_op_key_t   shutdown_op_key;
++    pj_timer_entry        timer;
++    pj_status_t           verify_status;
++    pj_status_t           handshake_status;
+     pj_bool_t           is_closing;
+     unsigned long       last_err;
+     pj_sock_t           sock;
+     pj_activesock_t    *asock;
+     pj_sockaddr                 local_addr;
+     pj_sockaddr                 rem_addr;
+     int                         addr_len;
+diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
+index 5c8e67b76..8a717e362 100644
+--- a/pjlib/src/pj/ssl_sock_ossl.c
++++ b/pjlib/src/pj/ssl_sock_ossl.c
+@@ -1646,27 +1646,34 @@ static void ssl_reset_sock_state(pj_ssl_sock_t *ssock)
+     /* Detach from SSL instance */
+     if (ossock->ossl_ssl) {
+       SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL);
+     }
+     /**
+      * Avoid calling SSL_shutdown() if handshake wasn't completed.
+      * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
+      * SSL handshake, while previous versions always return 0.
++     * Call SSL_shutdown() when there is a timeout handshake failure or
++     * the last error is not SSL_ERROR_SYSCALL and not SSL_ERROR_SSL.
+      */
+     if (ossock->ossl_ssl && SSL_in_init(ossock->ossl_ssl) == 0) {
+-        int ret = SSL_shutdown(ossock->ossl_ssl);
+-        if (ret == 0) {
+-            /* SSL_shutdown will potentially trigger a bunch of
+-             * data to dump to the socket */
+-            post_unlock_flush_circ_buf = 1;
++        if (ssock->handshake_status == PJ_ETIMEDOUT ||
++            (ssock->last_err != SSL_ERROR_SYSCALL &&
++             ssock->last_err != SSL_ERROR_SSL))
++        {
++            int ret = SSL_shutdown(ossock->ossl_ssl);
++            if (ret == 0) {
++                /* SSL_shutdown will potentially trigger a bunch of
++                 * data to dump to the socket */
++                post_unlock_flush_circ_buf = 1;
++            }
+         }
+     }
+     ssock->ssl_state = SSL_STATE_NULL;
+     pj_lock_release(ssock->write_mutex);
+     if (post_unlock_flush_circ_buf) {
+         /* Flush data to send close notify. */
+         flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0);
+-- 
+2.41.0
+