]> granicus.if.org Git - python/commitdiff
Issue #28563: Fixed possible DoS and arbitrary code execution when handle
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 8 Nov 2016 19:26:14 +0000 (21:26 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Tue, 8 Nov 2016 19:26:14 +0000 (21:26 +0200)
plural form selections in the gettext module.  The expression parser now
supports exact syntax supported by GNU gettext.

1  2 
Lib/gettext.py
Lib/test/test_gettext.py

diff --cc Lib/gettext.py
index 101378fefa511e4c1fc11293ff8f86bd28df7ae4,1591e7ee1c68d22b77a9cd8bb30b73b589a3c879..7032efae7ffa876f182358f1fb2b49ceec0def26
@@@ -62,52 -160,38 +160,38 @@@ def _parse(tokens, priority=-1)
  
  def c2py(plural):
      """Gets a C expression as used in PO files for plural forms and returns a
-     Python lambda function that implements an equivalent expression.
+     Python function that implements an equivalent expression.
      """
-     # Security check, allow only the "n" identifier
-     import token, tokenize
-     tokens = tokenize.generate_tokens(io.StringIO(plural).readline)
-     try:
-         danger = [x for x in tokens if x[0] == token.NAME and x[1] != 'n']
-     except tokenize.TokenError:
-         raise ValueError('plural forms expression error, maybe unbalanced parenthesis')
-     else:
-         if danger:
-             raise ValueError('plural forms expression could be dangerous')
-     # Replace some C operators by their Python equivalents
-     plural = plural.replace('&&', ' and ')
-     plural = plural.replace('||', ' or ')
-     expr = re.compile(r'\!([^=])')
-     plural = expr.sub(' not \\1', plural)
-     # Regular expression and replacement function used to transform
-     # "a?b:c" to "b if a else c".
-     expr = re.compile(r'(.*?)\?(.*?):(.*)')
-     def repl(x):
-         return "(%s if %s else %s)" % (x.group(2), x.group(1),
-                                        expr.sub(repl, x.group(3)))
-     # Code to transform the plural expression, taking care of parentheses
-     stack = ['']
-     for c in plural:
-         if c == '(':
-             stack.append('')
-         elif c == ')':
-             if len(stack) == 1:
-                 # Actually, we never reach this code, because unbalanced
-                 # parentheses get caught in the security check at the
-                 # beginning.
-                 raise ValueError('unbalanced parenthesis in plural form')
-             s = expr.sub(repl, stack.pop())
-             stack[-1] += '(%s)' % s
-         else:
-             stack[-1] += c
-     plural = expr.sub(repl, stack.pop())
-     return eval('lambda n: int(%s)' % plural)
  
 -    except RuntimeError:
+     if len(plural) > 1000:
+         raise ValueError('plural form expression is too long')
+     try:
+         result, nexttok = _parse(_tokenize(plural))
+         if nexttok:
+             raise _error(nexttok)
+         depth = 0
+         for c in result:
+             if c == '(':
+                 depth += 1
+                 if depth > 20:
+                     # Python compiler limit is about 90.
+                     # The most complex example has 2.
+                     raise ValueError('plural form expression is too complex')
+             elif c == ')':
+                 depth -= 1
+         ns = {}
+         exec('''if True:
+             def func(n):
+                 if not isinstance(n, int):
+                     raise ValueError('Plural value must be an integer.')
+                 return int(%s)
+             ''' % result, ns)
+         return ns['func']
++    except RecursionError:
+         # Recursion error can be raised in _parse() or exec().
+         raise ValueError('plural form expression is too complex')
  
  
  def _expand_lang(loc):
Simple merge