]> git.ipfire.org Git - people/arne_f/ipfire-2.x.git/commitdiff
squid: Apply fix for Squid Advisory SQUID-2015:2
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 9 Jul 2015 10:29:37 +0000 (12:29 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 9 Jul 2015 11:10:38 +0000 (13:10 +0200)
Squid configured with cache_peer and operating on explicit proxy
traffic does not correctly handle CONNECT method peer responses.

The bug is important because it allows remote clients to bypass
security in an explicit gateway proxy.

However, the bug is exploitable only if you have configured
cache_peer to receive CONNECT requests.

  http://www.squid-cache.org/Advisories/SQUID-2015_2.txt

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
config/rootfiles/core/92/filelists/squid [new symlink]
lfs/squid
src/patches/squid-3.4-13225.patch [new file with mode: 0644]

diff --git a/config/rootfiles/core/92/filelists/squid b/config/rootfiles/core/92/filelists/squid
new file mode 120000 (symlink)
index 0000000..2dc8372
--- /dev/null
@@ -0,0 +1 @@
+../../../common/squid
\ No newline at end of file
index d4fc4c5a13682a0d04cb49230694f312d171512a..a338660bb63b7499fe5125ff7d034e50882a9c18 100644 (file)
--- a/lfs/squid
+++ b/lfs/squid
@@ -70,6 +70,7 @@ $(subst %,%_MD5,$(objects)) :
 $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects))
        @$(PREBUILD)
        @rm -rf $(DIR_APP) && cd $(DIR_SRC) && tar xaf $(DIR_DL)/$(DL_FILE)
+       cd $(DIR_APP) && patch -Np0 < $(DIR_SRC)/src/patches/squid-3.4-13225.patch
        cd $(DIR_APP) && ./configure \
                --prefix=/usr \
                --sysconfdir=/etc/squid \
