CVE-2022-39244: 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. In versions of PJSIP prior to 2.13 the PJSIP parser, PJMEDIA RTP decoder, and PJMEDIA SDP parser are affeced by a buffer overflow vulnerability. Users connecting to untrusted clients are at risk. This issue has been patched and is available as commit c4d3498 in the master branch and will be included in releases 2.13 and later. Users are advised to upgrade. There are no known workarounds for this issue.
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-10-06T18:16Z
Repository: https://github.com/pjsip/pjproject
Language: C
Files changed: pjlib-util/src/pjlib-util/scanner.c, pjmedia/src/pjmedia/rtp.c, pjmedia/src/pjmedia/sdp.c
Commit date: 2022-10-03 08:07:22 +0800
Commit message: Merge pull request from GHSA-fq45-m3f7-3mhj

* Initial patch

* Use 'pj_scan_is_eof(scanner)'

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Use 'pj_scan_is_eof(scanner)'

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Use 'pj_scan_is_eof(scanner)'

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Use `!pj_scan_is_eof` instead of manually checking `scanner->curptr < scanner->end`

Co-authored-by: Maksim Mukosey <mmukosey@gmail.com>

* Update pjlib-util/src/pjlib-util/scanner.c

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Update pjlib-util/src/pjlib-util/scanner.c

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Update pjlib-util/src/pjlib-util/scanner.c

Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>

* Revert '>=' back to '>' in pj_scan_stricmp_alnum()

* Fix error compiles.

