[kupfer] Add a safer, pure-python ConservativeUnpickler in conspickle.py



commit 719078e130d6ceaf4ebbeb55237100379408856e
Author: Ulrik Sverdrup <ulrik sverdrup gmail com>
Date:   Tue Feb 9 14:54:21 2010 +0100

    Add a safer, pure-python ConservativeUnpickler in conspickle.py
    
    puid's use of pickle is not performance-critical, so using just
    python's pickle right now is fine, and it's more future-proof.
    
    We use safer defaults by explicitly denying almost all builtin
    function and classes. However, we have to allow all kupfer classes to
    be created.
    
    This change requires Python 2.6

 kupfer/conspickle.py |   34 +++++++++++++++++++++++++++++
 kupfer/puid.py       |   58 +++++++++++--------------------------------------
 2 files changed, 47 insertions(+), 45 deletions(-)
---
diff --git a/kupfer/conspickle.py b/kupfer/conspickle.py
new file mode 100644
index 0000000..3d2a6f6
--- /dev/null
+++ b/kupfer/conspickle.py
@@ -0,0 +1,34 @@
+import fnmatch
+import io
+import pickle
+import sys
+
+class universalset (object):
+	def __contains__(self, item):
+		return True
+
+class ConservativeUnpickler (pickle.Unpickler):
+	"An Unpickler that refuses to import new modules"
+	safe_modules = {
+		"__builtin__" : set(["set", "sum", "object"]),
+		"copy_reg" : set(["_reconstructor"]),
+		"kupfer.*" : universalset(),
+	}
+	@classmethod
+	def is_safe_symbol(cls, module, name):
+		for pattern in cls.safe_modules:
+			if fnmatch.fnmatchcase(module, pattern):
+				return name in cls.safe_modules[pattern]
+		return False
+
+	def find_class(self, module, name):
+		if module not in sys.modules:
+			raise pickle.UnpicklingError("Refusing to load module %s" % module)
+		if not self.is_safe_symbol(module, name):
+			raise pickle.UnpicklingError("Refusing unsafe %s.%s" % (module, name))
+		return pickle.Unpickler.find_class(self, module, name)
+
+	@classmethod
+	def loads(cls, pickledata):
+		unpickler = cls(io.BytesIO(pickledata))
+		return unpickler.load()
diff --git a/kupfer/puid.py b/kupfer/puid.py
index 28ce8ab..a206e53 100644
--- a/kupfer/puid.py
+++ b/kupfer/puid.py
@@ -1,22 +1,25 @@
 """
 Persistent Globally Unique Indentifiers for KupferObjects.
-"""
 
-from __future__ import with_statement
+Some objects are assigned identifiers by reference, some are assigned
+identifiers containing the whole object data (SerializedObject).
 
-import contextlib
-import cPickle as pickle
-import sys
+SerializedObject is a saved representation of a KupferObject, i.e. a
+data model user-level object.
 
-try:
-	from cStringIO import StringIO
-except ImportError:
-	from StringIO import StringIO
+We unpickle SerializedObjects in an especially conservative way: new
+module loading is always refused; this way, we avoid loading parts of
+the program that we didn't wish to activate.
+"""
+
+import contextlib
+import pickle
 
 from kupfer import pretty
 from kupfer.core import actioncompat
 from kupfer.core import qfurl
 from kupfer.core.sources import GetSourceController
+from kupfer.conspickle import ConservativeUnpickler
 
 __all__ = [
 	"SerializedObject", "SERIALIZABLE_ATTRIBUTE",
@@ -26,41 +29,6 @@ __all__ = [
 
 SERIALIZABLE_ATTRIBUTE = "serializable"
 
-"""
-SerializedObject is a saved representation of a KupferObject, i.e. a
-data model user-level object.
-
-We unpickle SerializedObjects in an especially conservative way: new
-module loading is always refused; this way, we avoid loading parts of
-the program that we didn't wish to activate.
-
-The implementation with Pure-Python pickle would look like::
-
-	class ConservativeUnpickler (pickle.Unpickler):
-		"An Unpickler that refuses to import new modules"
-		def find_class(self, module, name):
-			if module not in sys.modules:
-				raise ValueError("Plugin %s is not loaded" % module)
-			return pickle.Unpickler.find_class(self, module, name)
-
-		@classmethod
-		def loads(cls, pickledata):
-			unpickler = cls(StringIO(pickledata))
-			return unpickler.load()
-
-"""
-
-def _conservative_find_global(module, name):
-	if module not in sys.modules:
-		raise pickle.UnpicklingError("Refusing to load module %s" % module)
-	return getattr(sys.modules[module], name)
-
-def conservative_loads(pickledata):
-	"Unpickle, but refuse to import new modules"
-	unpickler = pickle.Unpickler(StringIO(pickledata))
-	unpickler.find_global = _conservative_find_global
-	return unpickler.load()
-
 
 class SerializedObject (object):
 	# treat the serializable attribute as a version number, defined on the class
@@ -71,7 +39,7 @@ class SerializedObject (object):
 		return (isinstance(other, type(self)) and self.data == other.data and
 		        self.version == other.version)
 	def reconstruct(self):
-		obj = conservative_loads(self.data)
+		obj = ConservativeUnpickler.loads(self.data)
 		if self.version != getattr(obj, SERIALIZABLE_ATTRIBUTE):
 			raise ValueError("Version mismatch for reconstructed %s" % obj)
 		return obj



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]