CVE-2022-24754: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: PJSIP is a free and open source multimedia communication library written in C language. In versions prior to and including 2.12 PJSIP there is a stack-buffer overflow vulnerability which only impacts PJSIP users who accept hashed digest credentials (credentials with data_type `PJSIP_CRED_DATA_DIGEST`). This issue has been patched in the master branch of the PJSIP repository and will be included with the next release. Users unable to upgrade need to check that the hashed digest data length must be equal to `PJSIP_MD5STRLEN` before passing to PJSIP.
CWE description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
CVSS v3 base score: 9.8
Published: 2022-03-11T20:15Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjsip/include/pjsip/sip_auth.h, pjsip/src/pjsip/sip_auth_aka.c, pjsip/src/pjsip/sip_auth_client.c, pjsip/src/pjsip/sip_auth_server.c, pjsip/src/pjsua-lib/pjsua_core.c
Commit date: 2022-03-08 22:20:39 +0700
Commit message: Use PJ_ASSERT_RETURN() on pjsip_auth_create_digest() and pjsua_init_tpselector() (#3009)

* Use PJ_ASSERT_RETURN on pjsip_auth_create_digest

* Use PJ_ASSERT_RETURN on pjsua_init_tpselector()

* Fix incorrect check.

* Add return value to pjsip_auth_create_digest() and pjsip_auth_create_digestSHA256()

* Modification based on comments.
Diff: diff --git a/pjsip/include/pjsip/sip_auth.h b/pjsip/include/pjsip/sip_auth.h
index 47470d91f7..a880232df3 100644
--- a/pjsip/include/pjsip/sip_auth.h
+++ b/pjsip/include/pjsip/sip_auth.h
@@ -582,20 +582,50 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
  * @param qop		Optional qop.
  * @param uri		URI.
  * @param realm		Realm.
  * @param cred_info	Credential info.
  * @param method	SIP method.
+ *
+ * @return		PJ_SUCCESS on success.
  */
-PJ_DECL(void) pjsip_auth_create_digest(pj_str_t *result,
-				       const pj_str_t *nonce,
-				       const pj_str_t *nc,
-				       const pj_str_t *cnonce,
-				       const pj_str_t *qop,
-				       const pj_str_t *uri,
-				       const pj_str_t *realm,
-				       const pjsip_cred_info *cred_info,
-				       const pj_str_t *method);
+PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result,
+					      const pj_str_t *nonce,
+					      const pj_str_t *nc,
+					      const pj_str_t *cnonce,
+					      const pj_str_t *qop,
+					      const pj_str_t *uri,
+					      const pj_str_t *realm,
+					      const pjsip_cred_info *cred_info,
+					      const pj_str_t *method);
+
+/**
+ * Helper function to create SHA-256 digest out of the specified 
+ * parameters.
+ *
+ * @param result	String to store the response digest. This string
+ *			must have been preallocated by caller with the 
+ *			buffer at least PJSIP_SHA256STRLEN (64 bytes) in size.
+ * @param nonce		Optional nonce.
+ * @param nc		Nonce count.
+ * @param cnonce	Optional cnonce.
+ * @param qop		Optional qop.
+ * @param uri		URI.
+ * @param realm		Realm.
+ * @param cred_info	Credential info.
+ * @param method	SIP method.
+ *
+ * @return		PJ_SUCCESS on success. 
+ */
+PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result,
+					    const pj_str_t* nonce,
+					    const pj_str_t* nc,
+					    const pj_str_t* cnonce,
+					    const pj_str_t* qop,
+					    const pj_str_t* uri,
+					    const pj_str_t* realm,
+					    const pjsip_cred_info* cred_info,
+					    const pj_str_t* method);
 
 /**
  * @}
  */
 
diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c
index bd0a0fd44e..b36c7dfd45 100644
--- a/pjsip/src/pjsip/sip_auth_aka.c
+++ b/pjsip/src/pjsip/sip_auth_aka.c
@@ -55,22 +55,23 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
     pj_uint8_t ak[PJSIP_AKA_AKLEN];
     pj_uint8_t sqn[PJSIP_AKA_SQNLEN];
     pj_uint8_t xmac[PJSIP_AKA_MACLEN];
     pjsip_cred_info aka_cred;
     int i, len;
