CVE-2022-24792 (published 2022-04-25T16:16Z)

PJSIP is a free and open source multimedia communication library written in C. A denial-of-service vulnerability affects applications on a 32-bit systems that use PJSIP versions 2.12 and prior to play/read invalid WAV files. The vulnerability occurs when reading WAV file data chunks with length greater than 31-bit integers. The vulnerability does not affect 64-bit apps and should not affect apps that only plays trusted WAV files. A patch is available on the `master` branch of the `pjsip/project` GitHub repository. As a workaround, apps can reject a WAV file received from an unknown source or validate the file first.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-835
cwe_name: Loop with Unreachable Exit Condition ('Infinite Loop')
description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
extended_description: If the loop can be influenced by an attacker, this weakness could allow attackers to consume excessive resources such as CPU or memory.
         

Fix information:
repo_url: https://github.com/pjsip/pjproject
commit_details: repo_url: https://github.com/pjsip/pjproject
author: Riza Sulistyo
author_date: 2022-04-25 11:54:19+07:00
msg: Merge pull request from GHSA-rwgw-vwxg-q799

* Prevent potential infinite loop when parsing WAV format file

* Check if subchunk is negative.

* Fix and add checks

* Change data type from pj_ssize_t to long.

* Modify check

* Fix leak file descriptor and modify check on wav_playlist

* Move overflow/underflow check to pj_file_setpos()

* Use macro to simplify check

* modification based on comments

* Remove unnecessary casting

* Modification based on comments
repository: repo_url: https://github.com/pjsip/pjproject
repo_name: pjsip/pjproject
description: PJSIP project
repo_language: C
owner: pjsip
file_changes: filename: types.h
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,7 @@
  * @{
  */
 #include <pj/config.h>
+#include <pj/limits.h>
 
 PJ_BEGIN_DECL
 
@@ -361,6 +362,13 @@ PJ_INLINE(pj_int32_t) pj_swap32(pj_int32_t val32)
     return val32;
 }
 
