diff --git a/NEWS.txt b/NEWS.txt index 7a940d9b264f3f607caeae2545008ba283cdc612..1902a3084f5c6a8fb14d096c8a8ce8cadce077be 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,4 +1,21 @@ +Whats new in ZODB 3.8.2 +======================= + +Bugs Fixed: + +- Fixed vulnerabilities in the ZEO network protocol that allow: + + - CVE-2009-0668 Arbitrary Python code execution in ZODB ZEO storage servers + - CVE-2009-0669 Authentication bypass in ZODB ZEO storage servers + + The vulnerabilities only apply if you are using ZEO to share a + database among multiple applications or application instances and if + untrusted clients are able to connect to your ZEO servers. + +- Limit the number of object ids that can be allocated at once to + avoid running out of memory. + Whats new in ZODB 3.8.1 ======================= diff --git a/setup.py b/setup.py index 7048badc38ac48afb841af49444bcaac372d815f..3a7f7bb998a36927cdad79b915018aca3d1e005f 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ to application logic. ZODB includes features such as a plugable storage interface, rich transaction support, and undo. """ -VERSION = "3.8.1" +VERSION = "3.8.2" # The (non-obvious!) choices for the Trove Development Status line: # Development Status :: 5 - Production/Stable diff --git a/src/ZEO/StorageServer.py b/src/ZEO/StorageServer.py index 3653cbe373f4d69e52f6e8e1c126f5c9d561d98c..0d81c5b06c4051b034e9031768f436570471d768 100644 --- a/src/ZEO/StorageServer.py +++ b/src/ZEO/StorageServer.py @@ -111,7 +111,7 @@ class ZEOStorage: for func in self.extensions: self._extensions[func.func_name] = None - def finish_auth(self, authenticated): + def _finish_auth(self, authenticated): if not self.auth_realm: return 1 self.authenticated = authenticated @@ -421,6 +421,7 @@ class ZEOStorage: def new_oids(self, n=100): """Return a sequence of n new oids, where n defaults to 100""" + n = min(n, 100) if self.read_only: raise ReadOnlyError() if n <= 0: diff --git a/src/ZEO/auth/auth_digest.py b/src/ZEO/auth/auth_digest.py index fef0af1474e344777a81e9372c6f8a6e4bd0c223..a440cce4449aa4604baf2b85d7110aad402cf9de 100644 --- a/src/ZEO/auth/auth_digest.py +++ b/src/ZEO/auth/auth_digest.py @@ -121,7 +121,7 @@ class StorageClass(ZEOStorage): check = hexdigest("%s:%s" % (h_up, challenge)) if check == response: self.connection.setSessionKey(session_key(h_up, self._key_nonce)) - return self.finish_auth(check == response) + return self._finish_auth(check == response) extensions = [auth_get_challenge, auth_response] diff --git a/src/ZEO/tests/auth_plaintext.py b/src/ZEO/tests/auth_plaintext.py index 50d4bbdeec29c3e5ea97f943273e81092961e7e1..a5ba209399bb59f30bc017e3830caac76c88f589 100644 --- a/src/ZEO/tests/auth_plaintext.py +++ b/src/ZEO/tests/auth_plaintext.py @@ -41,7 +41,7 @@ class StorageClass(ZEOStorage): self.connection.setSessionKey(session_key(username, self.database.realm, password)) - return self.finish_auth(dbpw == password_dig) + return self._finish_auth(dbpw == password_dig) class PlaintextClient(Client): extensions = ["auth"] diff --git a/src/ZEO/zrpc/connection.py b/src/ZEO/zrpc/connection.py index 2a7a02d130755b4bbed92cbd7b85b6029c2dccf0..57f689b9f4d8854644b49f987ecf7b4a6357eab7 100644 --- a/src/ZEO/zrpc/connection.py +++ b/src/ZEO/zrpc/connection.py @@ -24,7 +24,7 @@ import traceback, time import ThreadedAsync from ZEO.zrpc import smac from ZEO.zrpc.error import ZRPCError, DisconnectedError -from ZEO.zrpc.marshal import Marshaller +from ZEO.zrpc.marshal import Marshaller, ServerMarshaller from ZEO.zrpc.trigger import trigger from ZEO.zrpc.log import short_repr, log from ZODB.loglevels import BLATHER, TRACE @@ -883,6 +883,7 @@ class ManagedServerConnection(Connection): def __init__(self, sock, addr, obj, mgr): self.mgr = mgr self.__super_init(sock, addr, obj, 'S') + self.marshal = ServerMarshaller() self.obj.notifyConnected(self) def handshake(self): diff --git a/src/ZEO/zrpc/marshal.py b/src/ZEO/zrpc/marshal.py index 4a65c090dfb3e8425703e84d1ae3bfaea08d6b2f..6c3379e290c55ed5bbdee2fec3cbaba4475b9186 100644 --- a/src/ZEO/zrpc/marshal.py +++ b/src/ZEO/zrpc/marshal.py @@ -52,6 +52,20 @@ class Marshaller: level=logging.ERROR) raise +class ServerMarshaller(Marshaller): + + def decode(self, msg): + """Decodes msg and returns its parts""" + unpickler = cPickle.Unpickler(StringIO(msg)) + unpickler.find_global = server_find_global + + try: + return unpickler.load() # msgid, flags, name, args + except: + log("can't decode message: %s" % short_repr(msg), + level=logging.ERROR) + raise + _globals = globals() _silly = ('__doc__',) @@ -78,3 +92,21 @@ def find_global(module, name): return r raise ZRPCError("Unsafe global: %s.%s" % (module, name)) + +def server_find_global(module, name): + """Helper for message unpickler""" + try: + m = __import__(module, _globals, _globals, _silly) + except ImportError, msg: + raise ZRPCError("import error %s: %s" % (module, msg)) + + try: + r = getattr(m, name) + except AttributeError: + raise ZRPCError("module %s has no global %s" % (module, name)) + + safe = getattr(r, '__no_side_effects__', 0) + if safe: + return r + + raise ZRPCError("Unsafe global: %s.%s" % (module, name))