diff --git a/src/patches/squid-3.4-13225.patch b/src/patches/squid-3.4-13225.patch
new file mode 100644 (file)
index 0000000..9ccec5e
--- /dev/null
@@ -0,0 +1,284 @@
+------------------------------------------------------------
+revno: 13225
+revision-id: squid3@treenet.co.nz-20150709032133-qg1patn5zngt4o4h
+parent: squid3@treenet.co.nz-20150501100500-3utkhrao1yrd8ig6
+author: Alex Rousskov <rousskov@measurement-factory.com>
+committer: Amos Jeffries <squid3@treenet.co.nz>
+branch nick: 3.4
+timestamp: Wed 2015-07-08 20:21:33 -0700
+message:
+  Do not blindly forward cache peer CONNECT responses.
+  
+  Squid blindly forwards cache peer CONNECT responses to clients. This
+  may break things if the peer responds with something like HTTP 403
+  (Forbidden) and keeps the connection with Squid open:
+    -  The client application issues a CONNECT request.
+    -  Squid forwards this request to a cache peer.
+    -  Cache peer correctly responds back with a "403 Forbidden".
+    -  Squid does not parse cache peer response and
+       just forwards it as if it was a Squid response to the client.
+    -  The TCP connections are not closed.
+  
+  At this stage, Squid is unaware that the CONNECT request has failed. All
+  subsequent requests on the user agent TCP connection are treated as
+  tunnelled traffic. Squid is forwarding these requests to the peer on the
+  TCP connection previously used for the 403-ed CONNECT request, without
+  proper processing. The additional headers which should have been applied
+  by Squid to these requests are not applied, and the requests are being
+  forwarded to the cache peer even though the Squid configuration may
+  state that these requests must go directly to the origin server.
+  
+  This fixes Squid to parse cache peer responses, and if an error response
+  found, respond with "502 Bad Gateway" to the client and close the
+  connections.
+------------------------------------------------------------
+# Bazaar merge directive format 2 (Bazaar 0.90)
+# revision_id: squid3@treenet.co.nz-20150709032133-qg1patn5zngt4o4h
+# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.4
+# testament_sha1: 6cbce093f30c8a09173eb610eaa423c7c305ff23
+# timestamp: 2015-07-09 03:40:35 +0000
+# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.4
+# base_revision_id: squid3@treenet.co.nz-20150501100500-\
+#   3utkhrao1yrd8ig6
+# 
+# Begin patch
+=== modified file 'src/tunnel.cc'
+--- src/tunnel.cc      2014-04-26 10:58:22 +0000
++++ src/tunnel.cc      2015-07-09 03:21:33 +0000
+@@ -122,6 +122,10 @@
+                  (request->flags.interceptTproxy || request->flags.intercepted));
+     }
++    /// Sends "502 Bad Gateway" error response to the client,
++    /// if it is waiting for Squid CONNECT response, closing connections.
++    void informUserOfPeerError(const char *errMsg);
++
+     class Connection
+     {
+@@ -139,13 +143,14 @@
+         void error(int const xerrno);
+         int debugLevelForError(int const xerrno) const;
+-        /// handles a non-I/O error associated with this Connection
+-        void logicError(const char *errMsg);
+         void closeIfOpen();
+         void dataSent (size_t amount);
++        /// writes 'b' buffer, setting the 'writer' member to 'callback'.
++        void write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func);
+         int len;
+         char *buf;
+         int64_t *size_ptr;            /* pointer to size in an ConnStateData for logging */
++        AsyncCall::Pointer writer; ///< pending Comm::Write callback
+         Comm::ConnectionPointer conn;    ///< The currently connected connection.
+@@ -195,13 +200,14 @@
+     TunnelStateData *tunnelState = (TunnelStateData *)params.data;
+     debugs(26, 3, HERE << tunnelState->server.conn);
+     tunnelState->server.conn = NULL;
++    tunnelState->server.writer = NULL;
+     if (tunnelState->noConnections()) {
+         delete tunnelState;
+         return;
+     }
+-    if (!tunnelState->server.len) {
++    if (!tunnelState->client.writer) {
+         tunnelState->client.conn->close();
+         return;
+     }
+@@ -213,13 +219,14 @@
+     TunnelStateData *tunnelState = (TunnelStateData *)params.data;
+     debugs(26, 3, HERE << tunnelState->client.conn);
+     tunnelState->client.conn = NULL;
++    tunnelState->client.writer = NULL;
+     if (tunnelState->noConnections()) {
+         delete tunnelState;
+         return;
+     }
+-    if (!tunnelState->client.len) {
++    if (!tunnelState->server.writer) {
+         tunnelState->server.conn->close();
+         return;
+     }
+@@ -343,6 +350,23 @@
+         handleConnectResponse(len);
+ }
++void
++TunnelStateData::informUserOfPeerError(const char *errMsg)
++{
++    server.len = 0;
++    if (!clientExpectsConnectResponse()) {
++        // closing the connection is the best we can do here
++        debugs(50, 3, server.conn << " closing on error: " << errMsg);
++        server.conn->close();
++        return;
++    }
++    ErrorState *err  = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw());
++    err->callback = tunnelErrorComplete;
++    err->callback_data = this;
++    *status_ptr = Http::scBadGateway;
++    errorSend(http->getConn()->clientConnection, err);
++}
++
+ /* Read from client side and queue it for writing to the server */
+ void
+ TunnelStateData::ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data)
+@@ -374,7 +398,7 @@
+     const bool parsed = rep.parse(connectRespBuf, eof, &parseErr);
+     if (!parsed) {
+         if (parseErr > 0) { // unrecoverable parsing error
+-            server.logicError("malformed CONNECT response from peer");
++            informUserOfPeerError("malformed CONNECT response from peer");
+             return;
+         }
+@@ -383,7 +407,7 @@
+         assert(!parseErr);
+         if (!connectRespBuf->hasSpace()) {
+-            server.logicError("huge CONNECT response from peer");
++            informUserOfPeerError("huge CONNECT response from peer");
+             return;
+         }
+@@ -397,7 +421,8 @@
+     // bail if we did not get an HTTP 200 (Connection Established) response
+     if (rep.sline.status() != Http::scOkay) {
+-        server.logicError("unsupported CONNECT response status code");
++        // if we ever decide to reuse the peer connection, we must extract the error response first
++        informUserOfPeerError("unsupported CONNECT response status code");
+         return;
+     }
+@@ -416,13 +441,6 @@
+ }
+ void
+-TunnelStateData::Connection::logicError(const char *errMsg)
+-{
+-    debugs(50, 3, conn << " closing on error: " << errMsg);
+-    conn->close();
+-}
+-
+-void
+ TunnelStateData::Connection::error(int const xerrno)
+ {
+     /* XXX fixme xstrerror and xerrno... */
+@@ -517,7 +535,7 @@
+     debugs(26, 3, HERE << "Schedule Write");
+     AsyncCall::Pointer call = commCbCall(5,5, "TunnelBlindCopyWriteHandler",
+                                          CommIoCbPtrFun(completion, this));
+-    Comm::Write(to.conn, from.buf, len, call, NULL);
++    to.write(from.buf, len, call, NULL);
+ }
+ /* Writes data from the client buffer to the server side */
+@@ -526,6 +544,7 @@
+ {
+     TunnelStateData *tunnelState = (TunnelStateData *)data;
+     assert (cbdataReferenceValid (tunnelState));
++    tunnelState->server.writer = NULL;
+     tunnelState->writeServerDone(buf, len, flag, xerrno);
+ }
+@@ -575,6 +594,7 @@
+ {
+     TunnelStateData *tunnelState = (TunnelStateData *)data;
+     assert (cbdataReferenceValid (tunnelState));
++    tunnelState->client.writer = NULL;
+     tunnelState->writeClientDone(buf, len, flag, xerrno);
+ }
+@@ -592,7 +612,14 @@
+ }
+ void
+-TunnelStateData::writeClientDone(char *buf, size_t len, comm_err_t flag, int xerrno)
++TunnelStateData::Connection::write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func)
++{
++    writer = callback;
++    Comm::Write(conn, b, size, callback, free_func);
++}
++
++void
++TunnelStateData::writeClientDone(char *, size_t len, comm_err_t flag, int xerrno)
+ {
+     debugs(26, 3, HERE << client.conn << ", " << len << " bytes written, flag=" << flag);
+@@ -712,6 +739,7 @@
+ {
+     TunnelStateData *tunnelState = (TunnelStateData *)data;
+     debugs(26, 3, HERE << conn << ", flag=" << flag);
++    tunnelState->client.writer = NULL;
+     if (flag != COMM_OK) {
+         *tunnelState->status_ptr = Http::scInternalServerError;
+@@ -728,6 +756,7 @@
+ {
+     TunnelStateData *tunnelState = (TunnelStateData *)data;
+     debugs(26, 3, conn << ", flag=" << flag);
++    tunnelState->server.writer = NULL;
+     assert(tunnelState->waitingForConnectRequest());
+     if (flag != COMM_OK) {
+@@ -768,7 +797,7 @@
+     else {
+         AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
+                                              CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
+-        Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL);
++        tunnelState->client.write(conn_established, strlen(conn_established), call, NULL);
+     }
+ }
+@@ -955,29 +984,20 @@
+     debugs(11, 2, "Tunnel Server REQUEST: " << tunnelState->server.conn << ":\n----------\n" <<
+            Raw("tunnelRelayConnectRequest", mb.content(), mb.contentSize()) << "\n----------");
+-    if (tunnelState->clientExpectsConnectResponse()) {
+-        // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours.
+-        AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone",
+-                                       CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
+-        Comm::Write(srv, &mb, writeCall);
+-    } else {
+-        // we have to eat the connect response from the peer (so that the client
+-        // does not see it) and only then start shoveling data to the client
+-        AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
+-                                       CommIoCbPtrFun(tunnelConnectReqWriteDone,
+-                                                      tunnelState));
+-        Comm::Write(srv, &mb, writeCall);
+-        tunnelState->connectReqWriting = true;
+-
+-        tunnelState->connectRespBuf = new MemBuf;
+-        // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
+-        // can hold since any CONNECT response leftovers have to fit into server.buf.
+-        // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
+-        tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
+-        tunnelState->readConnectResponse();
+-
+-        assert(tunnelState->waitingForConnectExchange());
+-    }
++    AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
++                                   CommIoCbPtrFun(tunnelConnectReqWriteDone, tunnelState));
++
++    tunnelState->server.write(mb.buf, mb.size, writeCall, mb.freeFunc());
++    tunnelState->connectReqWriting = true;
++
++    tunnelState->connectRespBuf = new MemBuf;
++    // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
++    // can hold since any CONNECT response leftovers have to fit into server.buf.
++    // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
++    tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
++    tunnelState->readConnectResponse();
++
++    assert(tunnelState->waitingForConnectExchange());
+     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
+                                      CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
+