[CVE-2015-20107] Make mailcap refuse to match unsafe filenames/types/params 22/290522/1
authorJinWang An <jinwang.an@samsung.com>
Tue, 28 Mar 2023 05:51:35 +0000 (14:51 +0900)
committerJinWang An <jinwang.an@samsung.com>
Tue, 28 Mar 2023 05:59:13 +0000 (14:59 +0900)
Change-Id: I8ea488dc4d67657383097838417639a5d3a31ac3
Signed-off-by: JinWang An <jinwang.an@samsung.com>
Doc/library/mailcap.rst
Lib/mailcap.py

index 750d085796f737cf8638bef66bc1e926a689fba0..d0e6ced2bf3c6f7233d586c1da1f6fcd97f293a2 100644 (file)
@@ -54,6 +54,19 @@ standard.  However, mailcap files are supported on most Unix systems.
    use) to determine whether or not the mailcap line applies.  :func:`findmatch`
    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()
 
index 04077ba0db2cb4b2644f8829b6f27073b84f6d90..133b12d9920b37ca885d8f4b334dc2ce910faaf6 100644 (file)
@@ -1,8 +1,15 @@
 """Mailcap file handling.  See RFC 1524."""
 
 import os
+import warnings
+import re
 
 __all__ = ["getcaps","findmatch"]
+_find_unsafe = re.compile(ur'[^\xa1-\U0010FFFF\w@+=:,./-]').search
+
+class UnsafeMailcapInput(Warning):
+    """Warning raised when refusing unsafe input"""
+
 
 # Part 1: top-level interface.
 
@@ -18,6 +25,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 IOError:
@@ -149,10 +160,13 @@ def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
     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 +198,10 @@ def subst(field, MIMEtype, filename, plist=[]):
             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 +209,12 @@ def subst(field, MIMEtype, filename, plist=[]):
                     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