-    pj_status_t status;
+    pj_status_t status = PJ_SUCCESS;
 
     /* Check the algorithm is supported. */
     if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) {
 	/*
-	 * A normal MD5 authentication is requested. Fallbackt to the usual
+	 * A normal MD5 authentication is requested. Fallback to the usual
 	 * MD5 digest creation.
 	 */
-	pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc,
-				 &auth->cnonce, &auth->qop, &auth->uri,
-				 &auth->realm, cred, method);
-	return PJ_SUCCESS;
+	status = pjsip_auth_create_digest(&auth->response, &auth->nonce, 
+			         &auth->nc, &auth->cnonce, &auth->qop, 
+			         &auth->uri, &auth->realm, cred, method);
+
+	return status;
 
     } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) {
 	/*
 	 * AKA version 1 is requested.
 	 */
@@ -145,13 +146,13 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
 	 * For AKAv1, the password is RES
 	 */
 	aka_cred.data.ptr = (char*)res;
 	aka_cred.data.slen = PJSIP_AKA_RESLEN;
 
-	pjsip_auth_create_digest(&auth->response, &chal->nonce, 
+	status = pjsip_auth_create_digest(&auth->response, &chal->nonce, 
 				 &auth->nc, &auth->cnonce, &auth->qop, 
-				 &auth->uri, &chal->realm, &aka_cred, method);
+				 &auth->uri, &chal->realm, &aka_cred, method);	
 
     } else if (aka_version == 2) {
 
 	/*
 	 * For AKAv2, password is base64 encoded [1] parameters:
@@ -184,21 +185,21 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
 	aka_cred.data.ptr = tmp_buf;
 	pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest),
 	                 aka_cred.data.ptr, &len);
 	aka_cred.data.slen = hmac64_len;
 
-	pjsip_auth_create_digest(&auth->response, &chal->nonce, 
+	status = pjsip_auth_create_digest(&auth->response, &chal->nonce, 
 				 &auth->nc, &auth->cnonce, &auth->qop, 
 				 &auth->uri, &chal->realm, &aka_cred, method);
 
     } else {
 	pj_assert(!"Bug!");
 	return PJ_EBUG;
     }
 
     /* Done */
-    return PJ_SUCCESS;
+    return status;
 }
 
 
 #endif	/* PJSIP_HAS_DIGEST_AKA_AUTH */
 
diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
index e49f16364b..35460d01e1 100644
--- a/pjsip/src/pjsip/sip_auth_client.c
+++ b/pjsip/src/pjsip/sip_auth_client.c
@@ -158,19 +158,19 @@ static void digestNtoStr(const unsigned char digest[], int n, char *output)
 
 /*
  * Create response digest based on the parameters and store the
  * digest ASCII in 'result'.
  */
-PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
-				       const pj_str_t *nonce,
-				       const pj_str_t *nc,
-				       const pj_str_t *cnonce,
-				       const pj_str_t *qop,
-				       const pj_str_t *uri,
-				       const pj_str_t *realm,
-				       const pjsip_cred_info *cred_info,
-				       const pj_str_t *method)
+PJ_DEF(pj_status_t) pjsip_auth_create_digest( pj_str_t *result,
+					      const pj_str_t *nonce,
+					      const pj_str_t *nc,
+					      const pj_str_t *cnonce,
+					      const pj_str_t *qop,
+					      const pj_str_t *uri,
+					      const pj_str_t *realm,
+					      const pjsip_cred_info *cred_info,
+					      const pj_str_t *method)
 {
     char ha1[PJSIP_MD5STRLEN];
     char ha2[PJSIP_MD5STRLEN];
     unsigned char digest[16];
     pj_md5_context pms;
@@ -192,14 +192,22 @@ PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
 	pj_md5_final(&pms, digest);
 
 	digestNtoStr(digest, 16, ha1);
 
     } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
