File CVE-2015-20107-mailcap-unsafe-filenames.patch of Package python3-base

---
 Doc/library/mailcap.rst  |   13 +++++++++++++
 Lib/mailcap.py           |   28 ++++++++++++++++++++++++++--
 Lib/test/test_mailcap.py |   10 +++++++---
 3 files changed, 46 insertions(+), 5 deletions(-)

--- a/Doc/library/mailcap.rst
+++ b/Doc/library/mailcap.rst
@@ -55,6 +55,19 @@ standard.  However, mailcap files are su
    will automatically check such conditions and skip the entry if the check fails.
 
 
+.. versionchanged:: 3.11
+
+   To prevent security issues with shell metacharacters (symbols that have
+   special effects in a shell command line), ``findmatch`` will refuse
+   to inject ASCII characters other than alphanumerics and ``@+=:,./-_``
+   into the returned command line.
+
+   If a disallowed character appears in *filename*, ``findmatch`` will always
+   return ``(None, None)`` as if no entry was found.
+   If such a character appears elsewhere (a value in *plist* or in *MIMEtype*),
+   ``findmatch`` will ignore all mailcap entries which use that value.
+   A :mod:`warning <warnings>` will be raised in either case.
+
 .. function:: getcaps()
 
    Returns a dictionary mapping MIME types to a list of mailcap file entries. This
--- a/Lib/mailcap.py
+++ b/Lib/mailcap.py
@@ -1,9 +1,17 @@
 """Mailcap file handling.  See RFC 1524."""
 
 import os
+import warnings
+import re
 
 __all__ = ["getcaps","findmatch"]
 
+_find_unsafe = re.compile(r'[^\xa1-\U0010FFFF\w@+=:,./-]').search
+
+class UnsafeMailcapInput(Warning):
+    """Warning raised when refusing unsafe input"""
+
+
 # Part 1: top-level interface.
 
 def getcaps():
@@ -18,6 +26,10 @@ def getcaps():
     """
     caps = {}
     for mailcap in listmailcapfiles():
+        if _find_unsafe(mailcap):
+            msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (mailcap,)
+            warnings.warn(msg, UnsafeMailcapInput)
+            return None, None
         try:
             fp = open(mailcap, 'r')
         except OSError:
@@ -149,10 +161,13 @@ def findmatch(caps, MIMEtype, key='view'
     for e in entries:
         if 'test' in e:
             test = subst(e['test'], filename, plist)
+            if test is None:
+                continue
             if test and os.system(test) != 0:
                 continue
         command = subst(e[key], MIMEtype, filename, plist)
-        return command, e
+        if command is not None:
+            return command, e
     return None, None
 
 def lookup(caps, MIMEtype, key=None):
@@ -184,6 +199,10 @@ def subst(field, MIMEtype, filename, pli
             elif c == 's':
                 res = res + filename
             elif c == 't':
+                if _find_unsafe(MIMEtype):
+                    msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,)
+                    warnings.warn(msg, UnsafeMailcapInput)
+                    return None
                 res = res + MIMEtype
             elif c == '{':
                 start = i
@@ -191,7 +210,12 @@ def subst(field, MIMEtype, filename, pli
                     i = i+1
                 name = field[start:i]
                 i = i+1
-                res = res + findparam(name, plist)
+                param = findparam(name, plist)
+                if _find_unsafe(param):
+                    msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name)
+                    warnings.warn(msg, UnsafeMailcapInput)
+                    return None
+                res = res + param
             # XXX To do:
             # %n == number of parts if type is multipart/*
             # %F == list of alternating type and filename for parts
--- a/Lib/test/test_mailcap.py
+++ b/Lib/test/test_mailcap.py
@@ -101,8 +101,9 @@ class HelperFunctionTest(unittest.TestCa
             (["", "audio/*", "foo.txt"], ""),
             (["echo foo", "audio/*", "foo.txt"], "echo foo"),
             (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
-            (["echo %t", "audio/*", "foo.txt"], "echo audio/*"),
-            (["echo \%t", "audio/*", "foo.txt"], "echo %t"),
+            (["echo %t", "audio/*", "foo.txt"], None),
+            (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
+            (["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
             (["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
             (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
         ]
@@ -183,7 +184,10 @@ class FindmatchTest(unittest.TestCase):
              ('"An audio fragment"', audio_basic_entry)),
             ([c, "audio/*"],
              {"filename": fname},
-             ("/usr/local/bin/showaudio audio/*", audio_entry)),
+             (None, None)),
+            ([c, "audio/wav"],
+             {"filename": fname},
+             ("/usr/local/bin/showaudio audio/wav", audio_entry)),
             ([c, "message/external-body"],
              {"plist": plist},
              ("showexternal /dev/null default john python.org     /tmp foo bar", message_entry))
openSUSE Build Service is sponsored by