+/* This is to check if uint32 var will overflow if converted to signed long */
+#define PJ_CHECK_OVERFLOW_UINT32_TO_LONG(uint32_var, exec_on_overflow) \
+	do { \
+	    if (uint32_var > PJ_MAXLONG) { \
+		exec_on_overflow; \
+	    } \
+	} while (0)
 
 /**
  * @}

programming_language: C
filename: file_io_ansi.c
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@
 #include <pj/file_io.h>
 #include <pj/assert.h>
 #include <pj/errno.h>
+#include <pj/limits.h>
 #include <stdio.h>
 #include <errno.h>
 
@@ -124,6 +125,12 @@ PJ_DEF(pj_status_t) pj_file_setpos( pj_oshandle_t fd,
 {
     int mode;
 
+    if ((sizeof(pj_off_t) > sizeof(long)) &&
+        (offset > PJ_MAXLONG || offset < PJ_MINLONG)) 
+    {
+        return PJ_ENOTSUP;
+    }
+
     switch (whence) {
     case PJ_SEEK_SET:
         mode = SEEK_SET; break;

programming_language: C
filename: avi_player.c
change_type: ModificationType.MODIFY
diff: @@ -282,7 +282,7 @@ pjmedia_avi_player_create_streams(pj_pool_t *pool,
     /* Read the headers of each stream. */
     for (i = 0; i < avi_hdr.avih_hdr.num_streams; i++) {
         pj_size_t elem = 0;
-        pj_ssize_t size_to_read;
+        pj_off_t size_to_read;
 
         /* Read strl header */
         status = file_read(fport[0]->fd, &avi_hdr.strl_hdr[i],
@@ -335,6 +335,7 @@ pjmedia_avi_player_create_streams(pj_pool_t *pool,
     do {
         pjmedia_avi_subchunk ch;
         int read = 0;
+        pj_off_t size_to_read;
 
         status = file_read(fport[0]->fd, &ch, sizeof(pjmedia_avi_subchunk));
         if (status != PJ_SUCCESS) {
@@ -349,7 +350,15 @@ pjmedia_avi_player_create_streams(pj_pool_t *pool,
                 break;
         }
 
-        status = pj_file_setpos(fport[0]->fd, ch.len-read, PJ_SEEK_CUR);
+        if (ch.len < read) {
+            status = PJ_EINVAL;
+            goto on_error;
+        }
+        PJ_CHECK_OVERFLOW_UINT32_TO_LONG(ch.len - read, 
+                                         status = PJ_EINVAL; goto on_error;);
+        size_to_read = (pj_off_t)ch.len - read;
+
+        status = pj_file_setpos(fport[0]->fd, size_to_read, PJ_SEEK_CUR);
         if (status != PJ_SUCCESS) {
             goto on_error;
         }
@@ -775,6 +784,8 @@ static pj_status_t avi_get_frame(pjmedia_port *this_port,
         /* Read new chunk data */
         if (fport->size_left == 0) {
             pj_off_t pos;
+            pj_off_t ch_len;
+
             pj_file_getpos(fport->fd, &pos);
 
             /* Data is padded to the nearest WORD boundary */
@@ -788,6 +799,10 @@ static pj_status_t avi_get_frame(pjmedia_port *this_port,
                 size_read = 0;
                 goto on_error2;
             }
+            
+            PJ_CHECK_OVERFLOW_UINT32_TO_LONG(ch.len, 
+                                         status = PJ_EINVAL;  goto on_error2;);
+            ch_len = ch.len;
 
             cid = (char *)&ch.id;
             if (cid[0] >= '0' && cid[0] <= '9' &&
@@ -814,8 +829,7 @@ static pj_status_t avi_get_frame(pjmedia_port *this_port,
                     goto on_error2;
                 }
 
-                status = pj_file_setpos(fport->fd, ch.len,
-                                        PJ_SEEK_CUR);
+                status = pj_file_setpos(fport->fd, ch_len, PJ_SEEK_CUR);
                 continue;
             }
             fport->size_left = ch.len;

programming_language: C
filename: wav_player.c
change_type: ModificationType.MODIFY
diff: @@ -188,7 +188,8 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
 						     pjmedia_port **p_port )
 {
     pjmedia_wave_hdr wave_hdr;
-    pj_ssize_t size_to_read, size_read;
+    pj_ssize_t size_read;
+    pj_off_t size_to_read;
     struct file_reader_port *fport;
     pjmedia_audio_format_detail *ad;
     pj_off_t pos;
@@ -234,7 +235,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
 	return status;
 
     /* Read the file header plus fmt header only. */
-    size_read = size_to_read = sizeof(wave_hdr) - 8;
+    size_to_read = size_read = sizeof(wave_hdr) - 8;
     status = pj_file_read( fport->fd, &wave_hdr, &size_read);
     if (status != PJ_SUCCESS) {
 	pj_file_close(fport->fd);
@@ -297,7 +298,9 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
      * fmt header data.
      */
     if (wave_hdr.fmt_hdr.len > 16) {
-	size_to_read = wave_hdr.fmt_hdr.len - 16;
+	PJ_CHECK_OVERFLOW_UINT32_TO_LONG(wave_hdr.fmt_hdr.len - 16,
+		      pj_file_close(fport->fd); return PJMEDIA_ENOTVALIDWAVE;);
+	size_to_read = (pj_off_t)wave_hdr.fmt_hdr.len - 16;
 	status = pj_file_setpos(fport->fd, size_to_read, PJ_SEEK_CUR);
 	if (status != PJ_SUCCESS) {
 	    pj_file_close(fport->fd);
@@ -326,7 +329,10 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
 	}
 
 	/* Otherwise skip the chunk contents */
+	PJ_CHECK_OVERFLOW_UINT32_TO_LONG(subchunk.len, 
+		      pj_file_close(fport->fd); return PJMEDIA_ENOTVALIDWAVE;);
 	size_to_read = subchunk.len;
+
 	status = pj_file_setpos(fport->fd, size_to_read, PJ_SEEK_CUR);
 	if (status != PJ_SUCCESS) {
 	    pj_file_close(fport->fd);

programming_language: C
filename: wav_playlist.c
change_type: ModificationType.MODIFY
diff: @@ -419,7 +419,8 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool,
     for (index=file_count-1; index>=0; index--) {
 
 	pjmedia_wave_hdr wavehdr;
-	pj_ssize_t size_to_read, size_read;
+	pj_ssize_t size_read;
+	pj_off_t size_to_read;
 
 	/* we end with the last one so we are good to go if still in function*/
 	pj_memcpy(filename, file_list[index].ptr, file_list[index].slen);
@@ -442,7 +443,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool,
 	    goto on_error;
 	
 	/* Read the file header plus fmt header only. */
-	size_read = size_to_read = sizeof(wavehdr) - 8;
+	size_to_read = size_read = sizeof(wavehdr) - 8;	
 	status = pj_file_read( fport->fd_list[index], &wavehdr, &size_read);
 	if (status != PJ_SUCCESS) {
 	    goto on_error;
@@ -492,7 +493,9 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool,
 	 * fmt header data.
 	 */
 	if (wavehdr.fmt_hdr.len > 16) {
-	    size_to_read = wavehdr.fmt_hdr.len - 16;
+	    PJ_CHECK_OVERFLOW_UINT32_TO_LONG(wavehdr.fmt_hdr.len-16,
+			       status = PJMEDIA_ENOTVALIDWAVE; goto on_error;);
+	    size_to_read = (pj_off_t)wavehdr.fmt_hdr.len - 16;
 	    status = pj_file_setpos(fport->fd_list[index], size_to_read, 
 				    PJ_SEEK_CUR);
 	    if (status != PJ_SUCCESS) {
@@ -522,7 +525,10 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool,
 	    }
 	    
 	    /* Otherwise skip the chunk contents */
+	    PJ_CHECK_OVERFLOW_UINT32_TO_LONG(subchunk.len, 
+			       status = PJMEDIA_ENOTVALIDWAVE; goto on_error;);
 	    size_to_read = subchunk.len;
+
 	    status = pj_file_setpos(fport->fd_list[index], size_to_read, 
 				    PJ_SEEK_CUR);
 	    if (status != PJ_SUCCESS) {

programming_language: C