Hello,

    The attached patch prevents asserts when Squid reuses the same
Checklist object for multiple ACL checks. I missed one use case when
adding Checklist reuse controls for trunk r12859 (Boolean ACLs). The bug
can be triggered by a combination of log_access and access_log ACLs, for
example.

Concurrent checks are not supported, but it is possible for the same
ACLChecklist to be used for a sequence of checks, alternating
fastCheck(void) and fastCheck(list) calls. We needed a
different/dedicated mechanism to detect check concurrency (added
ACLChecklist::occupied_), and we needed to preserve (and then restore)
pre-set accessList during fastCheck(list) checks.


HTH,

Alex.
Fix detection of concurrent ACLChecklist checks, avoiding !accessList asserts.

Concurrent checks are not supported, but it is possible for the same
ACLChecklist to be used for a sequence of checks, alternating fastCheck(void)
and fastCheck(list) calls. We needed a different/dedicated mechanism to detect
check concurrency (added ACLChecklist::occupied_), and we needed to preserve
(and then restore) pre-set accessList during fastCheck(list) checks.

=== modified file 'src/acl/Checklist.cc'
--- src/acl/Checklist.cc	2013-06-01 10:01:13 +0000
+++ src/acl/Checklist.cc	2013-06-07 16:47:47 +0000
@@ -43,40 +43,45 @@
         calcImplicitAnswer();
 
     cbdataReferenceDone(accessList);
     checkCallback(currentAnswer());
 }
 
 void
 ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
 {
     assert (!finished() && !asyncInProgress());
     finished_ = true;
     allow_ = finalAnswer;
     debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
 }
 
 /// Called first (and once) by all checks to initialize their state
 void
 ACLChecklist::preCheck(const char *what)
 {
     debugs(28, 3, HERE << this << " checking " << what);
+
+    // concurrent checks using the same Checklist are not supported
+    assert(!occupied_);
+    occupied_ = true;
+
     AclMatchedName = NULL;
     finished_ = false;
 }
 
 bool
 ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const ACL *child)
 {
     assert(current && child);
 
     // Remember the current tree location to prevent "async loop" cases where
     // the same child node wants to go async more than once.
     matchLoc_ = Breadcrumb(current, pos);
 
     // if there are any breadcrumbs left, then follow them on the way down
     bool result = false;
     if (matchPath.empty()) {
         result = child->matches(this);
     } else {
         const Breadcrumb top(matchPath.top());
         assert(child == top.parent);
@@ -131,48 +136,52 @@
     // yes, we must pause until the async callback calls resumeNonBlockingCheck
     asyncStage_ = asyncRunning;
     return true;
 }
 
 // ACLFilledChecklist overwrites this to unclock something before we
 // "delete this"
 void
 ACLChecklist::checkCallback(allow_t answer)
 {
     ACLCB *callback_;
     void *cbdata_;
     debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer);
 
     callback_ = callback;
     callback = NULL;
 
     if (cbdataReferenceValidDone(callback_data, &cbdata_))
         callback_(answer, cbdata_);
 
+    // not really meaningful just before delete, but here for completeness sake
+    occupied_ = false;
+
     delete this;
 }
 
 ACLChecklist::ACLChecklist() :
         accessList (NULL),
         callback (NULL),
         callback_data (NULL),
         asyncCaller_(false),
