Patch applied to trunk as rev.14227
I am also attaching the squid-3.5 version of the patch. The trunk patch
does not apply cleanly.
On 08/15/2015 03:21 AM, Amos Jeffries wrote:
On 15/08/2015 2:41 a.m., Tsantilas Christos wrote:
Hi all,
The wiki pages are fixed.
Is it OK to commit this patch?
+1 from me.
Please remove the "ACLChecklist: " part from the new debugs in
ACLChecklist::bannedAction() during commit.
Amos
Ignore impossible SSL bumping actions, as intended and documented.
According to Squid wiki: "Some actions are not possible during
certain processing steps. During a given processing step, Squid
ignores ssl_bump lines with impossible actions". The distributed
squid.conf.documented has similar text.
Current Squid violates the above rule. Squid considers all actions,
and if an impossible action matches first, Squid guesses what the
true configuration intent was. Squid may guess wrong. For example,
depending on the transaction, Squid may guess that a matching
stare or peek action during bumping step3 means "bump", breaking
peeked connections that cannot be bumped.
This unintended but gross configuration semantics violation remained
invisible until bug 4237, probably because most configurations in
most environments either worked around the problem (where admins
experimented to "make it work") or did not result in visible
errors (where Squid guesses did not lead to terminated connections).
While configuration workarounds are possible, the current
implementation is very wrong and leads to overly complex and, hence,
often wrong configurations. It is also nearly impossible to document
accurately because the guessing logic depends on too many factors.
To fix this, we add an action filtering/banning mechanism to Squid
ACL code. This mechanism is then used to:
- ban client-first and server-first on bumping steps 2 and 3.
- ban peek and stare actions on bumping step 3.
- ban splice on step3 if stare is selected on step2 and
Squid cannot splice the SSL connection any more.
- ban bump on step3 if peek is selected on step2 and
Squid cannot bump the connection any more.
The same action filtering mechanism may be useful for other
ACL-driven directives with state-dependent custom actions.
This change adds a runtime performance overhead of a single virtual
method call to all ORed ACLs that do not use banned actions.
That method itself just returns false unless the ACL represents
a whole directive rule. In the latter case, an std::vector size()
is also checked. It is possible to avoid this overhead by adding
a boolean "I may ban actions" flag to Acl::OrNode, but we decided
the small performance harm is not worth the extra code to set
that flag.
This is a Measurement Factory project.
=== modified file 'src/acl/Acl.h'
--- src/acl/Acl.h 2015-01-13 09:13:49 +0000
+++ src/acl/Acl.h 2015-08-19 08:42:04 +0000
@@ -150,52 +150,56 @@
virtual bool requiresReply() const;
};
/// \ingroup ACLAPI
typedef enum {
// Authorization ACL result states
ACCESS_DENIED,
ACCESS_ALLOWED,
ACCESS_DUNNO,
// Authentication ACL result states
ACCESS_AUTH_REQUIRED, // Missing Credentials
} aclMatchCode;
/// \ingroup ACLAPI
/// ACL check answer; TODO: Rename to Acl::Answer
class allow_t
{
public:
// not explicit: allow "aclMatchCode to allow_t" conversions (for now)
- allow_t(const aclMatchCode aCode): code(aCode), kind(0) {}
+ allow_t(const aclMatchCode aCode, int aKind = 0): code(aCode), kind(aKind) {}
allow_t(): code(ACCESS_DUNNO), kind(0) {}
bool operator ==(const aclMatchCode aCode) const {
return code == aCode;
}
bool operator !=(const aclMatchCode aCode) const {
return !(*this == aCode);
}
+ bool operator ==(const allow_t allow) const {
+ return code == allow.code && kind == allow.kind;
+ }
+
operator aclMatchCode() const {
return code;
}
aclMatchCode code; ///< ACCESS_* code
int kind; ///< which custom access list verb matched
};
inline std::ostream &
operator <<(std::ostream &o, const allow_t a)
{
switch (a) {
case ACCESS_DENIED:
o << "DENIED";
break;
case ACCESS_ALLOWED:
o << "ALLOWED";
break;
case ACCESS_DUNNO:
o << "DUNNO";
=== modified file 'src/acl/BoolOps.cc'
--- src/acl/BoolOps.cc 2015-01-13 09:13:49 +0000
+++ src/acl/BoolOps.cc 2015-08-19 08:42:04 +0000
@@ -98,47 +98,55 @@
Acl::AndNode::parse()
{
// Not implemented: AndNode cannot be configured directly. See Acl::AllOf.
assert(false);
}
/* Acl::OrNode */
char const *
Acl::OrNode::typeString() const
{
return "any-of";
}
ACL *
Acl::OrNode::clone() const
{
return new OrNode;
}
+bool
+Acl::OrNode::bannedAction(ACLChecklist *, Nodes::const_iterator) const
+{
+ return false;
+}
+
int
Acl::OrNode::doMatch(ACLChecklist *checklist, Nodes::const_iterator start) const
{
lastMatch_ = nodes.end();
// find the first node that matches, but stop if things go wrong
for (Nodes::const_iterator i = start; i != nodes.end(); ++i) {
+ if (bannedAction(checklist, i))
+ continue;
if (checklist->matchChild(this, i, *i)) {
lastMatch_ = i;
return 1;
}
if (!checklist->keepMatching())
return -1; // suspend on async calls and stop on failures
}
// zero and not one on empty because in math empty sum equals zero
return 0; // all nodes mismatched
}
void
Acl::OrNode::parse()
{
// Not implemented: OrNode cannot be configured directly. See Acl::AnyOf.
assert(false);
}
=== modified file 'src/acl/BoolOps.h'
--- src/acl/BoolOps.h 2015-01-13 09:13:49 +0000
+++ src/acl/BoolOps.h 2015-08-19 08:43:37 +0000
@@ -47,37 +47,41 @@
MEMPROXY_CLASS(AndNode);
/* ACL API */
virtual char const *typeString() const;
virtual ACL *clone() const;
virtual void parse();
private:
virtual int doMatch(ACLChecklist *checklist, Nodes::const_iterator start) const;
};
MEMPROXY_CLASS_INLINE(Acl::AndNode);
/// An inner ACL expression tree node representing a boolean disjuction (OR)
/// operator applied to a list of child tree nodes.
/// For example, conditions expressed by multiple http_access lines are ORed.
class OrNode: public InnerNode
{
public:
MEMPROXY_CLASS(OrNode);
+ /// whether the given rule should be excluded from matching tests based
+ /// on its action
+ virtual bool bannedAction(ACLChecklist *, Nodes::const_iterator) const;
+
/* ACL API */
virtual char const *typeString() const;
virtual ACL *clone() const;
virtual void parse();
protected:
mutable Nodes::const_iterator lastMatch_;
private:
virtual int doMatch(ACLChecklist *checklist, Nodes::const_iterator start) const;
};
MEMPROXY_CLASS_INLINE(Acl::OrNode);
} // namespace Acl
#endif /* SQUID_ACL_LOGIC_H */
=== modified file 'src/acl/Checklist.cc'
--- src/acl/Checklist.cc 2015-01-13 09:13:49 +0000
+++ src/acl/Checklist.cc 2015-08-19 08:42:04 +0000
@@ -1,36 +1,38 @@
/*
* Copyright (C) 1996-2015 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
/* DEBUG: section 28 Access Control */
#include "squid.h"
#include "acl/Checklist.h"
#include "acl/Tree.h"
#include "Debug.h"
#include "profiler/Profiler.h"
+#include <algorithm>
+
/// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
bool
ACLChecklist::prepNonBlocking()
{
assert(accessList);
if (callerGone()) {
checkCallback(ACCESS_DUNNO); // the answer does not really matter
return false;
}
/** \par
* If the accessList is no longer valid (i.e. its been
* freed because of a reconfigure), then bail with ACCESS_DUNNO.
*/
if (!cbdataReferenceValid(accessList)) {
cbdataReferenceDone(accessList);
debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
checkCallback(ACCESS_DUNNO);
@@ -374,20 +376,34 @@
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 " <<
lastAction << " so returning " << implicitRuleAnswer);
markFinished(implicitRuleAnswer, "implicit rule won");
}
bool
ACLChecklist::callerGone()
{
return !cbdataReferenceValid(callback_data);
}
+bool
+ACLChecklist::bannedAction(const allow_t &action) const
+{
+ const bool found = std::find(bannedActions_.begin(), bannedActions_.end(), action) != bannedActions_.end();
+ debugs(28, 5, "Action '" << action << "/" << action.kind << (found ? " is " : "is not") << " banned");
+ return found;
+}
+
+void
+ACLChecklist::banAction(const allow_t &action)
+{
+ bannedActions_.push_back(action);
+}
+
=== modified file 'src/acl/Checklist.h'
--- src/acl/Checklist.h 2015-01-13 09:13:49 +0000
+++ src/acl/Checklist.h 2015-08-19 08:42:04 +0000
@@ -1,33 +1,34 @@
/*
* Copyright (C) 1996-2015 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef SQUID_ACLCHECKLIST_H
#define SQUID_ACLCHECKLIST_H
#include "acl/InnerNode.h"
#include <stack>
+#include <vector>
/// ACL checklist callback
typedef void ACLCB(allow_t, void *);
/** \ingroup ACLAPI
Base class for maintaining Squid and transaction state for access checks.
Provides basic ACL checking methods. Its only child, ACLFilledChecklist,
keeps the actual state data. The split is necessary to avoid exposing
all ACL-related code to virtually Squid data types. */
class ACLChecklist
{
public:
/**
* State class.
* This abstract class defines the behaviour of
* async lookups - which can vary for different ACL types.
* Today, every state object must be a singleton.
* See NULLState for an example.
@@ -135,40 +136,45 @@
/// Otherwise, returns false; the caller is expected to handle the failure.
bool goAsync(AsyncState *);
/// Matches (or resumes matching of) a child node while maintaning
/// resumption breadcrumbs if a [grand]child node goes async.
bool matchChild(const Acl::InnerNode *parent, Acl::Nodes::const_iterator pos, const ACL *child);
/// Whether we should continue to match tree nodes or stop/pause.
bool keepMatching() const { return !finished() && !asyncInProgress(); }
/// whether markFinished() was called
bool finished() const { return finished_; }
/// async call has been started and has not finished (or failed) yet
bool asyncInProgress() const { return asyncStage_ != asyncNone; }
/// called when no more ACLs should be checked; sets the final answer and
/// prints a debugging message explaining the reason for that answer
void markFinished(const allow_t &newAnswer, const char *reason);
const allow_t ¤tAnswer() const { return allow_; }
+ /// whether the action is banned or not
+ bool bannedAction(const allow_t &action) const;
+ /// add action to the list of banned actions
+ void banAction(const allow_t &action);
+
// XXX: ACLs that need request or reply have to use ACLFilledChecklist and
// should do their own checks so that we do not have to povide these two
// for ACL::checklistMatches to use
virtual bool hasRequest() const = 0;
virtual bool hasReply() const = 0;
private:
/// Calls non-blocking check callback with the answer and destroys self.
void checkCallback(allow_t answer);
void matchAndFinish();
void changeState(AsyncState *);
AsyncState *asyncState() const;
public:
const Acl::Tree *accessList;
ACLCB *callback;
void *callback_data;
@@ -200,24 +206,26 @@
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()
unsigned asyncLoopDepth_; ///< how many times the current async state has resumed
bool callerGone();
/// suspended (due to an async lookup) matches() in the ACL tree
std::stack<Breadcrumb> matchPath;
+ /// the list of actions which must ignored during acl checks
+ std::vector<allow_t> bannedActions_;
};
#endif /* SQUID_ACLCHECKLIST_H */
=== modified file 'src/acl/Tree.cc'
--- src/acl/Tree.cc 2015-01-13 09:13:49 +0000
+++ src/acl/Tree.cc 2015-08-19 08:42:04 +0000
@@ -1,29 +1,30 @@
/*
* Copyright (C) 1996-2015 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
+#include "acl/Checklist.h"
#include "acl/Tree.h"
#include "wordlist.h"
CBDATA_NAMESPACED_CLASS_INIT(Acl, Tree);
allow_t
Acl::Tree::winningAction() const
{
return actionAt(lastMatch_ - nodes.begin());
}
allow_t
Acl::Tree::lastAction() const
{
if (actions.empty())
return ACCESS_DUNNO;
return actions.back();
}
/// computes action that corresponds to the position of the matched rule
@@ -64,20 +65,30 @@
typedef Nodes::const_iterator NCI;
for (NCI node = nodes.begin(); node != nodes.end(); ++node) {
text.push_back(SBuf(prefix));
if (action != actions.end()) {
const char *act = convert ? convert[action->kind] :
(*action == ACCESS_ALLOWED ? "allow" : "deny");
text.push_back(act?SBuf(act):SBuf("???"));
++action;
}
// temp is needed until c++11 move constructor
SBufList temp = (*node)->dump();
text.splice(text.end(), temp);
text.push_back(SBuf("\n"));
}
return text;
}
+bool
+Acl::Tree::bannedAction(ACLChecklist *checklist, Nodes::const_iterator node) const
+{
+ if (actions.size()) {
+ assert(actions.size() == nodes.size());
+ const Nodes::size_type pos = node - nodes.begin();
+ return checklist->bannedAction(actions.at(pos));
+ }
+ return false;
+}
=== modified file 'src/acl/Tree.h'
--- src/acl/Tree.h 2015-01-13 09:13:49 +0000
+++ src/acl/Tree.h 2015-08-19 08:42:04 +0000
@@ -19,36 +19,38 @@
/// unique properties: cbdata protection and optional rule actions.
class Tree: public OrNode
{
public:
/// dumps <name, action, rule, new line> tuples
/// action.kind is mapped to a string using the supplied conversion table
typedef const char **ActionToString;
SBufList treeDump(const char *name, const ActionToString &convert) const;
/// Returns the corresponding action after a successful tree match.
allow_t winningAction() const;
/// what action to use if no nodes matched
allow_t lastAction() const;
/// appends and takes control over the rule with a given action
void add(ACL *rule, const allow_t &action);
void add(ACL *rule); ///< same as InnerNode::add()
protected:
+ /// Acl::OrNode API
+ virtual bool bannedAction(ACLChecklist *, Nodes::const_iterator) const override;
allow_t actionAt(const Nodes::size_type pos) const;
/// if not empty, contains actions corresponding to InnerNode::nodes
typedef std::vector<allow_t> Actions;
Actions actions;
private:
// XXX: We should use refcounting instead, but it requires making ACLs
// refcounted as well. Otherwise, async lookups will reach deleted ACLs.
CBDATA_CLASS2(Tree);
};
} // namespace Acl
#endif /* SQUID_ACL_TREE_H */
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2015-08-08 04:04:45 +0000
+++ src/client_side.cc 2015-08-19 08:42:04 +0000
@@ -4297,46 +4297,41 @@
switchedToHttps_ = true;
SSL *ssl = fd_table[clientConnection->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
bio->hold(true);
}
void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
{
ConnStateData *connState = (ConnStateData *) data;
// if the connection is closed or closing, just return.
if (!connState->isOpen())
return;
debugs(33, 5, "Answer: " << answer << " kind:" << answer.kind);
assert(connState->serverBump());
Ssl::BumpMode bumpAction;
if (answer == ACCESS_ALLOWED) {
- if (answer.kind == Ssl::bumpNone)
- bumpAction = Ssl::bumpSplice;
- else if (answer.kind == Ssl::bumpClientFirst || answer.kind == Ssl::bumpServerFirst)
- bumpAction = Ssl::bumpBump;
- else
- bumpAction = (Ssl::BumpMode)answer.kind;
+ bumpAction = (Ssl::BumpMode)answer.kind;
} else
bumpAction = Ssl::bumpSplice;
connState->serverBump()->act.step2 = bumpAction;
connState->sslBumpMode = bumpAction;
if (bumpAction == Ssl::bumpTerminate) {
connState->clientConnection->close();
} else if (bumpAction != Ssl::bumpSplice) {
connState->startPeekAndSpliceDone();
} else {
//Normally we can splice here, because we just got client hello message
SSL *ssl = fd_table[connState->clientConnection->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
MemBuf const &rbuf = bio->rBufData();
debugs(83,5, "Bio for " << connState->clientConnection << " read " << rbuf.contentSize() << " helo bytes");
// Do splice:
fd_table[connState->clientConnection->fd].read_method = &default_read_method;
fd_table[connState->clientConnection->fd].write_method = &default_write_method;
@@ -4352,40 +4347,43 @@
connState->in.buf.append(rbuf.content(), rbuf.contentSize());
ClientSocketContext::Pointer context = connState->getCurrentContext();
ClientHttpRequest *http = context->http;
tunnelStart(http, &http->out.size, &http->al->http.code, http->al);
}
}
}
void
ConnStateData::startPeekAndSpliceDone()
{
// This is the Step2 of the SSL bumping
assert(sslServerBump);
if (sslServerBump->step == Ssl::bumpStep1) {
sslServerBump->step = Ssl::bumpStep2;
// Run a accessList check to check if want to splice or continue bumping
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL);
//acl_checklist->src_addr = params.conn->remote;
//acl_checklist->my_addr = s->s;
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpClientFirst));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpServerFirst));
acl_checklist->nonBlockingCheck(httpsSslBumpStep2AccessCheckDone, this);
return;
}
FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
}
void
ConnStateData::doPeekAndSpliceStep()
{
SSL *ssl = fd_table[clientConnection->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
assert(b);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
debugs(33, 5, "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl));
bio->hold(false);
Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0);
switchedToHttps_ = true;
=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc 2015-08-01 05:59:31 +0000
+++ src/ssl/PeerConnector.cc 2015-08-19 08:46:07 +0000
@@ -371,61 +371,65 @@
{
Ssl::PeerConnector *peerConnect = (Ssl::PeerConnector *) data;
peerConnect->checkForPeekAndSpliceDone((Ssl::BumpMode)answer.kind);
}
void
Ssl::PeerConnector::checkForPeekAndSplice()
{
// Mark Step3 of bumping
if (request->clientConnectionManager.valid()) {
if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
serverBump->step = Ssl::bumpStep3;
}
}
handleServerCertificate();
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(
::Config.accessList.ssl_bump,
request.getRaw(), NULL);
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpPeek));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpStare));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpClientFirst));
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpServerFirst));
+ SSL *ssl = fd_table[serverConn->fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ if (!srvBio->canSplice())
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpSplice));
+ if (!srvBio->canBump())
+ acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpBump));
acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSpliceDone, this);
}
void
Ssl::PeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
{
SSL *ssl = fd_table[serverConn->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
debugs(83,5, "Will check for peek and splice on FD " << serverConn->fd);
Ssl::BumpMode finalAction = action;
- // adjust the final bumping mode if needed
- if (finalAction < Ssl::bumpSplice)
- finalAction = Ssl::bumpBump;
-
- if (finalAction == Ssl::bumpSplice && !srvBio->canSplice())
- finalAction = Ssl::bumpBump;
- else if (finalAction == Ssl::bumpBump && !srvBio->canBump())
- finalAction = Ssl::bumpSplice;
-
+ Must(finalAction == Ssl::bumpSplice || finalAction == Ssl::bumpBump || finalAction == Ssl::bumpTerminate);
// Record final decision
if (request->clientConnectionManager.valid()) {
request->clientConnectionManager->sslBumpMode = finalAction;
request->clientConnectionManager->serverBump()->act.step3 = finalAction;
}
if (finalAction == Ssl::bumpTerminate) {
serverConn->close();
clientConn->close();
} else if (finalAction != Ssl::bumpSplice) {
//Allow write, proceed with the connection
srvBio->holdWrite(false);
srvBio->recordInput(false);
Comm::SetSelect(serverConn->fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd);
} else {
splice = true;
// Ssl Negotiation stops here. Last SSL checks for valid certificates
// and if done, switch to tunnel mode
if (sslFinalized())
_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev