CVE-2022-31620: Reachable Assertion (CWE-617)
value: In libjpeg before 1.64, BitStream<false>::Get in bitstream.hpp has an assertion failure that may cause denial of service. This is related to out-of-bounds array access during arithmetically coded lossless scan or arithmetically coded sequential scan.
CWE description: The product contains an assert() or similar statement that can be triggered by an attacker, which leads to an application exit or other behavior that is more severe than necessary.
CVSS v3 base score: 6.5
Published: 2022-05-25T21:15Z
Repository: https://github.com/thorfdbg/libjpeg
Language: C++
Files changed: README, README.history, codestream/aclosslessscan.cpp, codestream/aclosslessscan.hpp, codestream/acsequentialscan.cpp, codestream/acsequentialscan.hpp, codestream/losslessscan.cpp, codestream/rectanglerequest.hpp
Commit date: 2022-05-23 07:57:40 +0200
Commit message: Added out-of-bounds checks for lossless symbol decoding and AC context
indices. Worked around some gcc warnings. Bumped to 1.64.

Diff: diff --git a/README b/README
index 156d430..40ef320 100644
--- a/README
+++ b/README
@@ -450,10 +450,21 @@ provided, the decoder crashed. This issue was fixed, the alpha channel is
 now in this case simply disregarded. Note that you can define the output
 file for the alpha channel with the "-al" command line option.
 
 --------------------------------------------------------------------------
 
+Release 1.64:
+
+The lossless scan, the arithmetically coded lossless scan and the
+arithmetically coded sequential scan could run into cases where an
+out-of-bounds symbol triggered and out-of-bounds array access and could
+have crashed the decoder. The code is now more carefully changing the
+validity of the symbols and aborts with an error if it finds illegal
+codes.
+
+--------------------------------------------------------------------------
+
 For license conditions, please check the file README.license in this
 directory.
 
 Finally, I want to thank Accusoft and the Computing Center of the University of
 Stuttgart and Fraunhofer IIS for sponsoring this project.
diff --git a/README.history b/README.history
index 2932b45..672d08d 100644
--- a/README.history
+++ b/README.history
@@ -553,5 +553,16 @@ i.e. a 18477-9 image, and no output file for the alpha channel was
 provided, the decoder crashed. This issue was fixed, the alpha channel is
 now in this case simply disregarded. Note that you can define the output
 file for the alpha channel with the "-al" command line option.
 
 --------------------------------------------------------------------------
+
+Release 1.64:
+
+The lossless scan, the arithmetically coded lossless scan and the
+arithmetically coded sequential scan could run into cases where an
+out-of-bounds symbol triggered and out-of-bounds array access and could
+have crashed the decoder. The code is now more carefully changing the
+validity of the symbols and aborts with an error if it finds illegal
+codes.
+
+--------------------------------------------------------------------------
diff --git a/codestream/aclosslessscan.cpp b/codestream/aclosslessscan.cpp
index fcd8719..cbfd282 100644
--- a/codestream/aclosslessscan.cpp
+++ b/codestream/aclosslessscan.cpp
@@ -40,11 +40,11 @@
 *************************************************************************/
 /*
 **
 ** Represents the scan including the scan header.
 **
-** $Id: aclosslessscan.cpp,v 1.42 2020/08/31 07:50:43 thor Exp $
+** $Id: aclosslessscan.cpp,v 1.43 2022/05/23 05:56:51 thor Exp $
 **
 */
 
 /// Includes
 #include "codestream/aclosslessscan.hpp"
