CVE-2021-20304: Integer Overflow or Wraparound (CWE-190)
value: A flaw was found in OpenEXR's hufDecode functionality. This flaw allows an attacker who can pass a crafted file to be processed by OpenEXR, to trigger an undefined right shift error. The highest threat from this vulnerability is to system availability.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.5
Published: 2022-08-23T16:15Z
Repository: https://github.com/AcademySoftwareFoundation/openexr
Language: C++
Files changed: OpenEXR/IlmImf/ImfHuf.cpp, OpenEXR/IlmImfTest/testHuf.cpp
Commit date: 2020-10-13 08:03:57 +1300
Commit message: check for valid Huf code lengths (#849)

* check for valid Huf code lengths
* test non-fast huf decoder in testHuf

Signed-off-by: Peter Hillman <peterh@wetafx.co.nz>
Diff: diff --git a/OpenEXR/IlmImf/ImfHuf.cpp b/OpenEXR/IlmImf/ImfHuf.cpp
index 23cfcfd6..1cc09d63 100644
--- a/OpenEXR/IlmImf/ImfHuf.cpp
+++ b/OpenEXR/IlmImf/ImfHuf.cpp
@@ -908,10 +908,15 @@ hufDecode
 		//
 		// Get short code
 		//
 
 		lc -= pl.len;
+
+		if ( lc < 0 )
+		{
+			invalidCode(); // code length too long
+		}
 		getCode (pl.lit, rlc, c, lc, in, out, outb, oe);
 	    }
 	    else
 	    {
 		if (!pl.p)
@@ -965,10 +970,14 @@ hufDecode
 	const HufDec pl = hdecod[(c << (HUF_DECBITS - lc)) & HUF_DECMASK];
 
 	if (pl.len)
 	{
 	    lc -= pl.len;
+            if ( lc < 0 )
+            {
+   	        invalidCode(); // code length too long
+            }
 	    getCode (pl.lit, rlc, c, lc, in, out, outb, oe);
 	}
 	else
 	{
 	    invalidCode(); // wrong (long) code
diff --git a/OpenEXR/IlmImfTest/testHuf.cpp b/OpenEXR/IlmImfTest/testHuf.cpp
index a93b8ea9..beb4edef 100644
--- a/OpenEXR/IlmImfTest/testHuf.cpp
+++ b/OpenEXR/IlmImfTest/testHuf.cpp
@@ -243,74 +243,90 @@ testHuf (const std::string&)
     {
 	cout << "Testing Huffman encoder" << endl;
 
 	IMATH_NAMESPACE::Rand48 rand48 (0);
 
-	const int N = 1000000;
-	Array <unsigned short> raw (N);
-
-	fill1 (raw, N, 1, rand48);	  // test various symbol distributions
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill1 (raw, N, 10, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill1 (raw, N, 100, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill1 (raw, N, 1000, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-
-	fill2 (raw, N, 1, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill2 (raw, N, 10, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill2 (raw, N, 100, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill2 (raw, N, 1000, rand48);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-
-	fill3 (raw, N, 0);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill3 (raw, N, 1);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill3 (raw, N, USHRT_MAX - 1);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-	fill3 (raw, N, USHRT_MAX);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-
-	fill4 (raw, USHRT_MAX + 1);
-        compressVerify(raw, USHRT_MAX + 1, HUF_COMPRESS_DEK_HASH_FOR_FILL4_USHRT_MAX_PLUS_ONE);
-	compressUncompress (raw, USHRT_MAX + 1);
-	compressUncompressSubset (raw, USHRT_MAX + 1);
-	fill4 (raw, N);
-        compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL4_N);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
-
-	fill4 (raw, 0);
-	compressUncompress (raw, 0);	// test small input data sets
-	fill4 (raw, 1);
-	compressUncompress (raw, 1);
-	fill4 (raw, 2);
-	compressUncompress (raw, 2);
-	fill4 (raw, 3);
-	compressUncompress (raw, 3);
-
-	fill5 (raw, N);			// test run-length coding of code table
-        compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL5_N);
-	compressUncompress (raw, N);
-	compressUncompressSubset (raw, N);
+        //
+        // FastHufDecoder is used for more than 128 bits, so first test with fewer than 128 bits,
+        // then test FastHufDecoder
+        //
+        for (int pass = 0 ; pass < 2 ; ++pass)
+        {
+
+            int N = pass==0 ? 12 : 1000000;
+            Array <unsigned short> raw (N);
+
+            fill1 (raw, N, 1, rand48);	  // test various symbol distributions
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill1 (raw, N, 10, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill1 (raw, N, 100, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill1 (raw, N, 1000, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+
+            fill2 (raw, N, 1, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill2 (raw, N, 10, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill2 (raw, N, 100, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill2 (raw, N, 1000, rand48);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+
+            fill3 (raw, N, 0);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill3 (raw, N, 1);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill3 (raw, N, USHRT_MAX - 1);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+            fill3 (raw, N, USHRT_MAX);
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+
+            if (pass==1)
+            {
+                fill4 (raw, USHRT_MAX + 1);
+                compressVerify(raw, USHRT_MAX + 1, HUF_COMPRESS_DEK_HASH_FOR_FILL4_USHRT_MAX_PLUS_ONE);
+
+                compressUncompress (raw, USHRT_MAX + 1);
+                compressUncompressSubset (raw, USHRT_MAX + 1);
+                fill4 (raw, N);
+                compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL4_N);
+            }
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+
+            fill4 (raw, 0);
+            compressUncompress (raw, 0);	// test small input data sets
+            fill4 (raw, 1);
+            compressUncompress (raw, 1);
+            fill4 (raw, 2);
+            compressUncompress (raw, 2);
+            fill4 (raw, 3);
+            compressUncompress (raw, 3);
+
+            fill5 (raw, N);			// test run-length coding of code table
+            if (pass==1)
+            {
+                compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL5_N);
+            }
+            compressUncompress (raw, N);
+            compressUncompressSubset (raw, N);
+
+        }
 
 	cout << "ok\n" << endl;
     }
     catch (const std::exception &e)
     {

Vulnerable code: 	const int N = 1000000;
	Array <unsigned short> raw (N);

	fill1 (raw, N, 1, rand48);	  // test various symbol distributions
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill1 (raw, N, 10, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill1 (raw, N, 100, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill1 (raw, N, 1000, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);

	fill2 (raw, N, 1, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill2 (raw, N, 10, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill2 (raw, N, 100, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill2 (raw, N, 1000, rand48);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);

	fill3 (raw, N, 0);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill3 (raw, N, 1);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill3 (raw, N, USHRT_MAX - 1);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
	fill3 (raw, N, USHRT_MAX);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);

	fill4 (raw, USHRT_MAX + 1);
        compressVerify(raw, USHRT_MAX + 1, HUF_COMPRESS_DEK_HASH_FOR_FILL4_USHRT_MAX_PLUS_ONE);
	compressUncompress (raw, USHRT_MAX + 1);
	compressUncompressSubset (raw, USHRT_MAX + 1);
	fill4 (raw, N);
        compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL4_N);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);

	fill4 (raw, 0);
	compressUncompress (raw, 0);	// test small input data sets
	fill4 (raw, 1);
	compressUncompress (raw, 1);
	fill4 (raw, 2);
	compressUncompress (raw, 2);
	fill4 (raw, 3);
	compressUncompress (raw, 3);

	fill5 (raw, N);			// test run-length coding of code table
        compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL5_N);
	compressUncompress (raw, N);
	compressUncompressSubset (raw, N);
Fixed code: 
		if ( lc < 0 )
		{
			invalidCode(); // code length too long
		}
            if ( lc < 0 )
            {
   	        invalidCode(); // code length too long
            }
        //
        // FastHufDecoder is used for more than 128 bits, so first test with fewer than 128 bits,
        // then test FastHufDecoder
        //
        for (int pass = 0 ; pass < 2 ; ++pass)
        {

            int N = pass==0 ? 12 : 1000000;
            Array <unsigned short> raw (N);

            fill1 (raw, N, 1, rand48);	  // test various symbol distributions
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill1 (raw, N, 10, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill1 (raw, N, 100, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill1 (raw, N, 1000, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);

            fill2 (raw, N, 1, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill2 (raw, N, 10, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill2 (raw, N, 100, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill2 (raw, N, 1000, rand48);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);

            fill3 (raw, N, 0);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill3 (raw, N, 1);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill3 (raw, N, USHRT_MAX - 1);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);
            fill3 (raw, N, USHRT_MAX);
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);

            if (pass==1)
            {
                fill4 (raw, USHRT_MAX + 1);
                compressVerify(raw, USHRT_MAX + 1, HUF_COMPRESS_DEK_HASH_FOR_FILL4_USHRT_MAX_PLUS_ONE);

                compressUncompress (raw, USHRT_MAX + 1);
                compressUncompressSubset (raw, USHRT_MAX + 1);
                fill4 (raw, N);
                compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL4_N);
            }
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);

            fill4 (raw, 0);
            compressUncompress (raw, 0);	// test small input data sets
            fill4 (raw, 1);
            compressUncompress (raw, 1);
            fill4 (raw, 2);
            compressUncompress (raw, 2);
            fill4 (raw, 3);
            compressUncompress (raw, 3);

            fill5 (raw, N);			// test run-length coding of code table
            if (pass==1)
            {
                compressVerify(raw, N, HUF_COMPRESS_DEK_HASH_FOR_FILL5_N);
            }
            compressUncompress (raw, N);
            compressUncompressSubset (raw, N);

        }