CVE-2015-1326: Improper Input Validation (CWE-20)
value: python-dbusmock before version 0.15.1 AddTemplate() D-Bus method call or DBusTestCase.spawn_server_template() method could be tricked into executing malicious code if an attacker supplies a .pyc file.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2019-04-22T16:29Z
Repository: https://github.com/martinpitt/python-dbusmock
Language: Python
Files changed: NEWS, dbusmock/mockobject.py, tests/test_api.py
Commit date: 2015-05-12 12:28:03 +0200
Commit message: SECURITY FIX: Prevent code execution through crafted pyc files

When loading a template from an arbitrary file through the AddTemplate() D-Bus
method call or DBusTestCase.spawn_server_template() Python method, don't create
or use Python's *.pyc cached files.By tricking a user into loading a template
from a world-writable directory like /tmp, an attacker could run arbitrary code
with the user's privileges by putting a crafted .pyc file into that directory.

Note that this is highly unlikely to actually appear in practice as custom
dbusmock templates are usually shipped in project directories, not directly in
world-writable directories.

Thanks to Simon McVittie for discovering this!

LP: #1453815
CVE-2015-1326

Diff: diff --git a/NEWS b/NEWS
index ab654b5..e746065 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,18 @@
+0.15.1 (UNRELEASED)
+-------------------
+ - SECURITY FIX: When loading a template from an arbitrary file through the
+   AddTemplate() D-Bus method call or DBusTestCase.spawn_server_template()
+   Python method, don't create or use Python's *.pyc cached files. By tricking
+   a user into loading a template from a world-writable directory like /tmp, an
+   attacker could run arbitrary code with the user's privileges by putting a
+   crafted .pyc file into that directory. Note that this is highly unlikely to
+   actually appear in practice as custom dbusmock templates are usually shipped
+   in project directories, not directly in world-writable directories.
+   Thanks to Simon McVittie for discovering this!
+   (LP: #1453815, CVE-2015-1326)
+
 0.15 (2015-05-08)
 -------------------
  - NetworkManager template: Restore nm-specific PropertiesChanged signal
  - NetworkManager template: Add DeactivateConnection(),
    Settings.AddConnection(), Settings.Connection.Update(), and
diff --git a/dbusmock/mockobject.py b/dbusmock/mockobject.py
index 29f790e..586dbad 100644
--- a/dbusmock/mockobject.py
+++ b/dbusmock/mockobject.py
@@ -15,10 +15,11 @@ __license__ = 'LGPL 3+'
 import copy
 import time
 import sys
 import types
 import importlib
+import imp
 from xml.etree import ElementTree
 
 # we do not use this ourselves, but mock methods often want to use this
 import os
 os  # pyflakes
@@ -38,18 +39,14 @@ if sys.version_info[0] >= 3:
     unicode = str
 
 
 def load_module(name):
     if os.path.exists(name) and os.path.splitext(name)[1] == '.py':
-        sys.path.insert(0, os.path.dirname(os.path.abspath(name)))
-        try:
-            m = os.path.splitext(os.path.basename(name))[0]
-            module = importlib.import_module(m)
-        finally:
-            sys.path.pop(0)
-
-        return module
+        mod = imp.new_module(os.path.splitext(os.path.basename(name))[0])
+        with open(name) as f:
+            exec(f.read(), mod.__dict__, mod.__dict__)
+        return mod
 
     return importlib.import_module('dbusmock.templates.' + name)
 
 
 class DBusMockObject(dbus.service.Object):
diff --git a/tests/test_api.py b/tests/test_api.py
index b7b06f2..e346a6b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -590,10 +590,20 @@ def load(mock, parameters):
                 my_template.name, stdout=subprocess.PIPE)
             self.addCleanup(p_mock.wait)
             self.addCleanup(p_mock.terminate)
             self.addCleanup(p_mock.stdout.close)
 
+            # ensure that we don't use/write any .pyc files, they are dangerous
+            # in a world-writable directory like /tmp
+            self.assertFalse(os.path.exists(my_template.name + 'c'))
+            try:
+                from importlib.util import cache_from_source
+                self.assertFalse(os.path.exists(cache_from_source(my_template.name)))
+            except ImportError:
+                # python < 3.4
+                pass
+
         self.assertEqual(dbus_ultimate.Answer(), 42)
 
         # should appear in introspection
         xml = dbus_ultimate.Introspect()
         self.assertIn('<interface name="universe.Ultimate">', xml)

Vulnerable code:         sys.path.insert(0, os.path.dirname(os.path.abspath(name)))
        try:
            m = os.path.splitext(os.path.basename(name))[0]
            module = importlib.import_module(m)
        finally:
            sys.path.pop(0)

        return module
Fixed code: 0.15.1 (UNRELEASED)
-------------------
 - SECURITY FIX: When loading a template from an arbitrary file through the
   AddTemplate() D-Bus method call or DBusTestCase.spawn_server_template()
   Python method, don't create or use Python's *.pyc cached files. By tricking
   a user into loading a template from a world-writable directory like /tmp, an
   attacker could run arbitrary code with the user's privileges by putting a
   crafted .pyc file into that directory. Note that this is highly unlikely to
   actually appear in practice as custom dbusmock templates are usually shipped
   in project directories, not directly in world-writable directories.
   Thanks to Simon McVittie for discovering this!
   (LP: #1453815, CVE-2015-1326)

import imp
        mod = imp.new_module(os.path.splitext(os.path.basename(name))[0])
        with open(name) as f:
            exec(f.read(), mod.__dict__, mod.__dict__)
        return mod
            # ensure that we don't use/write any .pyc files, they are dangerous
            # in a world-writable directory like /tmp
            self.assertFalse(os.path.exists(my_template.name + 'c'))
            try:
                from importlib.util import cache_from_source
                self.assertFalse(os.path.exists(cache_from_source(my_template.name)))
            except ImportError:
                # python < 3.4
                pass