+        occupied_(false),
         finished_(false),
         allow_(ACCESS_DENIED),
         asyncStage_(asyncNone),
         state_(NullState::Instance())
 {
 }
 
 ACLChecklist::~ACLChecklist()
 {
     assert (!asyncInProgress());
 
     cbdataReferenceDone(accessList);
 
     debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
 }
 
 ACLChecklist::NullState *
 ACLChecklist::NullState::Instance()
 {
     return &_instance;
@@ -270,87 +279,91 @@
     if (matchPath.empty()) {
         result = accessList->matches(this);
     } else {
         const Breadcrumb top(matchPath.top());
         matchPath.pop();
         result = top.parent->resumeMatchingAt(this, top.position);
     }
 
     if (result) // the entire tree matched
         markFinished(accessList->winningAction(), "match");
 }
 
 allow_t const &
 ACLChecklist::fastCheck(const Acl::Tree * list)
 {
     PROF_start(aclCheckFast);
 
     preCheck("fast ACLs");
     asyncCaller_ = false;
 
-    // This call is not compatible with a pre-set accessList because we cannot
-    // tell whether this Checklist is used by some other concurent call, which
-    // is not supported.
-    assert(!accessList);
+    // Concurrent checks are not supported, but sequential checks are, and they
+    // may use a mixture of fastCheck(void) and fastCheck(list) calls.
+    const Acl::Tree * const savedList = accessList;
+
     accessList = cbdataReference(list);
 
     // assume DENY/ALLOW on mis/matches due to action-free accessList
     // matchAndFinish() takes care of the ALLOW case
     if (accessList && cbdataReferenceValid(accessList))
         matchAndFinish(); // calls markFinished() on success
     if (!finished())
         markFinished(ACCESS_DENIED, "ACLs failed to match");
 
     cbdataReferenceDone(accessList);
+    accessList = savedList;
+    occupied_ = false;
     PROF_stop(aclCheckFast);
     return currentAnswer();
 }
 
 /* Warning: do not cbdata lock this here - it
  * may be static or on the stack
  */
 allow_t const &
 ACLChecklist::fastCheck()
 {
     PROF_start(aclCheckFast);
 
     preCheck("fast rules");
     asyncCaller_ = false;
 
     debugs(28, 5, "aclCheckFast: list: " << accessList);
     const Acl::Tree *acl = cbdataReference(accessList);
     if (acl != NULL && cbdataReferenceValid(acl)) {
         matchAndFinish(); // calls markFinished() on success
 
         // if finished (on a match or in exceptional cases), stop
         if (finished()) {
             cbdataReferenceDone(acl);
+            occupied_ = false;
             PROF_stop(aclCheckFast);
             return currentAnswer();
         }
 
         // fall through for mismatch handling
     }
 
     // There were no rules to match or no rules matched
     calcImplicitAnswer();
     cbdataReferenceDone(acl);
+    occupied_ = false;
     PROF_stop(aclCheckFast);
 
     return currentAnswer();
 }
 
 /// When no rules matched, the answer is the inversion of the last rule
 /// action (or ACCESS_DUNNO if the reversal is not possible).
 void
 ACLChecklist::calcImplicitAnswer()
 {
     const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ?
                                accessList->lastAction() : allow_t(ACCESS_DUNNO);
     allow_t implicitRuleAnswer = ACCESS_DUNNO;
     if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
         implicitRuleAnswer = ACCESS_ALLOWED;
     else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
         implicitRuleAnswer = ACCESS_DENIED;
     // else we saw no rules and will respond with ACCESS_DUNNO
 
     debugs(28, 3, HERE << this << " NO match found, last action " <<

=== modified file 'src/acl/Checklist.h'
--- src/acl/Checklist.h	2013-05-28 14:28:15 +0000
+++ src/acl/Checklist.h	2013-06-07 16:47:47 +0000
@@ -207,36 +207,37 @@
         Breadcrumb(): parent(NULL) {}
         Breadcrumb(const Acl::InnerNode *aParent, Acl::Nodes::const_iterator aPos): parent(aParent), position(aPos) {}
         bool operator ==(const Breadcrumb &b) const { return parent == b.parent && (!parent || position == b.position); }
         bool operator !=(const Breadcrumb &b) const { return !this->operator ==(b); }
         void clear() { parent = NULL; }
         const Acl::InnerNode *parent; ///< intermediate node in the ACL tree
         Acl::Nodes::const_iterator position; ///< child position inside parent
     };
 
     /// possible outcomes when trying to match a single ACL node in a list
     typedef enum { nmrMatch, nmrMismatch, nmrFinished, nmrNeedsAsync }
     NodeMatchingResult;
 
     /// prepare for checking ACLs; called once per check
     void preCheck(const char *what);
     bool prepNonBlocking();
     void completeNonBlocking();
     void calcImplicitAnswer();
 
     bool asyncCaller_; ///< whether the caller supports async/slow ACLs
+    bool occupied_; ///< whether a check (fast or non-blocking) is in progress
     bool finished_;
     allow_t allow_;
 
     enum AsyncStage { asyncNone, asyncStarting, asyncRunning, asyncFailed };
     AsyncStage asyncStage_;
     AsyncState *state_;
     Breadcrumb matchLoc_; ///< location of the node running matches() now
     Breadcrumb asyncLoc_; ///< currentNode_ that called goAsync()
 
     bool callerGone();
 
     /// suspended (due to an async lookup) matches() in the ACL tree
     std::stack<Breadcrumb> matchPath;
 };
 
 #endif /* SQUID_ACLCHECKLIST_H */

Reply via email to