Package: release.debian.org
Tags: buster
User: release.debian.org@packages.debian.org
Usertags: pu
Control: clone -1 -2
Control: retitle -2 stretch-pu: package limnoria/2017.01.10-1+deb9u1
Control: tag -2 = stretch
X-Debbugs-Cc: unit193@ubuntu.com
Hello SRMs,
Limnoria is affected by a security issue the security team deemed not
DSA-worthy. See https://security-tracker.debian.org/tracker/CVE-2019-19010
I'm uploading fixed packages for both stretch and buster; the patch is
pretty much the same, so I'm opening a single bug, attached are the two
diffs.
--
regards,
Mattia Rizzolo
GPG Key: 66AE 2B4A FCCF 3F52 DA18 4D18 4B04 3FCD B944 4540 .''`.
More about me: https://mapreri.org : :' :
Launchpad user: https://launchpad.net/~mapreri `. `'`
Debian QA page: https://qa.debian.org/developer.php?login=mattia `-
diffstat for limnoria-2019.02.23 limnoria-2019.02.23
changelog | 7
patches/fix-unsafe-eval.patch | 394 ++++++++++++++++++++++++++++++++++++++++++
patches/series | 1
3 files changed, 402 insertions(+)
diff -Nru limnoria-2019.02.23/debian/changelog limnoria-2019.02.23/debian/changelog
--- limnoria-2019.02.23/debian/changelog 2019-02-23 01:52:11.000000000 +0100
+++ limnoria-2019.02.23/debian/changelog 2019-11-12 16:43:35.000000000 +0100
@@ -1,3 +1,10 @@
+limnoria (2019.02.23-1+deb10u1) buster; urgency=medium
+
+ * Add patch from upstream to fix remote information disclosure and
+ possibly remote code execution in the Math plugin. CVE-2019-19010
+
+ -- Mattia Rizzolo <mattia@debian.org> Tue, 12 Nov 2019 16:43:35 +0100
+
limnoria (2019.02.23-1) unstable; urgency=medium
* New upstream version 2019.02.23
diff -Nru limnoria-2019.02.23/debian/patches/fix-unsafe-eval.patch limnoria-2019.02.23/debian/patches/fix-unsafe-eval.patch
--- limnoria-2019.02.23/debian/patches/fix-unsafe-eval.patch 1970-01-01 01:00:00.000000000 +0100
+++ limnoria-2019.02.23/debian/patches/fix-unsafe-eval.patch 2019-11-12 16:43:35.000000000 +0100
@@ -0,0 +1,394 @@
+commit 3848ae78de45b35c029cc333963d436b9d2f0a35
+Author: Valentin Lorentz <progval+git@progval.net>
+AuthorDate: Sat Nov 9 15:24:37 2019 +0100
+Commit: Valentin Lorentz <progval+git@progval.net>
+CommitDate: Sat Nov 9 15:49:31 2019 +0100
+
+ Math: Rewrite calc functions with a proper evaluator.
+
+ Instead of hacking around eval(), which everyone knows is a bad idea
+ even with prior expression sanitizing.
+
+diff --git a/plugins/Math/evaluator.py b/plugins/Math/evaluator.py
+new file mode 100644
+index 00000000..9e7208ef
+--- /dev/null
++++ b/plugins/Math/evaluator.py
+@@ -0,0 +1,169 @@
++###
++# Copyright (c) 2019, Valentin Lorentz
++# All rights reserved.
++#
++# Redistribution and use in source and binary forms, with or without
++# modification, are permitted provided that the following conditions are met:
++#
++# * Redistributions of source code must retain the above copyright notice,
++# this list of conditions, and the following disclaimer.
++# * Redistributions in binary form must reproduce the above copyright notice,
++# this list of conditions, and the following disclaimer in the
++# documentation and/or other materials provided with the distribution.
++# * Neither the name of the author of this software nor the name of
++# contributors to this software may be used to endorse or promote products
++# derived from this software without specific prior written consent.
++#
++# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++# POSSIBILITY OF SUCH DAMAGE.
++###
++
++import ast
++import math
++import cmath
++import operator
++
++class InvalidNode(Exception):
++ pass
++
++def filter_module(module, safe_names):
++ return dict([
++ (name, getattr(module, name))
++ for name in safe_names
++ if hasattr(module, name)
++ ])
++
++UNARY_OPS = {
++ ast.UAdd: lambda x: x,
++ ast.USub: lambda x: -x,
++}
++
++BIN_OPS = {
++ ast.Add: operator.add,
++ ast.Sub: operator.sub,
++ ast.Mult: operator.mul,
++ ast.Div: operator.truediv,
++ ast.Pow: operator.pow,
++ ast.BitXor: operator.xor,
++ ast.BitOr: operator.or_,
++ ast.BitAnd: operator.and_,
++}
++
++MATH_CONSTANTS = 'e inf nan pi tau'.split()
++SAFE_MATH_FUNCTIONS = (
++ 'acos acosh asin asinh atan atan2 atanh copysign cos cosh degrees erf '
++ 'erfc exp expm1 fabs fmod frexp fsum gamma hypot ldexp lgamma log log10 '
++ 'log1p log2 modf pow radians remainder sin sinh tan tanh'
++).split()
++SAFE_CMATH_FUNCTIONS = (
++ 'acos acosh asin asinh atan atanh cos cosh exp inf infj log log10 '
++ 'nanj phase polar rect sin sinh tan tanh tau'
++).split()
++
++SAFE_ENV = filter_module(math, MATH_CONSTANTS + SAFE_MATH_FUNCTIONS)
++SAFE_ENV.update(filter_module(cmath, SAFE_CMATH_FUNCTIONS))
++
++def _sqrt(x):
++ if isinstance(x, complex) or x < 0:
++ return cmath.sqrt(x)
++ else:
++ return math.sqrt(x)
++
++def _cbrt(x):
++ return math.pow(x, 1.0/3)
++
++def _factorial(x):
++ if x<=10000:
++ return float(math.factorial(x))
++ else:
++ raise Exception('factorial argument too large')
++
++SAFE_ENV.update({
++ 'i': 1j,
++ 'abs': abs,
++ 'max': max,
++ 'min': min,
++ 'round': lambda x, y=0: round(x, int(y)),
++ 'factorial': _factorial,
++ 'sqrt': _sqrt,
++ 'cbrt': _cbrt,
++ 'ceil': lambda x: float(math.ceil(x)),
++ 'floor': lambda x: float(math.floor(x)),
++})
++
++UNSAFE_ENV = SAFE_ENV.copy()
++# Add functions that return integers
++UNSAFE_ENV.update(filter_module(math, 'ceil floor factorial gcd'.split()))
++
++
++# It would be nice if ast.literal_eval used a visitor so we could subclass
++# to extend it, but it doesn't, so let's reimplement it entirely.
++class SafeEvalVisitor(ast.NodeVisitor):
++ def __init__(self, allow_ints):
++ self._allow_ints = allow_ints
++ self._env = UNSAFE_ENV if allow_ints else SAFE_ENV
++
++ def _convert_num(self, x):
++ """Converts numbers to complex if ints are not allowed."""
++ if self._allow_ints:
++ return x
++ else:
++ x = complex(x)
++ if x.imag == 0:
++ x = x.real
++ # Need to use string-formatting here instead of str() because
++ # use of str() on large numbers loses information:
++ # str(float(33333333333333)) => '3.33333333333e+13'
++ # float('3.33333333333e+13') => 33333333333300.0
++ return float('%.16f' % x)
++ else:
++ return x
++
++ def visit_Expression(self, node):
++ return self.visit(node.body)
++
++ def visit_Num(self, node):
++ return self._convert_num(node.n)
++
++ def visit_Name(self, node):
++ id_ = node.id.lower()
++ if id_ in self._env:
++ return self._env[id_]
++ else:
++ raise NameError(node.id)
++
++ def visit_Call(self, node):
++ func = self.visit(node.func)
++ args = map(self.visit, node.args)
++ # TODO: keywords?
++ return func(*args)
++
++ def visit_UnaryOp(self, node):
++ op = UNARY_OPS.get(node.op.__class__)
++ if op:
++ return op(self.visit(node.operand))
++ else:
++ raise InvalidNode('illegal operator %s' % node.op.__class__.__name__)
++
++ def visit_BinOp(self, node):
++ op = BIN_OPS.get(node.op.__class__)
++ if op:
++ return op(self.visit(node.left), self.visit(node.right))
++ else:
++ raise InvalidNode('illegal operator %s' % node.op.__class__.__name__)
++
++ def generic_visit(self, node):
++ raise InvalidNode('illegal construct %s' % node.__class__.__name__)
++
++def safe_eval(text, allow_ints):
++ node = ast.parse(text, mode='eval')
++ return SafeEvalVisitor(allow_ints).visit(node)
+diff --git a/plugins/Math/plugin.py b/plugins/Math/plugin.py
+index 88f6e142..9fe75a57 100644
+--- a/plugins/Math/plugin.py
++++ b/plugins/Math/plugin.py
+@@ -44,6 +44,7 @@ from supybot.i18n import PluginInternationalization, internationalizeDocstring
+ _ = PluginInternationalization('Math')
+
+ from .local import convertcore
++from .evaluator import safe_eval, InvalidNode, SAFE_ENV
+
+ baseArg = ('int', 'base', lambda i: i <= 36)
+
+@@ -97,36 +98,6 @@ class Math(callbacks.Plugin):
+ return str(number)
+ return self._convertDecimalToBase(number, toBase)
+
+- _mathEnv = {'__builtins__': types.ModuleType('__builtins__'), 'i': 1j}
+- _mathEnv.update(math.__dict__)
+- _mathEnv.update(cmath.__dict__)
+- def _sqrt(x):
+- if isinstance(x, complex) or x < 0:
+- return cmath.sqrt(x)
+- else:
+- return math.sqrt(x)
+- def _cbrt(x):
+- return math.pow(x, 1.0/3)
+- def _factorial(x):
+- if x<=10000:
+- return float(math.factorial(x))
+- else:
+- raise Exception('factorial argument too large')
+- _mathEnv['sqrt'] = _sqrt
+- _mathEnv['cbrt'] = _cbrt
+- _mathEnv['abs'] = abs
+- _mathEnv['max'] = max
+- _mathEnv['min'] = min
+- _mathEnv['round'] = lambda x, y=0: round(x, int(y))
+- _mathSafeEnv = dict([(x,y) for x,y in _mathEnv.items()])
+- _mathSafeEnv['factorial'] = _factorial
+- _mathRe = re.compile(r'((?:(?<![A-Fa-f\d)])-)?'
+- r'(?:0x[A-Fa-f\d]+|'
+- r'0[0-7]+|'
+- r'\d+\.\d+|'
+- r'\.\d+|'
+- r'\d+\.|'
+- r'\d+))')
+ def _floatToString(self, x):
+ if -1e-10 < x < 1e-10:
+ return '0'
+@@ -157,17 +128,6 @@ class Math(callbacks.Plugin):
+ else:
+ return '%s%s' % (realS, imagS)
+
+- _calc_match_forbidden_chars = re.compile('[_\[\]]')
+- _calc_remover = utils.str.MultipleRemover('_[] \t')
+- ###
+- # So this is how the 'calc' command works:
+- # First, we make a nice little safe environment for evaluation; basically,
+- # the names in the 'math' and 'cmath' modules. Then, we remove the ability
+- # of a random user to get ints evaluated: this means we have to turn all
+- # int literals (even octal numbers and hexadecimal numbers) into floats.
+- # Then we delete all square brackets, underscores, and whitespace, so no
+- # one can do list comprehensions or call __...__ functions.
+- ###
+ @internationalizeDocstring
+ def calc(self, irc, msg, args, text):
+ """<math expression>
+@@ -178,57 +138,17 @@ class Math(callbacks.Plugin):
+ crash to the bot with something like '10**10**10**10'. One consequence
+ is that large values such as '10**24' might not be exact.
+ """
+- try:
+- text = str(text)
+- except UnicodeEncodeError:
+- irc.error(_("There's no reason you should have fancy non-ASCII "
+- "characters in your mathematical expression. "
+- "Please remove them."))
+- return
+- if self._calc_match_forbidden_chars.match(text):
+- # Note: this is important to keep this to forbid usage of
+- # __builtins__
+- irc.error(_('There\'s really no reason why you should have '
+- 'underscores or brackets in your mathematical '
+- 'expression. Please remove them.'))
+- return
+- text = self._calc_remover(text)
+- if 'lambda' in text:
+- irc.error(_('You can\'t use lambda in this command.'))
+- return
+- text = text.lower()
+- def handleMatch(m):
+- s = m.group(1)
+- if s.startswith('0x'):
+- i = int(s, 16)
+- elif s.startswith('0') and '.' not in s:
+- try:
+- i = int(s, 8)
+- except ValueError:
+- i = int(s)
+- else:
+- i = float(s)
+- x = complex(i)
+- if x.imag == 0:
+- x = x.real
+- # Need to use string-formatting here instead of str() because
+- # use of str() on large numbers loses information:
+- # str(float(33333333333333)) => '3.33333333333e+13'
+- # float('3.33333333333e+13') => 33333333333300.0
+- return '%.16f' % x
+- return str(x)
+- text = self._mathRe.sub(handleMatch, text)
+ try:
+ self.log.info('evaluating %q from %s', text, msg.prefix)
+- x = complex(eval(text, self._mathSafeEnv, self._mathSafeEnv))
++ x = complex(safe_eval(text, allow_ints=False))
+ irc.reply(self._complexToString(x))
+ except OverflowError:
+ maxFloat = math.ldexp(0.9999999999999999, 1024)
+ irc.error(_('The answer exceeded %s or so.') % maxFloat)
+- except TypeError:
+- irc.error(_('Something in there wasn\'t a valid number.'))
++ except InvalidNode as e:
++ irc.error(_('Invalid syntax: %s') % e.args[0])
+ except NameError as e:
+- irc.error(_('%s is not a defined function.') % str(e).split()[1])
++ irc.error(_('%s is not a defined function.') % e.args[0])
+ except Exception as e:
+ irc.error(str(e))
+ calc = wrap(calc, ['text'])
+@@ -241,28 +161,15 @@ class Math(callbacks.Plugin):
+ math, and can thus cause the bot to suck up CPU. Hence it requires
+ the 'trusted' capability to use.
+ """
+- if self._calc_match_forbidden_chars.match(text):
+- # Note: this is important to keep this to forbid usage of
+- # __builtins__
+- irc.error(_('There\'s really no reason why you should have '
+- 'underscores or brackets in your mathematical '
+- 'expression. Please remove them.'))
+- return
+- # This removes spaces, too, but we'll leave the removal of _[] for
+- # safety's sake.
+- text = self._calc_remover(text)
+- if 'lambda' in text:
+- irc.error(_('You can\'t use lambda in this command.'))
+- return
+- text = text.replace('lambda', '')
+ try:
+ self.log.info('evaluating %q from %s', text, msg.prefix)
+- irc.reply(str(eval(text, self._mathEnv, self._mathEnv)))
++ x = safe_eval(text, allow_ints=True)
++ irc.reply(str(x))
+ except OverflowError:
+ maxFloat = math.ldexp(0.9999999999999999, 1024)
+ irc.error(_('The answer exceeded %s or so.') % maxFloat)
+- except TypeError:
+- irc.error(_('Something in there wasn\'t a valid number.'))
++ except InvalidNode as e:
++ irc.error(_('Invalid syntax: %s') % e.args[0])
+ except NameError as e:
+ irc.error(_('%s is not a defined function.') % str(e).split()[1])
+ except Exception as e:
+@@ -286,8 +193,8 @@ class Math(callbacks.Plugin):
+ x = abs(x)
+ stack.append(x)
+ except ValueError: # Not a float.
+- if arg in self._mathSafeEnv:
+- f = self._mathSafeEnv[arg]
++ if arg in SAFE_ENV:
++ f = SAFE_ENV[arg]
+ if callable(f):
+ called = False
+ arguments = []
+@@ -310,7 +217,7 @@ class Math(callbacks.Plugin):
+ arg1 = stack.pop()
+ s = '%s%s%s' % (arg1, arg, arg2)
+ try:
+- stack.append(eval(s, self._mathSafeEnv, self._mathSafeEnv))
++ stack.append(safe_eval(s, allow_ints=False))
+ except SyntaxError:
+ irc.error(format(_('%q is not a defined function.'),
+ arg))
+diff --git a/plugins/Math/test.py b/plugins/Math/test.py
+index 91e3c4aa..fbd0ff5e 100644
+--- a/plugins/Math/test.py
++++ b/plugins/Math/test.py
+@@ -91,9 +91,6 @@ class MathTestCase(PluginTestCase):
+ self.assertError('base 4 4')
+ self.assertError('base 10 12 A')
+
+- print()
+- print("If we have not fixed a bug with Math.base, the following ")
+- print("tests will hang the test-suite.")
+ self.assertRegexp('base 2 10 [base 10 2 -12]', '-12')
+ self.assertRegexp('base 16 2 [base 2 16 -110101]', '-110101')
+
+@@ -117,7 +114,10 @@ class MathTestCase(PluginTestCase):
+ self.assertError('calc factorial(20000)')
+
+ def testCalcNoNameError(self):
+- self.assertNotRegexp('calc foobar(x)', 'NameError')
++ self.assertRegexp('calc foobar(x)', 'foobar is not a defined function')
++
++ def testCalcInvalidNode(self):
++ self.assertRegexp('calc {"foo": "bar"}', 'Illegal construct Dict')
+
+ def testCalcImaginary(self):
+ self.assertResponse('calc 3 + sqrt(-1)', '3+i')
diff -Nru limnoria-2019.02.23/debian/patches/series limnoria-2019.02.23/debian/patches/series
--- limnoria-2019.02.23/debian/patches/series 2018-09-09 02:07:51.000000000 +0200
+++ limnoria-2019.02.23/debian/patches/series 2019-11-12 16:43:35.000000000 +0100
@@ -1 +1,2 @@
version
+fix-unsafe-eval.patch
diffstat for limnoria-2017.01.10 limnoria-2017.01.10
changelog | 7
patches/fix-unsafe-eval.patch | 364 ++++++++++++++++++++++++++++++++++++++++++
patches/series | 1
3 files changed, 372 insertions(+)
diff -Nru limnoria-2017.01.10/debian/changelog limnoria-2017.01.10/debian/changelog
--- limnoria-2017.01.10/debian/changelog 2017-01-31 11:59:27.000000000 +0100
+++ limnoria-2017.01.10/debian/changelog 2019-11-12 16:51:58.000000000 +0100
@@ -1,3 +1,10 @@
+limnoria (2017.01.10-1+deb9u1) stretch; urgency=medium
+
+ * Add patch from upstream to fix remote information disclosure and
+ possibly remote code execution in the Math plugin. CVE-2019-19010
+
+ -- Mattia Rizzolo <mattia@debian.org> Tue, 12 Nov 2019 16:51:58 +0100
+
limnoria (2017.01.10-1) unstable; urgency=medium
* New upstream version 2017.01.10.
diff -Nru limnoria-2017.01.10/debian/patches/fix-unsafe-eval.patch limnoria-2017.01.10/debian/patches/fix-unsafe-eval.patch
--- limnoria-2017.01.10/debian/patches/fix-unsafe-eval.patch 1970-01-01 01:00:00.000000000 +0100
+++ limnoria-2017.01.10/debian/patches/fix-unsafe-eval.patch 2019-11-12 16:51:58.000000000 +0100
@@ -0,0 +1,364 @@
+commit 3848ae78de45b35c029cc333963d436b9d2f0a35
+Author: Valentin Lorentz <progval+git@progval.net>
+AuthorDate: Sat Nov 9 15:24:37 2019 +0100
+Commit: Valentin Lorentz <progval+git@progval.net>
+CommitDate: Sat Nov 9 15:49:31 2019 +0100
+
+ Math: Rewrite calc functions with a proper evaluator.
+
+ Instead of hacking around eval(), which everyone knows is a bad idea
+ even with prior expression sanitizing.
+
+--- /dev/null
++++ b/plugins/Math/evaluator.py
+@@ -0,0 +1,169 @@
++###
++# Copyright (c) 2019, Valentin Lorentz
++# All rights reserved.
++#
++# Redistribution and use in source and binary forms, with or without
++# modification, are permitted provided that the following conditions are met:
++#
++# * Redistributions of source code must retain the above copyright notice,
++# this list of conditions, and the following disclaimer.
++# * Redistributions in binary form must reproduce the above copyright notice,
++# this list of conditions, and the following disclaimer in the
++# documentation and/or other materials provided with the distribution.
++# * Neither the name of the author of this software nor the name of
++# contributors to this software may be used to endorse or promote products
++# derived from this software without specific prior written consent.
++#
++# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++# POSSIBILITY OF SUCH DAMAGE.
++###
++
++import ast
++import math
++import cmath
++import operator
++
++class InvalidNode(Exception):
++ pass
++
++def filter_module(module, safe_names):
++ return dict([
++ (name, getattr(module, name))
++ for name in safe_names
++ if hasattr(module, name)
++ ])
++
++UNARY_OPS = {
++ ast.UAdd: lambda x: x,
++ ast.USub: lambda x: -x,
++}
++
++BIN_OPS = {
++ ast.Add: operator.add,
++ ast.Sub: operator.sub,
++ ast.Mult: operator.mul,
++ ast.Div: operator.truediv,
++ ast.Pow: operator.pow,
++ ast.BitXor: operator.xor,
++ ast.BitOr: operator.or_,
++ ast.BitAnd: operator.and_,
++}
++
++MATH_CONSTANTS = 'e inf nan pi tau'.split()
++SAFE_MATH_FUNCTIONS = (
++ 'acos acosh asin asinh atan atan2 atanh copysign cos cosh degrees erf '
++ 'erfc exp expm1 fabs fmod frexp fsum gamma hypot ldexp lgamma log log10 '
++ 'log1p log2 modf pow radians remainder sin sinh tan tanh'
++).split()
++SAFE_CMATH_FUNCTIONS = (
++ 'acos acosh asin asinh atan atanh cos cosh exp inf infj log log10 '
++ 'nanj phase polar rect sin sinh tan tanh tau'
++).split()
++
++SAFE_ENV = filter_module(math, MATH_CONSTANTS + SAFE_MATH_FUNCTIONS)
++SAFE_ENV.update(filter_module(cmath, SAFE_CMATH_FUNCTIONS))
++
++def _sqrt(x):
++ if isinstance(x, complex) or x < 0:
++ return cmath.sqrt(x)
++ else:
++ return math.sqrt(x)
++
++def _cbrt(x):
++ return math.pow(x, 1.0/3)
++
++def _factorial(x):
++ if x<=10000:
++ return float(math.factorial(x))
++ else:
++ raise Exception('factorial argument too large')
++
++SAFE_ENV.update({
++ 'i': 1j,
++ 'abs': abs,
++ 'max': max,
++ 'min': min,
++ 'round': lambda x, y=0: round(x, int(y)),
++ 'factorial': _factorial,
++ 'sqrt': _sqrt,
++ 'cbrt': _cbrt,
++ 'ceil': lambda x: float(math.ceil(x)),
++ 'floor': lambda x: float(math.floor(x)),
++})
++
++UNSAFE_ENV = SAFE_ENV.copy()
++# Add functions that return integers
++UNSAFE_ENV.update(filter_module(math, 'ceil floor factorial gcd'.split()))
++
++
++# It would be nice if ast.literal_eval used a visitor so we could subclass
++# to extend it, but it doesn't, so let's reimplement it entirely.
++class SafeEvalVisitor(ast.NodeVisitor):
++ def __init__(self, allow_ints):
++ self._allow_ints = allow_ints
++ self._env = UNSAFE_ENV if allow_ints else SAFE_ENV
++
++ def _convert_num(self, x):
++ """Converts numbers to complex if ints are not allowed."""
++ if self._allow_ints:
++ return x
++ else:
++ x = complex(x)
++ if x.imag == 0:
++ x = x.real
++ # Need to use string-formatting here instead of str() because
++ # use of str() on large numbers loses information:
++ # str(float(33333333333333)) => '3.33333333333e+13'
++ # float('3.33333333333e+13') => 33333333333300.0
++ return float('%.16f' % x)
++ else:
++ return x
++
++ def visit_Expression(self, node):
++ return self.visit(node.body)
++
++ def visit_Num(self, node):
++ return self._convert_num(node.n)
++
++ def visit_Name(self, node):
++ id_ = node.id.lower()
++ if id_ in self._env:
++ return self._env[id_]
++ else:
++ raise NameError(node.id)
++
++ def visit_Call(self, node):
++ func = self.visit(node.func)
++ args = map(self.visit, node.args)
++ # TODO: keywords?
++ return func(*args)
++
++ def visit_UnaryOp(self, node):
++ op = UNARY_OPS.get(node.op.__class__)
++ if op:
++ return op(self.visit(node.operand))
++ else:
++ raise InvalidNode('illegal operator %s' % node.op.__class__.__name__)
++
++ def visit_BinOp(self, node):
++ op = BIN_OPS.get(node.op.__class__)
++ if op:
++ return op(self.visit(node.left), self.visit(node.right))
++ else:
++ raise InvalidNode('illegal operator %s' % node.op.__class__.__name__)
++
++ def generic_visit(self, node):
++ raise InvalidNode('illegal construct %s' % node.__class__.__name__)
++
++def safe_eval(text, allow_ints):
++ node = ast.parse(text, mode='eval')
++ return SafeEvalVisitor(allow_ints).visit(node)
+--- a/plugins/Math/plugin.py
++++ b/plugins/Math/plugin.py
+@@ -48,6 +48,9 @@
+ except ImportError:
+ from .local import convertcore
+
++from .evaluator import safe_eval, InvalidNode, SAFE_ENV
++
++
+ baseArg = ('int', 'base', lambda i: i <= 36)
+
+ class Math(callbacks.Plugin):
+@@ -100,36 +103,6 @@
+ return str(number)
+ return self._convertDecimalToBase(number, toBase)
+
+- _mathEnv = {'__builtins__': types.ModuleType('__builtins__'), 'i': 1j}
+- _mathEnv.update(math.__dict__)
+- _mathEnv.update(cmath.__dict__)
+- def _sqrt(x):
+- if isinstance(x, complex) or x < 0:
+- return cmath.sqrt(x)
+- else:
+- return math.sqrt(x)
+- def _cbrt(x):
+- return math.pow(x, 1.0/3)
+- def _factorial(x):
+- if x<=10000:
+- return math.factorial(x)
+- else:
+- raise Exception('factorial argument too large')
+- _mathEnv['sqrt'] = _sqrt
+- _mathEnv['cbrt'] = _cbrt
+- _mathEnv['abs'] = abs
+- _mathEnv['max'] = max
+- _mathEnv['min'] = min
+- _mathEnv['round'] = round
+- _mathSafeEnv = dict([(x,y) for x,y in _mathEnv.items()])
+- _mathSafeEnv['factorial'] = _factorial
+- _mathRe = re.compile(r'((?:(?<![A-Fa-f\d)])-)?'
+- r'(?:0x[A-Fa-f\d]+|'
+- r'0[0-7]+|'
+- r'\d+\.\d+|'
+- r'\.\d+|'
+- r'\d+\.|'
+- r'\d+))')
+ def _floatToString(self, x):
+ if -1e-10 < x < 1e-10:
+ return '0'
+@@ -160,17 +133,6 @@
+ else:
+ return '%s%s' % (realS, imagS)
+
+- _calc_match_forbidden_chars = re.compile('[_[\]]')
+- _calc_remover = utils.str.MultipleRemover('_[] \t')
+- ###
+- # So this is how the 'calc' command works:
+- # First, we make a nice little safe environment for evaluation; basically,
+- # the names in the 'math' and 'cmath' modules. Then, we remove the ability
+- # of a random user to get ints evaluated: this means we have to turn all
+- # int literals (even octal numbers and hexadecimal numbers) into floats.
+- # Then we delete all square brackets, underscores, and whitespace, so no
+- # one can do list comprehensions or call __...__ functions.
+- ###
+ @internationalizeDocstring
+ def calc(self, irc, msg, args, text):
+ """<math expression>
+@@ -182,56 +144,16 @@
+ is that large values such as '10**24' might not be exact.
+ """
+ try:
+- text = str(text)
+- except UnicodeEncodeError:
+- irc.error(_("There's no reason you should have fancy non-ASCII "
+- "characters in your mathematical expression. "
+- "Please remove them."))
+- return
+- if self._calc_match_forbidden_chars.match(text):
+- # Note: this is important to keep this to forbid usage of
+- # __builtins__
+- irc.error(_('There\'s really no reason why you should have '
+- 'underscores or brackets in your mathematical '
+- 'expression. Please remove them.'))
+- return
+- text = self._calc_remover(text)
+- if 'lambda' in text:
+- irc.error(_('You can\'t use lambda in this command.'))
+- return
+- text = text.lower()
+- def handleMatch(m):
+- s = m.group(1)
+- if s.startswith('0x'):
+- i = int(s, 16)
+- elif s.startswith('0') and '.' not in s:
+- try:
+- i = int(s, 8)
+- except ValueError:
+- i = int(s)
+- else:
+- i = float(s)
+- x = complex(i)
+- if x.imag == 0:
+- x = x.real
+- # Need to use string-formatting here instead of str() because
+- # use of str() on large numbers loses information:
+- # str(float(33333333333333)) => '3.33333333333e+13'
+- # float('3.33333333333e+13') => 33333333333300.0
+- return '%.16f' % x
+- return str(x)
+- text = self._mathRe.sub(handleMatch, text)
+- try:
+ self.log.info('evaluating %q from %s', text, msg.prefix)
+- x = complex(eval(text, self._mathSafeEnv, self._mathSafeEnv))
++ x = complex(safe_eval(text, allow_ints=False))
+ irc.reply(self._complexToString(x))
+ except OverflowError:
+ maxFloat = math.ldexp(0.9999999999999999, 1024)
+ irc.error(_('The answer exceeded %s or so.') % maxFloat)
+- except TypeError:
+- irc.error(_('Something in there wasn\'t a valid number.'))
++ except InvalidNode as e:
++ irc.error(_('Invalid syntax: %s') % e.args[0])
+ except NameError as e:
+- irc.error(_('%s is not a defined function.') % str(e).split()[1])
++ irc.error(_('%s is not a defined function.') % e.args[0])
+ except Exception as e:
+ irc.error(str(e))
+ calc = wrap(calc, ['text'])
+@@ -244,28 +166,15 @@
+ math, and can thus cause the bot to suck up CPU. Hence it requires
+ the 'trusted' capability to use.
+ """
+- if self._calc_match_forbidden_chars.match(text):
+- # Note: this is important to keep this to forbid usage of
+- # __builtins__
+- irc.error(_('There\'s really no reason why you should have '
+- 'underscores or brackets in your mathematical '
+- 'expression. Please remove them.'))
+- return
+- # This removes spaces, too, but we'll leave the removal of _[] for
+- # safety's sake.
+- text = self._calc_remover(text)
+- if 'lambda' in text:
+- irc.error(_('You can\'t use lambda in this command.'))
+- return
+- text = text.replace('lambda', '')
+ try:
+ self.log.info('evaluating %q from %s', text, msg.prefix)
+- irc.reply(str(eval(text, self._mathEnv, self._mathEnv)))
++ x = safe_eval(text, allow_ints=True)
++ irc.reply(str(x))
+ except OverflowError:
+ maxFloat = math.ldexp(0.9999999999999999, 1024)
+ irc.error(_('The answer exceeded %s or so.') % maxFloat)
+- except TypeError:
+- irc.error(_('Something in there wasn\'t a valid number.'))
++ except InvalidNode as e:
++ irc.error(_('Invalid syntax: %s') % e.args[0])
+ except NameError as e:
+ irc.error(_('%s is not a defined function.') % str(e).split()[1])
+ except Exception as e:
+@@ -289,8 +198,8 @@
+ x = abs(x)
+ stack.append(x)
+ except ValueError: # Not a float.
+- if arg in self._mathSafeEnv:
+- f = self._mathSafeEnv[arg]
++ if arg in SAFE_ENV:
++ f = SAFE_ENV[arg]
+ if callable(f):
+ called = False
+ arguments = []
+@@ -313,7 +222,7 @@
+ arg1 = stack.pop()
+ s = '%s%s%s' % (arg1, arg, arg2)
+ try:
+- stack.append(eval(s, self._mathSafeEnv, self._mathSafeEnv))
++ stack.append(safe_eval(s, allow_ints=False))
+ except SyntaxError:
+ irc.error(format(_('%q is not a defined function.'),
+ arg))
diff -Nru limnoria-2017.01.10/debian/patches/series limnoria-2017.01.10/debian/patches/series
--- limnoria-2017.01.10/debian/patches/series 2017-01-31 11:56:34.000000000 +0100
+++ limnoria-2017.01.10/debian/patches/series 2019-11-12 16:51:58.000000000 +0100
@@ -1 +1,2 @@
version
+fix-unsafe-eval.patch
Attachment:
signature.asc
Description: PGP signature