fix security issue #36564 (integrity error w/ RQLUniqueConstraint) by using a global lock serializing commit when some RQLUniqueConstraint is encountered. stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 08 Feb 2010 10:06:40 +0100
branchstable
changeset 4489 63128e8b9af9
parent 4488 e8e7f6a0e7b6
child 4490 d45cde54d464
child 4496 14cbf2570ce8
child 4502 1422589c35a0
fix security issue #36564 (integrity error w/ RQLUniqueConstraint) by using a global lock serializing commit when some RQLUniqueConstraint is encountered.
server/hooks.py
--- a/server/hooks.py	Mon Feb 08 09:57:25 2010 +0100
+++ b/server/hooks.py	Mon Feb 08 10:06:40 2010 +0100
@@ -8,6 +8,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from threading import Lock
 from datetime import datetime
 
 from cubicweb import UnknownProperty, ValidationError, BadConnectionId
@@ -25,6 +26,41 @@
 DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
                                 'wf_info_for', 'from_state', 'to_state'))
 
+_UNIQUE_CONSTRAINTS_LOCK = Lock()
+_UNIQUE_CONSTRAINTS_HOLDER = None
+
+class _ReleaseUniqueConstraintsHook(Operation):
+    def commit_event(self):
+        pass
+    def postcommit_event(self):
+        _release_unique_cstr_lock(self.session)
+    def rollback_event(self):
+        _release_unique_cstr_lock(self.session)
+
+def _acquire_unique_cstr_lock(session):
+    """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session.
+
+    This lock used to avoid potential integrity pb when checking
+    RQLUniqueConstraint in two different transactions, as explained in
+    http://intranet.logilab.fr/jpl/ticket/36564
+    """
+    global _UNIQUE_CONSTRAINTS_HOLDER
+    asession = session.actual_session()
+    if _UNIQUE_CONSTRAINTS_HOLDER is asession:
+        return
+    _UNIQUE_CONSTRAINTS_LOCK.acquire()
+    _UNIQUE_CONSTRAINTS_HOLDER = asession
+    # register operation responsible to release the lock on commit/rollback
+    _ReleaseUniqueConstraintsHook(asession)
+
+def _release_unique_cstr_lock(session):
+    global _UNIQUE_CONSTRAINTS_HOLDER
+    if _UNIQUE_CONSTRAINTS_HOLDER is session:
+        _UNIQUE_CONSTRAINTS_HOLDER = None
+        _UNIQUE_CONSTRAINTS_LOCK.release()
+    else:
+        assert _UNIQUE_CONSTRAINTS_HOLDER is None
+
 
 def relation_deleted(session, eidfrom, rtype, eidto):
     session.transaction_data.setdefault('pendingrelations', []).append(
@@ -216,6 +252,12 @@
         if eidto in pending:
             return
         for constraint in self.constraints:
+            # XXX
+            # * lock RQLConstraint as well?
+            # * use a constraint id to use per constraint lock and avoid
+            #   unnecessary commit serialization ?
+            if isinstance(constraint, RQLUniqueConstraint):
+                _acquire_unique_cstr_lock(self.session)
             try:
                 constraint.repo_check(self.session, eidfrom, rtype, eidto)
             except NotImplementedError: