CVE-2021-46822: Out-of-bounds Write (CWE-787)
value: The PPM reader in libjpeg-turbo through 2.0.90 mishandles use of tjLoadImage for loading a 16-bit binary PPM file into a grayscale buffer and loading a 16-bit binary PGM file into an RGB buffer. This is related to a heap-based buffer overflow in the get_word_rgb_row function in rdppm.c.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 5.5
Published: 2022-06-18T16:15Z
Repository: https://github.com/libjpeg-turbo/libjpeg-turbo
Language: C
Files changed: ChangeLog.md, rdppm.c
Commit date: 2021-04-06 16:12:29 -0500
Commit message: tjLoadImage: Fix issues w/loading 16-bit PPMs/PGMs

- The PPM reader now throws an error rather than segfaulting (due to a
  buffer overrun) if an application attempts to load a 16-bit PPM file
  into a grayscale uncompressed image buffer.  No known applications
  allowed that (not even the test applications in libjpeg-turbo),
  because that mode of operation was never expected to work and did not
  work under any circumstances.  (In fact, it was necessary to modify
  TJBench in order to reproduce the issue outside of a fuzzing
  environment.)  This was purely a matter of making the library bow out
  gracefully rather than crash if an application tries to do something
  really stupid.

- The PPM reader now throws an error rather than generating incorrect
  pixels if an application attempts to load a 16-bit PGM file into an
  RGB uncompressed image buffer.

- The PPM reader now correctly loads 16-bit PPM files into extended
  RGB uncompressed image buffers.  (Previously it generated incorrect
  pixels unless the input colorspace was JCS_RGB or JCS_EXT_RGB.)

The only way that users could have potentially encountered these issues
was through the tjLoadImage() function.  cjpeg and TJBench were
unaffected.

Diff: diff --git a/ChangeLog.md b/ChangeLog.md
index 968969c6b..12e730a0e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -33,10 +33,20 @@ and transform functions/operations to return/throw an error if a progressive
 JPEG image contains an unreasonably large number of scans.  This allows
 applications that use the TurboJPEG API to guard against an exploit of the
 progressive JPEG format described in the report
 ["Two Issues with the JPEG Standard"](https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf).
 
+7. The PPM reader now throws an error, rather than segfaulting (due to a buffer
+overrun) or generating incorrect pixels, if an application attempts to use the
+`tjLoadImage()` function to load a 16-bit binary PPM file (a binary PPM file
+with a maximum value greater than 255) into a grayscale image buffer or to load
+a 16-bit binary PGM file into an RGB image buffer.
+
+8. Fixed an issue in the PPM reader that caused incorrect pixels to be
+generated when using the `tjLoadImage()` function to load a 16-bit binary PPM
+file into an extended RGB image buffer.
+
 
 2.0.90 (2.1 beta1)
 ==================
 
 ### Significant changes relative to 2.0.6:
diff --git a/rdppm.c b/rdppm.c
index c4c937e8a..6ac8fdbf7 100644
--- a/rdppm.c
+++ b/rdppm.c
@@ -3,11 +3,11 @@
  *
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2009 by Bill Allombert, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015-2017, 2020, D. R. Commander.
+ * Copyright (C) 2015-2017, 2020-2021, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
  * This file contains routines to read input images in PPM/PGM format.
  * The extended 2-byte-per-sample raw PPM/PGM formats are supported.
@@ -514,10 +514,15 @@ get_word_rgb_row(j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
   register JSAMPROW ptr;
   register U_CHAR *bufferptr;
   register JSAMPLE *rescale = source->rescale;
   JDIMENSION col;
   unsigned int maxval = source->maxval;
+  register int rindex = rgb_red[cinfo->in_color_space];
+  register int gindex = rgb_green[cinfo->in_color_space];
+  register int bindex = rgb_blue[cinfo->in_color_space];
+  register int aindex = alpha_index[cinfo->in_color_space];
+  register int ps = rgb_pixelsize[cinfo->in_color_space];
 
   if (!ReadOK(source->pub.input_file, source->iobuffer, source->buffer_width))
     ERREXIT(cinfo, JERR_INPUT_EOF);
   ptr = source->pub.buffer[0];
   bufferptr = source->iobuffer;
@@ -525,21 +530,24 @@ get_word_rgb_row(j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
     register unsigned int temp;
     temp  = UCH(*bufferptr++) << 8;
     temp |= UCH(*bufferptr++);
     if (temp > maxval)
       ERREXIT(cinfo, JERR_PPM_OUTOFRANGE);
-    *ptr++ = rescale[temp];
+    ptr[rindex] = rescale[temp];
     temp  = UCH(*bufferptr++) << 8;
     temp |= UCH(*bufferptr++);
     if (temp > maxval)
       ERREXIT(cinfo, JERR_PPM_OUTOFRANGE);
-    *ptr++ = rescale[temp];
+    ptr[gindex] = rescale[temp];
     temp  = UCH(*bufferptr++) << 8;
     temp |= UCH(*bufferptr++);
     if (temp > maxval)
       ERREXIT(cinfo, JERR_PPM_OUTOFRANGE);
-    *ptr++ = rescale[temp];
+    ptr[bindex] = rescale[temp];
+    if (aindex >= 0)
+      ptr[aindex] = 0xFF;
+    ptr += ps;
   }
   return 1;
 }
 
 