-	pj_assert(cred_info->data.slen == 32);
+	if (cred_info->data.slen != 32) {
+	    pj_assert(!"Invalid cred_info data length");
+	    pj_bzero(result->ptr, result->slen);
+	    result->slen = 0;
+	    return PJ_EINVAL;
+	}
 	pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
     } else {
 	pj_assert(!"Invalid data_type");
+	pj_bzero(result->ptr, result->slen);
+	result->slen = 0;
+	return PJ_EINVAL;
     }
 
     AUTH_TRACE_((THIS_FILE, "  ha1=%.32s", ha1));
 
     /***
@@ -243,18 +251,19 @@ PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
     result->slen = PJSIP_MD5STRLEN;
     digestNtoStr(digest, 16, result->ptr);
 
     AUTH_TRACE_((THIS_FILE, "  digest=%.32s", result->ptr));
     AUTH_TRACE_((THIS_FILE, "Digest created"));
+    return PJ_SUCCESS;
 }
 
 
 /*
  * Create response SHA-256 digest based on the parameters and store the
  * digest ASCII in 'result'.
  */
-PJ_DEF(void) pjsip_auth_create_digestSHA256(pj_str_t *result,
+PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result,
 					    const pj_str_t *nonce,
 					    const pj_str_t *nc,
 					    const pj_str_t *cnonce,
 					    const pj_str_t *qop,
 					    const pj_str_t *uri,
@@ -289,14 +298,22 @@ PJ_DEF(void) pjsip_auth_create_digestSHA256(pj_str_t *result,
 
 	digestNtoStr(digest, 32, ha1);
 
     } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST)
     {
-	pj_assert(cred_info->data.slen == 32);
+	if (cred_info->data.slen != 64) {
+	    pj_assert(!"Invalid cred_info data length");
+	    pj_bzero(result->ptr, result->slen);
+	    result->slen = 0;
+	    return PJ_EINVAL;
+	}
 	pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
     } else {
 	pj_assert(!"Invalid data_type");
+	pj_bzero(result->ptr, result->slen);
+	result->slen = 0;
+	return PJ_EINVAL;
     }
 
     AUTH_TRACE_((THIS_FILE, " ha1=%.64s", ha1));
 
     /***
@@ -352,10 +369,11 @@ PJ_DEF(void) pjsip_auth_create_digestSHA256(pj_str_t *result,
     PJ_UNUSED_ARG(uri);
     PJ_UNUSED_ARG(realm);
     PJ_UNUSED_ARG(cred_info);
     PJ_UNUSED_ARG(method);
 #endif
+    return PJ_SUCCESS;
 }
 
 
 /*
  * Finds out if qop offer contains "auth" token.
@@ -406,10 +424,11 @@ static pj_status_t respond_digest( pj_pool_t *pool,
 				   pj_uint32_t nc,
 				   const pj_str_t *method)
 {
     const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
     pj_bool_t algo_sha256 = PJ_FALSE;
+    pj_status_t status = PJ_SUCCESS;
 
     /* Check if algo is sha256 */
 #if PJSIP_AUTH_HAS_DIGEST_SHA256
     algo_sha256 = (pj_stricmp(&chal->algorithm, &pjsip_SHA256_STR)==0);
 #endif
@@ -450,18 +469,18 @@ static pj_status_t respond_digest( pj_pool_t *pool,
 					    method, cred);
 	}
 	else {
 	    /* Convert digest to string and store in chal->response. */
 	    if (algo_sha256) {
-		pjsip_auth_create_digestSHA256(
+		status = pjsip_auth_create_digestSHA256(
 					  &cred->response, &cred->nonce, NULL,
 					  NULL,  NULL, uri, &chal->realm,
 					  cred_info, method);
 	    } else {
-		pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
-					  NULL,  NULL, uri, &chal->realm,
-					  cred_info, method);
+		status = pjsip_auth_create_digest( &cred->response, 
+					  &cred->nonce, NULL, NULL, NULL, uri, 
+					  &chal->realm, cred_info, method);
 	    }
 	}
 
     } else if (has_auth_qop(pool, &chal->qop)) {
 	/* Server requires quality of protection.
@@ -484,33 +503,33 @@ static pj_status_t respond_digest( pj_pool_t *pool,
 					    method, cred);
 	}
 	else {
 	    /* Convert digest to string and store in chal->response. */
 	    if (algo_sha256) {
-		pjsip_auth_create_digestSHA256(
+		status = pjsip_auth_create_digestSHA256(
 					  &cred->response, &cred->nonce,
 					  &cred->nc, &cred->cnonce,
 					  &pjsip_AUTH_STR, uri,
 					  &chal->realm, cred_info,
 					  method);
 	    } else {
-		pjsip_auth_create_digest( &cred->response, &cred->nonce,
-					  &cred->nc, &cred->cnonce,
-					  &pjsip_AUTH_STR, uri,
-					  &chal->realm, cred_info,
-					  method);
+		status = pjsip_auth_create_digest( &cred->response, 
+					  &cred->nonce, &cred->nc, 
+				          &cred->cnonce, &pjsip_AUTH_STR, 
+					  uri, &chal->realm, 
+					  cred_info, method);
 	    }
 	}
 
     } else {
 	/* Server requires quality protection that we don't support. */
 	PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s",
 		  chal->qop.slen, chal->qop.ptr));
 	return PJSIP_EINVALIDQOP;
     }
 
