CVE-2021-28676: Loop with Unreachable Exit Condition ('Infinite Loop') (CWE-835)
value: An issue was discovered in Pillow before 8.2.0. For FLI data, FliDecode did not properly check that the block advance was non-zero, potentially leading to an infinite loop on load.
CWE description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVSS v3 base score: 7.5
Published: 2021-06-02T16:15Z
Repository: https://github.com/python-pillow/Pillow
Language: C
Files changed: Tests/test_file_fli.py, src/libImaging/FliDecode.c
Commit date: 2021-04-01 17:17:23 +0300
Commit message: Fix FLI DOS -- CVE-2021-28676

* FliDecode did not properly check that the block advance was
  non-zero, potentally leading to an infinite loop on load.
* This dates to the PIL Fork
* Found with oss-fuzz

Diff: diff --git a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli
new file mode 100644
index 000000000..ce4607d2d
Binary files /dev/null and b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli differ
diff --git a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli
new file mode 100644
index 000000000..77a94b87a
Binary files /dev/null and b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli differ
diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py
index 0d9748a95..1c1abf2b1 100644
--- a/Tests/test_file_fli.py
+++ b/Tests/test_file_fli.py
@@ -121,5 +121,20 @@ def test_seek_tell():
 def test_seek():
     with Image.open(animated_test_file) as im:
         im.seek(50)
 
         assert_image_equal_tofile(im, "Tests/images/a_fli.png")
+
+
+@pytest.mark.parametrize(
+    "test_file",
+    [
+        "Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli",
+        "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
+    ],
+)
+@pytest.mark.timeout(timeout=3)
+def test_timeouts(test_file):
+    with open(test_file, "rb") as f:
+        with Image.open(f) as im:
+            with pytest.raises(OSError):
+                im.load()
diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c
index e9000fc99..35a6ccccc 100644
--- a/src/libImaging/FliDecode.c
+++ b/src/libImaging/FliDecode.c
@@ -241,10 +241,15 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
                 /* printf("unknown FLI/FLC chunk: %d\n", I16(ptr+4)); */
                 state->errcode = IMAGING_CODEC_UNKNOWN;
                 return -1;
         }
         advance = I32(ptr);
+        if (advance == 0 ) {
+            // If there's no advance, we're in in infinite loop
+            state->errcode = IMAGING_CODEC_BROKEN;
+            return -1;
+        }
         if (advance < 0 || advance > bytes) {
             state->errcode = IMAGING_CODEC_OVERRUN;
             return -1;
         }
         ptr += advance;

Fixed code: 

@pytest.mark.parametrize(
    "test_file",
    [
        "Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli",
        "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
    ],
)
@pytest.mark.timeout(timeout=3)
def test_timeouts(test_file):
    with open(test_file, "rb") as f:
        with Image.open(f) as im:
            with pytest.raises(OSError):
                im.load()
        if (advance == 0 ) {
            // If there's no advance, we're in in infinite loop
            state->errcode = IMAGING_CODEC_BROKEN;
            return -1;
        }