CVE-2022-21722: Out-of-bounds Read (CWE-125)
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 version 2.11.1 and prior, there are various cases where it is possible that certain incoming RTP/RTCP packets can potentially cause out-of-bound read access. This issue affects all users that use PJMEDIA and accept incoming RTP/RTCP. A patch is available as a commit in the `master` branch. There are no known workarounds.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 9.1
Published: 2022-01-27T00:15Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjmedia/src/pjmedia/rtcp.c, pjmedia/src/pjmedia/rtcp_fb.c, pjmedia/src/pjmedia/rtp.c
Commit date: 2022-01-26 08:03:48 +0800
Commit message: Merge pull request from GHSA-m66q-q64c-hv36

* Prevent OOB read during RTP/RTCP parsing

* Add log

* Add more logs
Diff: diff --git a/pjmedia/src/pjmedia/rtcp.c b/pjmedia/src/pjmedia/rtcp.c
index f675724579..28d5ad177e 100644
--- a/pjmedia/src/pjmedia/rtcp.c
+++ b/pjmedia/src/pjmedia/rtcp.c
@@ -500,16 +500,26 @@ static void parse_rtcp_report( pjmedia_rtcp_session *sess,
     const pjmedia_rtcp_sr *sr = NULL;
     pj_uint32_t last_loss, jitter_samp, jitter;
 
     /* Parse RTCP */
     if (common->pt == RTCP_SR) {
+        if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_sr) > size) {
+	    TRACE_((sess->name, "Discarding RTCP SR due to truncated size "
+	    			"%d bytes", size));
+            return;
+        }
 	sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common));
 	if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) {
 	    rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common)
 				    + sizeof(pjmedia_rtcp_sr)));
 	}
     } else if (common->pt == RTCP_RR && common->count > 0) {
+	if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_rr) > size) {
+	    TRACE_((sess->name, "Discarding RTCP RR due to truncated size "
+	    			"%d bytes", size));
+	    return;
+	}
 	rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common));
 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
     } else if (common->pt == RTCP_XR) {
 	if (sess->xr_enabled)
 	    pjmedia_rtcp_xr_rx_rtcp_xr(&sess->xr_session, pkt, size);
@@ -824,16 +834,24 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess,
     pj_uint8_t *p, *p_end;
 
     p = (pj_uint8_t*)pkt;
     p_end = p + size;
     while (p < p_end) {
-	pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)p;
+	pjmedia_rtcp_common *common;
 	unsigned len;
 
+	if (p + sizeof(pjmedia_rtcp_common) > p_end) {
+	    TRACE_((sess->name, "Receiving truncated RTCP packet (1)"));
+	    break;
+	}
+	common = (pjmedia_rtcp_common*)p;
+
 	len = (pj_ntohs((pj_uint16_t)common->length)+1) * 4;
-	if (p + len > p_end)
+	if (p + len > p_end) {
+	    TRACE_((sess->name, "Receiving truncated RTCP packet (2)"));
 	    break;
+	}
 
 	switch(common->pt) {
 	case RTCP_SR:
 	case RTCP_RR:
 	case RTCP_XR:
diff --git a/pjmedia/src/pjmedia/rtcp_fb.c b/pjmedia/src/pjmedia/rtcp_fb.c
index 0ca7712d8a..21f4e2e8ab 100644
--- a/pjmedia/src/pjmedia/rtcp_fb.c
+++ b/pjmedia/src/pjmedia/rtcp_fb.c
@@ -629,11 +629,12 @@ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_nack(
 
     /* Generic NACK uses pt==RTCP_RTPFB and FMT==1 */
     if (hdr->pt != RTCP_RTPFB || hdr->count != 1)
 	return PJ_ENOTFOUND;
 
-    cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2;
+    cnt = pj_ntohs((pj_uint16_t)hdr->length);
+    if (cnt > 2) cnt -= 2; else cnt = 0;
     if (length < (cnt+3)*4)
 	return PJ_ETOOSMALL;
 
     *nack_cnt = PJ_MIN(*nack_cnt, cnt);
 
@@ -661,11 +662,13 @@ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_pli(
 					pj_size_t length)
 {
     pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf;
 
     PJ_ASSERT_RETURN(buf, PJ_EINVAL);
-    PJ_ASSERT_RETURN(length >= 12, PJ_ETOOSMALL);
+
+    if (length < 12)
+    	return PJ_ETOOSMALL;
 
     /* PLI uses pt==RTCP_PSFB and FMT==1 */
     if (hdr->pt != RTCP_PSFB || hdr->count != 1)
 	return PJ_ENOTFOUND;
 
diff --git a/pjmedia/src/pjmedia/rtp.c b/pjmedia/src/pjmedia/rtp.c
index 78f0c06581..d052aa8545 100644
--- a/pjmedia/src/pjmedia/rtp.c
+++ b/pjmedia/src/pjmedia/rtp.c
@@ -188,10 +188,12 @@ PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp2(
     /* Payload is located right after header plus CSRC */
     offset = sizeof(pjmedia_rtp_hdr) + ((*hdr)->cc * sizeof(pj_uint32_t));
 
     /* Decode RTP extension. */
     if ((*hdr)->x) {
+        if (offset + sizeof (pjmedia_rtp_ext_hdr) > pkt_len)
+            return PJMEDIA_RTP_EINLEN;
         dec_hdr->ext_hdr = (pjmedia_rtp_ext_hdr*)(((pj_uint8_t*)pkt) + offset);
         dec_hdr->ext = (pj_uint32_t*)(dec_hdr->ext_hdr + 1);
         dec_hdr->ext_len = pj_ntohs((dec_hdr->ext_hdr)->length);
         offset += ((dec_hdr->ext_len + 1) * sizeof(pj_uint32_t));
     } else {

Vulnerable code: 	pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)p;
	if (p + len > p_end)
    cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2;
    PJ_ASSERT_RETURN(length >= 12, PJ_ETOOSMALL);
Fixed code:         if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_sr) > size) {
	    TRACE_((sess->name, "Discarding RTCP SR due to truncated size "
	    			"%d bytes", size));
            return;
        }
	if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_rr) > size) {
	    TRACE_((sess->name, "Discarding RTCP RR due to truncated size "
	    			"%d bytes", size));
	    return;
	}
	pjmedia_rtcp_common *common;
	if (p + sizeof(pjmedia_rtcp_common) > p_end) {
	    TRACE_((sess->name, "Receiving truncated RTCP packet (1)"));
	    break;
	}
	common = (pjmedia_rtcp_common*)p;

	if (p + len > p_end) {
	    TRACE_((sess->name, "Receiving truncated RTCP packet (2)"));
	}
    cnt = pj_ntohs((pj_uint16_t)hdr->length);
    if (cnt > 2) cnt -= 2; else cnt = 0;

    if (length < 12)
    	return PJ_ETOOSMALL;
        if (offset + sizeof (pjmedia_rtp_ext_hdr) > pkt_len)
            return PJMEDIA_RTP_EINLEN;