-    return PJ_SUCCESS;
+    return status;
 }
 
 #if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
 /*
  * Update authentication session with a challenge.
diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c
index 514d9d5afa..ae5b6fd436 100644
--- a/pjsip/src/pjsip/sip_auth_server.c
+++ b/pjsip/src/pjsip/sip_auth_server.c
@@ -77,10 +77,11 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
 				      const pjsip_cred_info *cred_info )
 {
     if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
 	char digest_buf[PJSIP_MD5STRLEN];
 	pj_str_t digest;
+	pj_status_t status;
 	const pjsip_digest_credential *dig = &hdr->credential.digest;
 
 	/* Check that username and realm match. 
 	 * These checks should have been performed before entering this
 	 * function.
@@ -93,20 +94,23 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
 	/* Prepare for our digest calculation. */
 	digest.ptr = digest_buf;
 	digest.slen = PJSIP_MD5STRLEN;
 
 	/* Create digest for comparison. */
-	pjsip_auth_create_digest(&digest, 
+	status = pjsip_auth_create_digest(&digest, 
 				 &hdr->credential.digest.nonce,
 				 &hdr->credential.digest.nc, 
 				 &hdr->credential.digest.cnonce,
 				 &hdr->credential.digest.qop,
 				 &hdr->credential.digest.uri,
 				 &cred_info->realm,
 				 cred_info, 
 				 method );
 
+	if (status != PJ_SUCCESS)
+	    return status;
+
 	/* Compare digest. */
 	return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
 	       PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;
 
     } else {
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 87032c6826..c437011b57 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -3124,11 +3124,12 @@ void pjsua_init_tpselector(pjsua_transport_id tp_id,
 
     pj_bzero(sel, sizeof(*sel));
     if (tp_id == PJSUA_INVALID_ID)
 	return;
 
-    pj_assert(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata));
+    PJ_ASSERT_RETURN(tp_id >= 0 && 
+		     tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata), );
     tpdata = &pjsua_var.tpdata[tp_id];
 
     flag = pjsip_transport_get_flag_from_type(tpdata->type);
 
     if (flag & PJSIP_TRANSPORT_DATAGRAM) {

Vulnerable code: PJ_DECL(void) pjsip_auth_create_digest(pj_str_t *result,
				       const pj_str_t *nonce,
				       const pj_str_t *nc,
				       const pj_str_t *cnonce,
				       const pj_str_t *qop,
				       const pj_str_t *uri,
				       const pj_str_t *realm,
				       const pjsip_cred_info *cred_info,
				       const pj_str_t *method);
    pj_status_t status;
	 * A normal MD5 authentication is requested. Fallbackt to the usual
	pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc,
				 &auth->cnonce, &auth->qop, &auth->uri,
				 &auth->realm, cred, method);
	return PJ_SUCCESS;
	pjsip_auth_create_digest(&auth->response, &chal->nonce, 
				 &auth->uri, &chal->realm, &aka_cred, method);
	pjsip_auth_create_digest(&auth->response, &chal->nonce, 
    return PJ_SUCCESS;
PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
				       const pj_str_t *nonce,
				       const pj_str_t *nc,
				       const pj_str_t *cnonce,
				       const pj_str_t *qop,
				       const pj_str_t *uri,
				       const pj_str_t *realm,
				       const pjsip_cred_info *cred_info,
				       const pj_str_t *method)
	pj_assert(cred_info->data.slen == 32);
