diff -Nru python-reportlab-3.5.34/debian/changelog python-reportlab-3.5.34/debian/changelog --- python-reportlab-3.5.34/debian/changelog 2020-03-21 10:57:10.000000000 +0000 +++ python-reportlab-3.5.34/debian/changelog 2023-06-26 13:38:43.000000000 +0000 @@ -1,3 +1,15 @@ +python-reportlab (3.5.34-1ubuntu1.1) focal-security; urgency=medium + + * SECURITY UPDATE: Arbitrary code execution + - debian/patches/CVE-2023-33733.patch: implements a safer + toColor in src/reportlab/lib/colors.py, + src/reportlab/lib/rl_safe_eval.py, + src/reportlab/lib/utils.py, + src/reportlab/rl_settings.py, tests/test_lib_rl_safe_eval.py. + - CVE-2023-33733 + + -- Leonidas Da Silva Barbosa Mon, 26 Jun 2023 10:38:43 -0300 + python-reportlab (3.5.34-1ubuntu1) focal; urgency=medium * debian/control: diff -Nru python-reportlab-3.5.34/debian/control python-reportlab-3.5.34/debian/control --- python-reportlab-3.5.34/debian/control 2020-03-21 10:57:04.000000000 +0000 +++ python-reportlab-3.5.34/debian/control 2023-06-26 13:38:43.000000000 +0000 @@ -1,7 +1,8 @@ Source: python-reportlab Section: python Priority: optional -Maintainer: Matthias Klose +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Matthias Klose Standards-Version: 4.5.0 Build-Depends: debhelper (>= 9), dh-python, python3-all-dev, python3-all-dbg, diff -Nru python-reportlab-3.5.34/debian/patches/CVE-2023-33733.patch python-reportlab-3.5.34/debian/patches/CVE-2023-33733.patch --- python-reportlab-3.5.34/debian/patches/CVE-2023-33733.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-reportlab-3.5.34/debian/patches/CVE-2023-33733.patch 2023-06-26 13:38:33.000000000 +0000 @@ -0,0 +1,338 @@ +Backported of: + +# HG changeset patch +# User robin +# Date 1682340760 -3600 +# Node ID 1c39d2db15bbce98b5f6515c746cf0151f9b41c2 +# Parent bbe50343dd16916be760a577e9b92ffa116341b5 +update 3.6.13 branch with rl_safe_eval & rl_config.toColorCanUse changes +diff --git a/src/reportlab/lib/colors.py b/src/reportlab/lib/colors.py +index eb863e4..d5e6e1c 100644 +--- a/src/reportlab/lib/colors.py ++++ b/src/reportlab/lib/colors.py +@@ -42,7 +42,8 @@ + import math, re, functools + from reportlab import isPy3, cmp + from reportlab.lib.rl_accel import fp_str +-from reportlab.lib.utils import asNative, isStr, rl_safe_eval ++from reportlab.lib.utils import asNative, isStr, rl_safe_eval, rl_extended_literal_eval ++from reportlab import rl_config + import collections + from ast import literal_eval + +@@ -883,6 +884,17 @@ def parseColorClassFromString(arg): + return None + + class toColor: ++ """Accepot an expression returnng a Color subclass. ++ ++ This used to accept arbitrary Python expressions, which resulted in increasngly devilish CVEs and ++ security holes from tie to time. In April 2023 we are creating explicit, "dumb" parsing code to ++ replace this. Acceptable patterns are ++ ++ a Color instance passed in by the Python programmer ++ a named list of colours ('pink' etc') ++ list of 3 or 4 numbers ++ all CSS colour expression ++ """ + _G = {} #globals we like (eventually) + + def __init__(self): +@@ -908,20 +920,58 @@ def __call__(self,arg,default=None): + C = getAllNamedColors() + s = arg.lower() + if s in C: return C[s] +- G = C.copy() +- G.update(self.extraColorsNS) +- if not self._G: ++ ++ ++ # allow expressions like 'Blacker(red, 0.5)' ++ # >>> re.compile(r"(Blacker|Whiter)\((\w+)\,\s?([0-9.]+)\)").match(msg).groups() ++ # ('Blacker', 'red', '0.5') ++ # >>> ++ pat = re.compile(r"(Blacker|Whiter)\((\w+)\,\s?([0-9.]+)\)") ++ m = pat.match(arg) ++ if m: ++ funcname, rootcolor, num = m.groups() ++ if funcname == 'Blacker': ++ return Blacker(rootcolor, float(num)) ++ else: ++ return Whiter(rootcolor, float(num)) ++ ++ try: ++ import ast ++ expr = ast.literal_eval(arg) #safe probably only a tuple or list of values ++ return toColor(expr) ++ except (SyntaxError, ValueError): ++ pass ++ ++ if rl_config.toColorCanUse=='rl_safe_eval': ++ #the most dangerous option ++ G = C.copy() ++ G.update(self.extraColorsNS) ++ if not self._G: ++ C = globals() ++ self._G = {s:C[s] for s in '''Blacker CMYKColor CMYKColorSep Color ColorType HexColor PCMYKColor PCMYKColorSep Whiter ++ _chooseEnforceColorSpace _enforceCMYK _enforceError _enforceRGB _enforceSEP _enforceSEP_BLACK ++ _enforceSEP_CMYK _namedColors _re_css asNative cmyk2rgb cmykDistance color2bw colorDistance ++ cssParse describe fade fp_str getAllNamedColors hsl2rgb hue2rgb isStr linearlyInterpolatedColor ++ literal_eval obj_R_G_B opaqueColor rgb2cmyk setColors toColor toColorOrNone'''.split()} ++ G.update(self._G) ++ try: ++ return toColor(rl_safe_eval(arg,g=G,l={})) ++ except: ++ pass ++ elif rl_config.toColorCanUse=='rl_extended_literal_eval': + C = globals() +- self._G = {s:C[s] for s in '''Blacker CMYKColor CMYKColorSep Color ColorType HexColor PCMYKColor PCMYKColorSep Whiter +- _chooseEnforceColorSpace _enforceCMYK _enforceError _enforceRGB _enforceSEP _enforceSEP_BLACK +- _enforceSEP_CMYK _namedColors _re_css asNative cmyk2rgb cmykDistance color2bw colorDistance +- cssParse describe fade fp_str getAllNamedColors hsl2rgb hue2rgb isStr linearlyInterpolatedColor +- literal_eval obj_R_G_B opaqueColor rgb2cmyk setColors toColor toColorOrNone'''.split()} +- G.update(self._G) +- #try: +- # return toColor(rl_safe_eval(arg,g=G,l={})) +- #except: +- # pass ++ S = getAllNamedColors().copy() ++ C = {k:C[k] for k in '''Blacker CMYKColor CMYKColorSep Color ColorType HexColor PCMYKColor PCMYKColorSep Whiter ++ _chooseEnforceColorSpace _enforceCMYK _enforceError _enforceRGB _enforceSEP _enforceSEP_BLACK ++ _enforceSEP_CMYK _namedColors _re_css asNative cmyk2rgb cmykDistance color2bw colorDistance ++ cssParse describe fade fp_str getAllNamedColors hsl2rgb hue2rgb linearlyInterpolatedColor ++ obj_R_G_B opaqueColor rgb2cmyk setColors toColor toColorOrNone'''.split() ++ if callable(C.get(k,None))} ++ try: ++ return rl_extended_literal_eval(arg,C,S) ++ except (ValueError, SyntaxError): ++ pass ++ + parsedColor = parseColorClassFromString(arg) + if (parsedColor): return parsedColor + +diff --git a/src/reportlab/lib/rl_safe_eval.py b/src/reportlab/lib/rl_safe_eval.py +index 8145e23..12b8d97 100644 +--- a/src/reportlab/lib/rl_safe_eval.py ++++ b/src/reportlab/lib/rl_safe_eval.py +@@ -3,7 +3,7 @@ + #https://github.com/zopefoundation/RestrictedPython + #https://github.com/danthedeckie/simpleeval + #hopefully we are standing on giants' shoulders +-import sys, os, ast, re, weakref, time, copy, math ++import sys, os, ast, re, weakref, time, copy, math, types + from collections import Mapping as collectionsMapping + from reportlab import isPy3 + isPy2 = not isPy3 +@@ -60,7 +60,9 @@ class BadCode(ValueError): + func_doc func_globals func_name gi_code gi_frame gi_running gi_yieldfrom + __globals__ im_class im_func im_self __iter__ __kwdefaults__ __module__ + __name__ next __qualname__ __self__ tb_frame tb_lasti tb_lineno tb_next +- globals vars locals'''.split() ++ globals vars locals ++ type eval exec aiter anext compile open ++ dir print classmethod staticmethod __import__ super property'''.split() + ) + __rl_unsafe_re__ = re.compile(r'\b(?:%s)' % '|'.join(__rl_unsafe__),re.M) + +@@ -1339,5 +1341,70 @@ def __call__(self, expr, g=None, l=None, timeout=None, allowed_magic_methods=Non + class __rl_safe_exec__(__rl_safe_eval__): + mode = 'exec' + ++def rl_extended_literal_eval(expr, safe_callables=None, safe_names=None): ++ if safe_callables is None: ++ safe_callables = {} ++ if safe_names is None: ++ safe_names = {} ++ safe_names = safe_names.copy() ++ safe_names.update({'None': None, 'True': True, 'False': False}) ++ #make these readonly with MappingProxyType ++ safe_names = types.MappingProxyType(safe_names) ++ safe_callables = types.MappingProxyType(safe_callables) ++ if isinstance(expr, str): ++ expr = ast.parse(expr, mode='eval') ++ if isinstance(expr, ast.Expression): ++ expr = expr.body ++ try: ++ # Python 3.4 and up ++ ast.NameConstant ++ safe_test = lambda n: isinstance(n, ast.NameConstant) or isinstance(n,ast.Name) and n.id in safe_names ++ safe_extract = lambda n: n.value if isinstance(n,ast.NameConstant) else safe_names[n.id] ++ except AttributeError: ++ # Everything before ++ safe_test = lambda n: isinstance(n, ast.Name) and n.id in safe_names ++ safe_extract = lambda n: safe_names[n.id] ++ def _convert(node): ++ if isinstance(node, (ast.Str, ast.Bytes)): ++ return node.s ++ elif isinstance(node, ast.Num): ++ return node.n ++ elif isinstance(node, ast.Tuple): ++ return tuple(map(_convert, node.elts)) ++ elif isinstance(node, ast.List): ++ return list(map(_convert, node.elts)) ++ elif isinstance(node, ast.Dict): ++ return dict((_convert(k), _convert(v)) for k, v ++ in zip(node.keys, node.values)) ++ elif safe_test(node): ++ return safe_extract(node) ++ elif isinstance(node, ast.UnaryOp) and \ ++ isinstance(node.op, (ast.UAdd, ast.USub)) and \ ++ isinstance(node.operand, (ast.Num, ast.UnaryOp, ast.BinOp)): ++ operand = _convert(node.operand) ++ if isinstance(node.op, ast.UAdd): ++ return + operand ++ else: ++ return - operand ++ elif isinstance(node, ast.BinOp) and \ ++ isinstance(node.op, (ast.Add, ast.Sub)) and \ ++ isinstance(node.right, (ast.Num, ast.UnaryOp, ast.BinOp)) and \ ++ isinstance(node.right.n, complex) and \ ++ isinstance(node.left, (ast.Num, ast.UnaryOp, astBinOp)): ++ left = _convert(node.left) ++ right = _convert(node.right) ++ if isinstance(node.op, ast.Add): ++ return left + right ++ else: ++ return left - right ++ elif isinstance(node, ast.Call) and \ ++ isinstance(node.func, ast.Name) and \ ++ node.func.id in safe_callables: ++ return safe_callables[node.func.id]( ++ *[_convert(n) for n in node.args], ++ **{kw.arg: _convert(kw.value) for kw in node.keywords}) ++ raise ValueError('Bad expression') ++ return _convert(expr) ++ + rl_safe_exec = __rl_safe_exec__() + rl_safe_eval = __rl_safe_eval__() +diff --git a/src/reportlab/lib/utils.py b/src/reportlab/lib/utils.py +index 99cabac..2d5d9e3 100644 +--- a/src/reportlab/lib/utils.py ++++ b/src/reportlab/lib/utils.py +@@ -11,7 +11,7 @@ + from reportlab import isPy3 + from reportlab.lib.logger import warnOnce + from reportlab.lib.rltempfile import get_rl_tempfile, get_rl_tempdir, _rl_getuid +-from . rl_safe_eval import rl_safe_exec, rl_safe_eval, safer_globals ++from . rl_safe_eval import rl_safe_exec, rl_safe_eval, safer_globals, rl_extended_literal_eval + + try: + import cPickle as pickle +diff --git a/src/reportlab/rl_settings.py b/src/reportlab/rl_settings.py +index 67a0fb5..197d26c 100644 +--- a/src/reportlab/rl_settings.py ++++ b/src/reportlab/rl_settings.py +@@ -65,7 +65,8 @@ + hyphenationMinWordLength + reserveTTFNotdef + documentLang +-encryptionStrength'''.split()) ++encryptionStrength ++toColorCanUse'''.split()) + + allowTableBoundsErrors = 1 # set to 0 to die on too large elements in tables in debug (recommend 1 for production use) + shapeChecking = 1 +@@ -156,6 +157,8 @@ + encryptionStrength=40 #the bits for standard encryption 40, 128 or 256 (AES) + + ++toColorCanUse='rl_extended_literal_eval' #change to None or 'rl_safe_eval' depending on trust ++ + # places to look for T1Font information + T1SearchPath = ( + 'c:/Program Files/Adobe/Acrobat 9.0/Resource/Font', +diff --git a/tests/test_lib_rl_safe_eval.py b/tests/test_lib_rl_safe_eval.py +index 26f5afe..c800d96 100644 +--- a/tests/test_lib_rl_safe_eval.py ++++ b/tests/test_lib_rl_safe_eval.py +@@ -1,6 +1,6 @@ + #Copyright ReportLab Europe Ltd. 2000-2017 + #see license.txt for license details +-"""Tests for reportlab.lib.rl_eval ++"""Tests for reportlab.lib.rl_safe_eval + """ + __version__='3.5.33' + from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, printLocation +@@ -10,7 +10,7 @@ + from reportlab import rl_config + import unittest + from reportlab.lib import colors +-from reportlab.lib.utils import rl_safe_eval, rl_safe_exec, isPy3, annotateException ++from reportlab.lib.utils import rl_safe_eval, rl_safe_exec, isPy3, annotateException, rl_extended_literal_eval + from reportlab.lib.rl_safe_eval import BadCode + + testObj = [1,('a','b',2),{'A':1,'B':2.0},"32"] +@@ -52,7 +52,6 @@ def test(self): + 'dict(a=1).get("a",2)', + 'dict(a=1).pop("a",2)', + '{"_":1+_ for _ in (1,2)}.pop(1,None)', +- '(type(1),type(str),type(testObj),type(TestClass))', + '1 if True else "a"', + '1 if False else "a"', + 'testFunc(bad=False)', +@@ -77,6 +76,8 @@ def test(self): + ( + 'fail', + ( ++ 'vars()', ++ '(type(1),type(str),type(testObj),type(TestClass))', + 'open("/tmp/myfile")', + None if isPy3 else 'file("/tmp/myfile")', + 'SafeEvalTestCase.__module__', +@@ -101,6 +102,8 @@ def test(self): + 'testFunc(bad=True)', + 'getattr(testInst,"__class__",14)', + '"{1}{2}".format(1,2)', ++ 'builtins', ++ '[ [ [ [ ftype(ctype(0, 0, 0, 0, 3, 67, b"t\\x00d\\x01\\x83\\x01\\xa0\\x01d\\x02\\xa1\\x01\\x01\\x00d\\x00S\\x00", (None, "os", "touch /tmp/exploited"), ("__import__", "system"), (), "", "", 1, b"\\x12\\x01"), {})() for ftype in [type(lambda: None)] ] for ctype in [type(getattr(lambda: {None}, Word("__code__")))] ] for Word in [orgTypeFun("Word", (str,), { "mutated": 1, "startswith": lambda self, x: False, "__eq__": lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, "mutate": lambda self: {setattr(self, "mutated", self.mutated - 1)}, "__hash__": lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))]] and "red"', + ) + ), + ): +@@ -159,8 +162,46 @@ def test_001(self): + def test_002(self): + self.assertTrue(rl_safe_eval("GA=='ga'")) + ++class ExtendedLiteralEval(unittest.TestCase): ++ def test_001(self): ++ S = colors.getAllNamedColors().copy() ++ C = {s:getattr(colors,s) for s in '''Blacker CMYKColor CMYKColorSep Color ColorType HexColor PCMYKColor PCMYKColorSep Whiter ++ _chooseEnforceColorSpace _enforceCMYK _enforceError _enforceRGB _enforceSEP _enforceSEP_BLACK ++ _enforceSEP_CMYK _namedColors _re_css asNative cmyk2rgb cmykDistance color2bw colorDistance ++ cssParse describe fade fp_str getAllNamedColors hsl2rgb hue2rgb linearlyInterpolatedColor ++ obj_R_G_B opaqueColor rgb2cmyk setColors toColor toColorOrNone'''.split() ++ if callable(getattr(colors,s,None))} ++ def showVal(s): ++ try: ++ r = rl_extended_literal_eval(s,C,S) ++ except: ++ r = str(sys.exc_info()[1]) ++ return r ++ ++ for expr, expected in ( ++ ('1.0', 1.0), ++ ('1', 1), ++ ('red', colors.red), ++ ('True', True), ++ ('False', False), ++ ('None', None), ++ ('Blacker(red,0.5)', colors.Color(.5,0,0,1)), ++ ('PCMYKColor(21,10,30,5,spotName="ABCD")', colors.PCMYKColor(21,10,30,5,spotName='ABCD',alpha=100)), ++ ('HexColor("#ffffff")', colors.Color(1,1,1,1)), ++ ('linearlyInterpolatedColor(red, blue, 0, 1, 0.5)', colors.Color(.5,0,.5,1)), ++ ('red.rgb()', 'Bad expression'), ++ ('__import__("sys")', 'Bad expression'), ++ ('globals()', 'Bad expression'), ++ ('locals()', 'Bad expression'), ++ ('vars()', 'Bad expression'), ++ ('builtins', 'Bad expression'), ++ ('__file__', 'Bad expression'), ++ ('__name__', 'Bad expression'), ++ ): ++ self.assertEqual(showVal(expr),expected,f"rl_extended_literal_eval({expr!r}) is not equal to expected {expected}") ++ + def makeSuite(): +- return makeSuiteForClasses(SafeEvalTestCase,SafeEvalTestBasics) ++ return makeSuiteForClasses(SafeEvalTestCase,SafeEvalTestBasics,ExtendedLiteralEval) + + if __name__ == "__main__": #noruntests + unittest.TextTestRunner().run(makeSuite()) diff -Nru python-reportlab-3.5.34/debian/patches/series python-reportlab-3.5.34/debian/patches/series --- python-reportlab-3.5.34/debian/patches/series 2020-01-28 15:56:28.000000000 +0000 +++ python-reportlab-3.5.34/debian/patches/series 2023-06-26 12:36:57.000000000 +0000 @@ -1,3 +1,4 @@ gsfonts.diff reproducible-build.patch toColor.patch +CVE-2023-33733.patch