Description: Fix CVE-2019-17626: remote code execution in colors.py
 Parse input string of toColor.__call__ for color classes
 .
 It constructs respective object from the string then.
 This currently supports CMYKColor, PCMYKColor, CMYKColorSep
 and PCMYKColorSep.
Origin: vendor, https://bitbucket.org/rptlab/reportlab/issues/199/eval-in-colorspy-leads-to-remote-code#comment-55887892
Bug: https://bitbucket.org/rptlab/reportlab/issues/199/eval-in-colorspy-leads-to-remote-code
Bug-Debian: https://bugs.debian.org/942763
Forwarded: no
Author: Marek Kasik <mkasik@redhat.com>
Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
Last-Update: 2020-04-24

diff -r 9bb6ebf1b847 -r b47055e78d8b src/reportlab/lib/colors.py
--- a/src/reportlab/lib/colors.py	Fri Sep 20 14:12:39 2019 +0100
+++ b/src/reportlab/lib/colors.py	Mon Jan 27 14:46:08 2020 +0100
@@ -838,6 +838,53 @@
 
 cssParse=cssParse()
 
+def parseColorClassFromString(arg):
+    '''Parses known classes which holds color information from string
+    and constructs respective object.
+    It constructs CMYKColor, PCMYKColor, CMYKColorSep and PCMYKColorSep now.
+    '''
+
+    # Strips input string and splits it with {'(', ')', ','} delimiters
+    splitted = "".join(arg.split()).replace('(', ',').replace(')','').split(',')
+
+    # Creates a "fingerprint" of given string made of {'(', ')', ','} characters only.
+    fingerprint = ''.join(c for c in arg if c in set('(,)'))
+
+    if (len(splitted) > 0):
+        if (splitted[0] == 'Color'):
+            if (fingerprint == '(,,,)'):
+                try:
+                    return Color(*list(map(float, splitted[1:5])))
+                except:
+                    return None
+            elif (fingerprint == '(,,)'):
+                try:
+                    return Color(*list(map(float, splitted[1:4])))
+                except:
+                    return None
+        elif (splitted[0] == 'CMYKColor' and fingerprint == '(,,,)'):
+            try:
+                return CMYKColor(*list(map(float, splitted[1:5])))
+            except:
+                return None
+        elif (splitted[0] == 'PCMYKColor' and fingerprint == '(,,,)'):
+            try:
+                return PCMYKColor(*list(map(float, splitted[1:5])))
+            except:
+                return None
+        elif (splitted[0] == 'CMYKColorSep' and fingerprint == '(,,,)'):
+            try:
+                return CMYKColorSep(*list(map(float, splitted[1:5])))
+            except:
+                return None
+        elif (splitted[0] == 'PCMYKColorSep' and fingerprint == '(,,,)'):
+            try:
+                return PCMYKColorSep(*list(map(float, splitted[1:5])))
+            except:
+                return None
+    else:
+                return None
+
 class toColor:
 
     def __init__(self):
@@ -863,10 +910,8 @@
             C = getAllNamedColors()
             s = arg.lower()
             if s in C: return C[s]
-            try:
-                return toColor(eval(arg))
-            except:
-                pass
+            parsedColor = parseColorClassFromString(arg)
+            if (parsedColor): return parsedColor
 
         try:
             return HexColor(arg)