PJ_DEF(void) pjsip_auth_create_digestSHA256(pj_str_t *result,
	pj_assert(cred_info->data.slen == 32);
		pjsip_auth_create_digestSHA256(
		pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
					  NULL,  NULL, uri, &chal->realm,
					  cred_info, method);
		pjsip_auth_create_digestSHA256(
		pjsip_auth_create_digest( &cred->response, &cred->nonce,
					  &cred->nc, &cred->cnonce,
					  &pjsip_AUTH_STR, uri,
					  &chal->realm, cred_info,
					  method);
    return PJ_SUCCESS;
	pjsip_auth_create_digest(&digest, 
    pj_assert(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata));
Fixed code:  *
 * @return		PJ_SUCCESS on success.
PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result,
					      const pj_str_t *nonce,
					      const pj_str_t *nc,
					      const pj_str_t *cnonce,
					      const pj_str_t *qop,
					      const pj_str_t *uri,
					      const pj_str_t *realm,
					      const pjsip_cred_info *cred_info,
					      const pj_str_t *method);

/**
 * Helper function to create SHA-256 digest out of the specified 
 * parameters.
 *
 * @param result	String to store the response digest. This string
 *			must have been preallocated by caller with the 
 *			buffer at least PJSIP_SHA256STRLEN (64 bytes) in size.
 * @param nonce		Optional nonce.
 * @param nc		Nonce count.
 * @param cnonce	Optional cnonce.
 * @param qop		Optional qop.
 * @param uri		URI.
 * @param realm		Realm.
 * @param cred_info	Credential info.
 * @param method	SIP method.
 *
 * @return		PJ_SUCCESS on success. 
 */
PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result,
					    const pj_str_t* nonce,
					    const pj_str_t* nc,
					    const pj_str_t* cnonce,
					    const pj_str_t* qop,
					    const pj_str_t* uri,
					    const pj_str_t* realm,
					    const pjsip_cred_info* cred_info,
					    const pj_str_t* method);
    pj_status_t status = PJ_SUCCESS;
	 * A normal MD5 authentication is requested. Fallback to the usual
	status = pjsip_auth_create_digest(&auth->response, &auth->nonce, 
			         &auth->nc, &auth->cnonce, &auth->qop, 
			         &auth->uri, &auth->realm, cred, method);

	return status;
	status = pjsip_auth_create_digest(&auth->response, &chal->nonce, 
				 &auth->uri, &chal->realm, &aka_cred, method);	
	status = pjsip_auth_create_digest(&auth->response, &chal->nonce, 
    return status;
PJ_DEF(pj_status_t) pjsip_auth_create_digest( pj_str_t *result,
					      const pj_str_t *nonce,
					      const pj_str_t *nc,
					      const pj_str_t *cnonce,
					      const pj_str_t *qop,
					      const pj_str_t *uri,
					      const pj_str_t *realm,
					      const pjsip_cred_info *cred_info,
					      const pj_str_t *method)
	if (cred_info->data.slen != 32) {
	    pj_assert(!"Invalid cred_info data length");
	    pj_bzero(result->ptr, result->slen);
	    result->slen = 0;
	    return PJ_EINVAL;
	}
	pj_bzero(result->ptr, result->slen);
	result->slen = 0;
	return PJ_EINVAL;
    return PJ_SUCCESS;
PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result,
	if (cred_info->data.slen != 64) {
	    pj_assert(!"Invalid cred_info data length");
	    pj_bzero(result->ptr, result->slen);
	    result->slen = 0;
	    return PJ_EINVAL;
	}
	pj_bzero(result->ptr, result->slen);
	result->slen = 0;
	return PJ_EINVAL;
    return PJ_SUCCESS;
    pj_status_t status = PJ_SUCCESS;
		status = pjsip_auth_create_digestSHA256(
		status = pjsip_auth_create_digest( &cred->response, 
					  &cred->nonce, NULL, NULL, NULL, uri, 
					  &chal->realm, cred_info, method);
		status = pjsip_auth_create_digestSHA256(
		status = pjsip_auth_create_digest( &cred->response, 
					  &cred->nonce, &cred->nc, 
				          &cred->cnonce, &pjsip_AUTH_STR, 
					  uri, &chal->realm, 
					  cred_info, method);
    return status;
	pj_status_t status;
	status = pjsip_auth_create_digest(&digest, 
	if (status != PJ_SUCCESS)
	    return status;

    PJ_ASSERT_RETURN(tp_id >= 0 && 
		     tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata), );