@@ -357,11 +357,13 @@ void ACLosslessScan::ParseMCU(struct Line **prev,struct Line **top)
             int  i = 0;
             LONG m = 2;
             //
             while(m_Coder.Get(mset.X[i])) {
               m <<= 1;
-              i++;
+              if (++i >= QMContextSet::MagnitudeSet::MagnitudeContexts)
+                JPG_THROW(MALFORMED_STREAM,"ACLosslessScan::ParseMCU",
+                          "received an out-of-bounds signal while parsing an AC-coded lossless symbol");
             }
             //
             m >>= 1;
             sz  = m;
             while((m >>= 1)) {
diff --git a/codestream/aclosslessscan.hpp b/codestream/aclosslessscan.hpp
index eccf792..db870df 100644
--- a/codestream/aclosslessscan.hpp
+++ b/codestream/aclosslessscan.hpp
@@ -41,11 +41,11 @@
 /*
 **
 ** Represents the lossless scan - lines are coded directly with predictive
 ** coding, though here residuals are encoded with the arithmetic encoder.
 **
-** $Id: aclosslessscan.hpp,v 1.28 2014/11/16 15:49:58 thor Exp $
+** $Id: aclosslessscan.hpp,v 1.29 2022/05/23 05:56:51 thor Exp $
 **
 */
 
 #ifndef CODESTREAM_ACLOSSLESSSCAN_HPP
 #define CODESTREAM_ACLOSSLESSSCAN_HPP
@@ -109,16 +109,20 @@ class ACLosslessScan : public PredictiveScan {
       }
     } SignZeroCoding[5][5];
     //
     // The Magnitude/refinement coding contexts.
     struct MagnitudeSet {
-      QMContext X[15];
-      QMContext M[15];
+      enum {
+        MagnitudeContexts = 15
+      };
+      //
+      QMContext X[MagnitudeContexts];
+      QMContext M[MagnitudeContexts];
       //
       void Init(void)
       {
-        for(int i = 0;i < 15;i++) {
+        for(int i = 0;i < MagnitudeContexts;i++) {
           X[i].Init();
           M[i].Init();
         }
       }
     } MagnitudeLow,MagnitudeHigh;
diff --git a/codestream/acsequentialscan.cpp b/codestream/acsequentialscan.cpp
index 20cecab..b12db52 100644
--- a/codestream/acsequentialscan.cpp
+++ b/codestream/acsequentialscan.cpp
@@ -40,11 +40,11 @@
 *************************************************************************/
 /*
 **
 ** Represents the scan including the scan header.
 **
-** $Id: acsequentialscan.cpp,v 1.51 2016/10/28 13:58:53 thor Exp $
+** $Id: acsequentialscan.cpp,v 1.52 2022/05/23 05:56:51 thor Exp $
 **
 */
 
 /// Includes
 #include "codestream/acsequentialscan.hpp"
@@ -580,12 +580,11 @@ void ACSequentialScan::DecodeBlock(LONG *block,
         int  i = 0;
         LONG m = 2;
         
         while(m_Coder.Get(m_Context[dc].DCMagnitude.X[i])) {
           m <<= 1;
-          i++;
-          if (m == 0) 
+          if(++i >= QMContextSet::DCContextMagnitudeSet::MagnitudeContexts)
             JPG_THROW(MALFORMED_STREAM,"ACSequentialScan::DecodeBlock",
                       "QMDecoder is out of sync");
         }
         //
         // Get the MSB to decode.
@@ -651,12 +650,11 @@ void ACSequentialScan::DecodeBlock(LONG *block,
           LONG m = 4;
           struct QMContextSet::ACContextMagnitudeSet &acm = (k > kx)?(m_Context[ac].ACMagnitudeHigh):(m_Context[ac].ACMagnitudeLow);
           
           while(m_Coder.Get(acm.X[i])) {
             m <<= 1;
-            i++;
-            if (m == 0)
+            if(++i >= QMContextSet::ACContextMagnitudeSet::MagnitudeContexts)
               JPG_THROW(MALFORMED_STREAM,"ACSequentialScan::DecodeBlock",
                         "QMDecoder is out of sync");
           }
           //
           // Get the MSB to decode
diff --git a/codestream/acsequentialscan.hpp b/codestream/acsequentialscan.hpp
index fd340fa..fbe7941 100644
--- a/codestream/acsequentialscan.hpp
+++ b/codestream/acsequentialscan.hpp
@@ -41,11 +41,11 @@
 /*
 **
 ** Represents the scan including the scan header for the
 ** arithmetic coding procedure.
 **
-** $Id: acsequentialscan.hpp,v 1.38 2016/10/28 13:58:53 thor Exp $
+** $Id: acsequentialscan.hpp,v 1.39 2022/05/23 05:56:51 thor Exp $
 **
 */
 
 #ifndef CODESTREAM_ACSEQUENTIALSCAN_HPP
 #define CODESTREAM_ACSEQUENTIALSCAN_HPP
@@ -118,17 +118,21 @@ class ACSequentialScan : public EntropyParser {
       //
     } DCZero,DCSmallPositive,DCSmallNegative,DCLargePositive,DCLargeNegative;
     //
     // The DC Magnitude coding contexts.
     struct DCContextMagnitudeSet {
-      QMContext X[19];
-      QMContext M[19];
+      enum {
+        MagnitudeContexts = 19
+      };
+      //
+      QMContext X[MagnitudeContexts];
+      QMContext M[MagnitudeContexts];
       //
       // Initialize
       void Init(void)
       {
-        for(int i = 0;i < 19;i++) {
+        for(int i = 0;i < MagnitudeContexts;i++) {
 #ifdef DEBUG_QMCODER
           char string[5] = "X0  ";
           string[1] = (i / 10) + '0';
           string[2] = (i % 10) + '0';
           X[i].Init(string);
@@ -169,18 +173,21 @@ class ACSequentialScan : public EntropyParser {
 #endif
     } ACZero[63];
     //
     // The AC Magnitude coder.
     struct ACContextMagnitudeSet {
-      QMContext X[18];
-      QMContext M[18];
+      enum {
+        MagnitudeContexts = 18
+      };
+      //
+      QMContext X[MagnitudeContexts];
+      QMContext M[MagnitudeContexts];
       //
-
 #ifdef DEBUG_QMCODER
       void Init(bool hi) 
       {
-        for(int i = 0;i < 18;i++) {
+        for(int i = 0;i < MagnitudeContexts;i++) {
           char string[5] = "xl00";
           string[1] = (hi)?('h'):('l');
           string[2] = (i / 10) + '0';
           string[3] = (i % 10) + '0';
           X[i].Init(string);
@@ -189,11 +196,11 @@ class ACSequentialScan : public EntropyParser {
         }
       }
 #else
       void Init(void)
       {
-        for(int i = 0;i < 18;i++) {
+        for(int i = 0;i < MagnitudeContexts;i++) {
           X[i].Init();
           M[i].Init();
         }
       }
 #endif
diff --git a/codestream/losslessscan.cpp b/codestream/losslessscan.cpp
index a337ffa..75a15a6 100644
--- a/codestream/losslessscan.cpp
+++ b/codestream/losslessscan.cpp
@@ -40,11 +40,11 @@
 *************************************************************************/
 /*
 **
 ** Represents the scan including the scan header.
 **
-** $Id: losslessscan.cpp,v 1.50 2015/03/25 08:45:43 thor Exp $
+** $Id: losslessscan.cpp,v 1.51 2022/05/23 05:56:51 thor Exp $
 **
 */
 
 /// Includes
 #include "codestream/losslessscan.hpp"
@@ -375,10 +375,13 @@ void LosslessScan::ParseMCU(struct Line **prev,struct Line **top)
         
         if (symbol == 0) {
           v = 0;
         } else if (symbol == 16) {
           v = -32768;
+        } else if (symbol > 16) {
+          JPG_THROW(MALFORMED_STREAM,"LosslessScan::ParseMCU",
+                    "received an out-of-bounds symbol in a lossless JPEG scan");
         } else {
           LONG thre = 1L << (symbol - 1);
           LONG diff = m_Stream.Get(symbol); // get the number of bits 
           if (diff < thre) {
             diff += (-1L << symbol) + 1;
diff --git a/codestream/rectanglerequest.hpp b/codestream/rectanglerequest.hpp
index 63af5d0..a2d7c59 100644
--- a/codestream/rectanglerequest.hpp
+++ b/codestream/rectanglerequest.hpp
@@ -40,11 +40,11 @@
 *************************************************************************/
 /*
  * Definition of how to request a given rectangle for display,
  * for load or for checking for a necessary update.
  * 
- * $Id: rectanglerequest.hpp,v 1.12 2017/11/28 13:08:07 thor Exp $
+ * $Id: rectanglerequest.hpp,v 1.13 2022/05/23 05:56:51 thor Exp $
  *
  */
 
 #ifndef CODESTREAM_RECTANGLEREQUEST_HPP
 #define CODESTREAM_RECTANGLEREQUEST_HPP
@@ -103,23 +103,33 @@ struct RectangleRequest : public JObject, private Explicit {
   //
   // Copy constructor.
   RectangleRequest(const struct RectangleRequest &req)
     : Explicit()
   {
-    // Not nice, but this is really faster and simpler
-    memcpy(this,&req,sizeof(struct RectangleRequest));
     // Not linked in any way if this is new.
-    rr_pNext = NULL;
+    rr_pNext            = NULL;
+    rr_Request          = req.rr_Request;
+    rr_usFirstComponent = req.rr_usFirstComponent;
+    rr_usLastComponent  = req.rr_usLastComponent;
+    rr_cPriority        = req.rr_cPriority;
+    rr_bIncludeAlpha    = req.rr_bIncludeAlpha;
+    rr_bUpsampling      = req.rr_bUpsampling;
+    rr_bColorTrafo      = req.rr_bColorTrafo;
   }
   //
   // Assignment operator.
   RectangleRequest &operator=(const struct RectangleRequest &req)
   { 
-    // Not nice, but this is really faster and simpler
-    memcpy(this,&req,sizeof(struct RectangleRequest));
-    // Not linked in any way if this is new.
-    rr_pNext = NULL;
+   // Not linked in any way if this is new.
+    rr_pNext            = NULL;
+    rr_Request          = req.rr_Request;
+    rr_usFirstComponent = req.rr_usFirstComponent;
+    rr_usLastComponent  = req.rr_usLastComponent;
+    rr_cPriority        = req.rr_cPriority;
+    rr_bIncludeAlpha    = req.rr_bIncludeAlpha;
+    rr_bUpsampling      = req.rr_bUpsampling;
+    rr_bColorTrafo      = req.rr_bColorTrafo;
     //
     return *this;
   }
   //
   //

Vulnerable code: ** $Id: aclosslessscan.cpp,v 1.42 2020/08/31 07:50:43 thor Exp $
              i++;
** $Id: aclosslessscan.hpp,v 1.28 2014/11/16 15:49:58 thor Exp $
      QMContext X[15];
      QMContext M[15];
        for(int i = 0;i < 15;i++) {
** $Id: acsequentialscan.cpp,v 1.51 2016/10/28 13:58:53 thor Exp $
          i++;
          if (m == 0) 
            i++;
            if (m == 0)
** $Id: acsequentialscan.hpp,v 1.38 2016/10/28 13:58:53 thor Exp $
      QMContext X[19];
      QMContext M[19];
        for(int i = 0;i < 19;i++) {
      QMContext X[18];
      QMContext M[18];

        for(int i = 0;i < 18;i++) {
        for(int i = 0;i < 18;i++) {
** $Id: losslessscan.cpp,v 1.50 2015/03/25 08:45:43 thor Exp $
 * $Id: rectanglerequest.hpp,v 1.12 2017/11/28 13:08:07 thor Exp $
    // Not nice, but this is really faster and simpler
    memcpy(this,&req,sizeof(struct RectangleRequest));
    rr_pNext = NULL;
    // Not nice, but this is really faster and simpler
    memcpy(this,&req,sizeof(struct RectangleRequest));
    // Not linked in any way if this is new.
    rr_pNext = NULL;
Fixed code: Release 1.64:

The lossless scan, the arithmetically coded lossless scan and the
arithmetically coded sequential scan could run into cases where an
out-of-bounds symbol triggered and out-of-bounds array access and could
have crashed the decoder. The code is now more carefully changing the
validity of the symbols and aborts with an error if it finds illegal
codes.

--------------------------------------------------------------------------


Release 1.64:

The lossless scan, the arithmetically coded lossless scan and the
arithmetically coded sequential scan could run into cases where an
out-of-bounds symbol triggered and out-of-bounds array access and could
have crashed the decoder. The code is now more carefully changing the
validity of the symbols and aborts with an error if it finds illegal
codes.

--------------------------------------------------------------------------
** $Id: aclosslessscan.cpp,v 1.43 2022/05/23 05:56:51 thor Exp $
              if (++i >= QMContextSet::MagnitudeSet::MagnitudeContexts)
                JPG_THROW(MALFORMED_STREAM,"ACLosslessScan::ParseMCU",
                          "received an out-of-bounds signal while parsing an AC-coded lossless symbol");
** $Id: aclosslessscan.hpp,v 1.29 2022/05/23 05:56:51 thor Exp $
      enum {
        MagnitudeContexts = 15
      };
      //
      QMContext X[MagnitudeContexts];
      QMContext M[MagnitudeContexts];
        for(int i = 0;i < MagnitudeContexts;i++) {
** $Id: acsequentialscan.cpp,v 1.52 2022/05/23 05:56:51 thor Exp $
          if(++i >= QMContextSet::DCContextMagnitudeSet::MagnitudeContexts)
            if(++i >= QMContextSet::ACContextMagnitudeSet::MagnitudeContexts)
** $Id: acsequentialscan.hpp,v 1.39 2022/05/23 05:56:51 thor Exp $
      enum {
        MagnitudeContexts = 19
      };
      //
      QMContext X[MagnitudeContexts];
      QMContext M[MagnitudeContexts];
        for(int i = 0;i < MagnitudeContexts;i++) {
      enum {
        MagnitudeContexts = 18
      };
      //
      QMContext X[MagnitudeContexts];
      QMContext M[MagnitudeContexts];
        for(int i = 0;i < MagnitudeContexts;i++) {
        for(int i = 0;i < MagnitudeContexts;i++) {
** $Id: losslessscan.cpp,v 1.51 2022/05/23 05:56:51 thor Exp $
        } else if (symbol > 16) {
          JPG_THROW(MALFORMED_STREAM,"LosslessScan::ParseMCU",
                    "received an out-of-bounds symbol in a lossless JPEG scan");
 * $Id: rectanglerequest.hpp,v 1.13 2022/05/23 05:56:51 thor Exp $
    rr_pNext            = NULL;
    rr_Request          = req.rr_Request;
    rr_usFirstComponent = req.rr_usFirstComponent;
    rr_usLastComponent  = req.rr_usLastComponent;
    rr_cPriority        = req.rr_cPriority;
    rr_bIncludeAlpha    = req.rr_bIncludeAlpha;
    rr_bUpsampling      = req.rr_bUpsampling;
    rr_bColorTrafo      = req.rr_bColorTrafo;
   // Not linked in any way if this is new.
    rr_pNext            = NULL;
    rr_Request          = req.rr_Request;
    rr_usFirstComponent = req.rr_usFirstComponent;
    rr_usLastComponent  = req.rr_usLastComponent;
    rr_cPriority        = req.rr_cPriority;
    rr_bIncludeAlpha    = req.rr_bIncludeAlpha;
    rr_bUpsampling      = req.rr_bUpsampling;
    rr_bColorTrafo      = req.rr_bColorTrafo;