@@ -622,11 +630,14 @@ start_input_ppm(j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
   case '5':                     /* it's a raw-format PGM file */
     if (cinfo->in_color_space == JCS_UNKNOWN)
       cinfo->in_color_space = JCS_GRAYSCALE;
     TRACEMS2(cinfo, 1, JTRC_PGM, w, h);
     if (maxval > 255) {
-      source->pub.get_pixel_rows = get_word_gray_row;
+      if (cinfo->in_color_space == JCS_GRAYSCALE)
+        source->pub.get_pixel_rows = get_word_gray_row;
+      else
+        ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
     } else if (maxval == MAXJSAMPLE && sizeof(JSAMPLE) == sizeof(U_CHAR) &&
                cinfo->in_color_space == JCS_GRAYSCALE) {
       source->pub.get_pixel_rows = get_raw_row;
       use_raw_buffer = TRUE;
       need_rescale = FALSE;
@@ -645,11 +656,14 @@ start_input_ppm(j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
   case '6':                     /* it's a raw-format PPM file */
     if (cinfo->in_color_space == JCS_UNKNOWN)
       cinfo->in_color_space = JCS_EXT_RGB;
     TRACEMS2(cinfo, 1, JTRC_PPM, w, h);
     if (maxval > 255) {
-      source->pub.get_pixel_rows = get_word_rgb_row;
+      if (IsExtRGB(cinfo->in_color_space))
+        source->pub.get_pixel_rows = get_word_rgb_row;
+      else
+        ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
     } else if (maxval == MAXJSAMPLE && sizeof(JSAMPLE) == sizeof(U_CHAR) &&
 #if RGB_RED == 0 && RGB_GREEN == 1 && RGB_BLUE == 2 && RGB_PIXELSIZE == 3
                (cinfo->in_color_space == JCS_EXT_RGB ||
                 cinfo->in_color_space == JCS_RGB)) {
 #else

Vulnerable code:  * Copyright (C) 2015-2017, 2020, D. R. Commander.
    *ptr++ = rescale[temp];
    *ptr++ = rescale[temp];
    *ptr++ = rescale[temp];
      source->pub.get_pixel_rows = get_word_gray_row;
      source->pub.get_pixel_rows = get_word_rgb_row;
Fixed code: 7. The PPM reader now throws an error, rather than segfaulting (due to a buffer
overrun) or generating incorrect pixels, if an application attempts to use the
`tjLoadImage()` function to load a 16-bit binary PPM file (a binary PPM file
with a maximum value greater than 255) into a grayscale image buffer or to load
a 16-bit binary PGM file into an RGB image buffer.

8. Fixed an issue in the PPM reader that caused incorrect pixels to be
generated when using the `tjLoadImage()` function to load a 16-bit binary PPM
file into an extended RGB image buffer.

 * Copyright (C) 2015-2017, 2020-2021, D. R. Commander.
  register int rindex = rgb_red[cinfo->in_color_space];
  register int gindex = rgb_green[cinfo->in_color_space];
  register int bindex = rgb_blue[cinfo->in_color_space];
  register int aindex = alpha_index[cinfo->in_color_space];
  register int ps = rgb_pixelsize[cinfo->in_color_space];
    ptr[rindex] = rescale[temp];
    ptr[gindex] = rescale[temp];
    ptr[bindex] = rescale[temp];
    if (aindex >= 0)
      ptr[aindex] = 0xFF;
    ptr += ps;
      if (cinfo->in_color_space == JCS_GRAYSCALE)
        source->pub.get_pixel_rows = get_word_gray_row;
      else
        ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
      if (IsExtRGB(cinfo->in_color_space))
        source->pub.get_pixel_rows = get_word_rgb_row;
      else
        ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);