Co-authored-by: Nanang Izzuddin <nanang@teluu.com>
Co-authored-by: Aaron Lichtman <aaronlichtman@gmail.com>
Co-authored-by: Maksim Mukosey <mmukosey@gmail.com>
Diff: diff --git a/pjlib-util/src/pjlib-util/scanner.c b/pjlib-util/src/pjlib-util/scanner.c
index a54edf2d8e..6541bbae31 100644
--- a/pjlib-util/src/pjlib-util/scanner.c
+++ b/pjlib-util/src/pjlib-util/scanner.c
@@ -193,11 +193,17 @@ PJ_DEF(void) pj_scan_skip_whitespace( pj_scanner *scanner )
     }
 }
 
 PJ_DEF(void) pj_scan_skip_line( pj_scanner *scanner )
 {
-    char *s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr);
+    char *s;
+
+    if (pj_scan_is_eof(scanner)) {
+        return;
+    }
+
+    s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr);
     if (!s) {
 	scanner->curptr = scanner->end;
     } else {
 	scanner->curptr = scanner->start_line = s+1;
 	scanner->line++;
@@ -262,12 +268,11 @@ PJ_DEF(void) pj_scan_get( pj_scanner *scanner,
 {
     register char *s = scanner->curptr;
 
     pj_assert(pj_cis_match(spec,0)==0);
 
-    /* EOF is detected implicitly */
-    if (!pj_cis_match(spec, *s)) {
+    if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s)) {
 	pj_scan_syntax_err(scanner);
 	return;
     }
 
     do {
@@ -297,12 +302,11 @@ PJ_DEF(void) pj_scan_get_unescape( pj_scanner *scanner,
     pj_assert(pj_cis_match(spec,0)==0);
 
     /* Must not match character '%' */
     pj_assert(pj_cis_match(spec,'%')==0);
 
-    /* EOF is detected implicitly */
-    if (!pj_cis_match(spec, *s) && *s != '%') {
+    if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s) && *s != '%') {
 	pj_scan_syntax_err(scanner);
 	return;
     }
 
     out->ptr = s;
@@ -434,11 +438,13 @@ PJ_DEF(void) pj_scan_get_n( pj_scanner *scanner,
 
     pj_strset(out, scanner->curptr, N);
     
     scanner->curptr += N;
 
-    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
+    if (!pj_scan_is_eof(scanner) &&
+	PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws)
+    {
 	pj_scan_skip_whitespace(scanner);
     }
 }
 
 
@@ -465,19 +471,20 @@ PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
 }
 
 
 PJ_DEF(void) pj_scan_get_newline( pj_scanner *scanner )
 {
-    if (!PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
+    if (pj_scan_is_eof(scanner) || !PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
 	pj_scan_syntax_err(scanner);
 	return;
     }
 
+    /* We have checked scanner->curptr validity above */
     if (*scanner->curptr == '\r') {
 	++scanner->curptr;
     }
-    if (*scanner->curptr == '\n') {
+    if (!pj_scan_is_eof(scanner) && *scanner->curptr == '\n') {
 	++scanner->curptr;
     }
 
     ++scanner->line;
     scanner->start_line = scanner->curptr;
@@ -518,11 +525,13 @@ PJ_DEF(void) pj_scan_get_until( pj_scanner *scanner,
 
     pj_strset3(out, scanner->curptr, s);
 
     scanner->curptr = s;
 
-    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
+	scanner->skip_ws)
+    {
 	pj_scan_skip_whitespace(scanner);
     }
 }
 
 
@@ -542,11 +551,13 @@ PJ_DEF(void) pj_scan_get_until_ch( pj_scanner *scanner,
 
     pj_strset3(out, scanner->curptr, s);
 
     scanner->curptr = s;
 
-    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
+	scanner->skip_ws)
+    {
 	pj_scan_skip_whitespace(scanner);
     }
 }
 
 
@@ -568,11 +579,13 @@ PJ_DEF(void) pj_scan_get_until_chr( pj_scanner *scanner,
 
     pj_strset3(out, scanner->curptr, s);
 
     scanner->curptr = s;
 
-    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
+	scanner->skip_ws)
+    {
 	pj_scan_skip_whitespace(scanner);
     }
 }
 
 PJ_DEF(void) pj_scan_advance_n( pj_scanner *scanner,
@@ -583,11 +596,13 @@ PJ_DEF(void) pj_scan_advance_n( pj_scanner *scanner,
 	return;
     }
 
     scanner->curptr += N;
 
-    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws) {
+    if (!pj_scan_is_eof(scanner) && 
+	PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws)
+    {
 	pj_scan_skip_whitespace(scanner);
     }
 }
 
 
@@ -634,7 +649,5 @@ PJ_DEF(void) pj_scan_restore_state( pj_scanner *scanner,
 {
     scanner->curptr = state->curptr;
     scanner->line = state->line;
     scanner->start_line = state->start_line;
 }
-
-
diff --git a/pjmedia/src/pjmedia/rtp.c b/pjmedia/src/pjmedia/rtp.c
index 18917f18b5..d29348cc5f 100644
--- a/pjmedia/src/pjmedia/rtp.c
+++ b/pjmedia/src/pjmedia/rtp.c
@@ -186,10 +186,15 @@ 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));
 
+    /* Check that offset is less than packet size */
+    if (offset >= pkt_len) {
+        return PJMEDIA_RTP_EINLEN;
+    }
+
     /* Decode RTP extension. */
     if ((*hdr)->x) {
         if (offset + sizeof (pjmedia_rtp_ext_hdr) > (unsigned)pkt_len)
             return PJMEDIA_RTP_EINLEN;
         dec_hdr->ext_hdr = (pjmedia_rtp_ext_hdr*)(((pj_uint8_t*)pkt) + offset);
@@ -200,12 +205,12 @@ PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp2(
 	dec_hdr->ext_hdr = NULL;
 	dec_hdr->ext = NULL;
 	dec_hdr->ext_len = 0;
     }
 
-    /* Check that offset is less than packet size */
-    if (offset > pkt_len)
+    /* Check again that offset is still less than packet size */
+    if (offset >= pkt_len)
 	return PJMEDIA_RTP_EINLEN;
 
     /* Find and set payload. */
     *payload = ((pj_uint8_t*)pkt) + offset;
     *payloadlen = pkt_len - offset;
@@ -391,7 +396,5 @@ void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *sess,
     if (seq_status) {
 	seq_status->diff = st.diff;
 	seq_status->status.value = st.status.value;
     }
 }
-
-
diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c
index 3905c2f525..647f49e138 100644
--- a/pjmedia/src/pjmedia/sdp.c
+++ b/pjmedia/src/pjmedia/sdp.c
@@ -981,17 +981,17 @@ static void parse_version(pj_scanner *scanner,
                           volatile parse_context *ctx)
 {
     ctx->last_error = PJMEDIA_SDP_EINVER;
 
     /* check equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* check version is 0 */
-    if (*(scanner->curptr+2) != '0') {
+    if (scanner->curptr+2 >= scanner->end || *(scanner->curptr+2) != '0') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* We've got what we're looking for, skip anything until newline */
@@ -1004,11 +1004,11 @@ static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
     pj_str_t str;
 
     ctx->last_error = PJMEDIA_SDP_EINORIGIN;
 
     /* check equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* o= */
@@ -1050,11 +1050,11 @@ static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
     pj_str_t str;
 
     ctx->last_error = PJMEDIA_SDP_EINTIME;
 
     /* check equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* t= */
@@ -1078,11 +1078,11 @@ static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
 			       volatile parse_context *ctx)
 {
     ctx->last_error = PJMEDIA_SDP_EINSDP;
 
     /* check equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if ((scanner->curptr+1 >= scanner->end) || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* x= */
@@ -1147,11 +1147,11 @@ static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
     pj_str_t str;
 
     ctx->last_error = PJMEDIA_SDP_EINMEDIA;
 
     /* check the equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return;
     }
 
     /* m= */
@@ -1162,10 +1162,14 @@ static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
     pj_scan_get_char(scanner);
 
     /* port */
     pj_scan_get(scanner, &cs_token, &str);
     med->desc.port = (unsigned short)pj_strtoul(&str);
+    if (pj_scan_is_eof(scanner)) {
+        on_scanner_error(scanner);
+        return;
+    }
     if (*scanner->curptr == '/') {
 	/* port count */
 	pj_scan_get_char(scanner);
 	pj_scan_get(scanner, &cs_token, &str);
 	med->desc.port_count = pj_strtoul(&str);
@@ -1173,19 +1177,19 @@ static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
     } else {
 	med->desc.port_count = 0;
     }
 
     if (pj_scan_get_char(scanner) != ' ') {
-	PJ_THROW(SYNTAX_ERROR);
+	on_scanner_error(scanner);
     }
 
     /* transport */
     pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport);
 
     /* format list */
     med->desc.fmt_count = 0;
-    while (*scanner->curptr == ' ') {
+    while (scanner->curptr < scanner->end && *scanner->curptr == ' ') {
 	pj_str_t fmt;
 
 	pj_scan_get_char(scanner);
 
 	/* Check again for the end of the line */
@@ -1221,11 +1225,11 @@ static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner,
     ctx->last_error = PJMEDIA_SDP_EINATTR;
 
     attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
 
     /* check equal sign */
-    if (*(scanner->curptr+1) != '=') {
+    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
 	on_scanner_error(scanner);
 	return NULL;
     }
 
     /* skip a= */
@@ -1240,11 +1244,11 @@ static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner,
 	/* skip ':' if present. */
 	if (*scanner->curptr == ':')
 	    pj_scan_get_char(scanner);
 
 	/* get value */
-	if (*scanner->curptr != '\r' && *scanner->curptr != '\n') {
+	if (!pj_scan_is_eof(scanner) && *scanner->curptr != '\r' && *scanner->curptr != '\n') {
 	    pj_scan_get_until_chr(scanner, "\r\n", &attr->value);
 	} else {
 	    attr->value.ptr = NULL;
 	    attr->value.slen = 0;
 	}

Vulnerable code:     char *s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr);
    /* EOF is detected implicitly */
    if (!pj_cis_match(spec, *s)) {
    /* EOF is detected implicitly */
    if (!pj_cis_match(spec, *s) && *s != '%') {
    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
    if (!PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
    if (*scanner->curptr == '\n') {
    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws) {


    /* Check that offset is less than packet size */
    if (offset > pkt_len)


    if (*(scanner->curptr+1) != '=') {
    if (*(scanner->curptr+2) != '0') {
    if (*(scanner->curptr+1) != '=') {
    if (*(scanner->curptr+1) != '=') {
    if (*(scanner->curptr+1) != '=') {
    if (*(scanner->curptr+1) != '=') {
	PJ_THROW(SYNTAX_ERROR);
    while (*scanner->curptr == ' ') {
    if (*(scanner->curptr+1) != '=') {
	if (*scanner->curptr != '\r' && *scanner->curptr != '\n') {
Fixed code:     char *s;

    if (pj_scan_is_eof(scanner)) {
        return;
    }

    s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr);
    if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s)) {
    if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s) && *s != '%') {
    if (!pj_scan_is_eof(scanner) &&
	PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws)
    {
    if (pj_scan_is_eof(scanner) || !PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
    /* We have checked scanner->curptr validity above */
    if (!pj_scan_is_eof(scanner) && *scanner->curptr == '\n') {
    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
	scanner->skip_ws)
    {
    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
	scanner->skip_ws)
    {
    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
	scanner->skip_ws)
    {
    if (!pj_scan_is_eof(scanner) && 
	PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws)
    {
    /* Check that offset is less than packet size */
    if (offset >= pkt_len) {
        return PJMEDIA_RTP_EINLEN;
    }

    /* Check again that offset is still less than packet size */
    if (offset >= pkt_len)
    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
    if (scanner->curptr+2 >= scanner->end || *(scanner->curptr+2) != '0') {
    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
    if ((scanner->curptr+1 >= scanner->end) || *(scanner->curptr+1) != '=') {
    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
    if (pj_scan_is_eof(scanner)) {
        on_scanner_error(scanner);
        return;
    }
	on_scanner_error(scanner);
    while (scanner->curptr < scanner->end && *scanner->curptr == ' ') {
    if (scanner->curptr+1 >= scanner->end || *(scanner->curptr+1) != '=') {
	if (!pj_scan_is_eof(scanner) && *scanner->curptr != '\r' && *scanner->curptr != '\n') {