When a certificate validator was used, sslCrtvdHandleReplyWrapper delivered validator response directly to the Ssl::PeerConnector job using job's Ssl::CertValidationHelper::CVHCB callback. If that synchronous call happened to be the last job call, then Ssl::PeerConnector::done() would become true for the job, as it should, but nobody would notice that the PeerConnector job object should be deleted, and the object would leak.

This fix converts CVHCB into an async job call to avoid direct, unprotected job calls in this context.

This is a Measurement Factory project.
Avoid memory leaks when a certificate validator is used with SslBump

When a certificate validator was used, sslCrtvdHandleReplyWrapper delivered
validator response directly to the Ssl::PeerConnector job using job's
Ssl::CertValidationHelper::CVHCB callback. If that synchronous call happened
to be the last job call, then Ssl::PeerConnector::done() would become true
for the job, as it should, but nobody would notice that the PeerConnector
job object should be deleted, and the object would leak.

This fix converts CVHCB into an async job call to avoid direct, unprotected
job calls in this context.

This is a Measurement Factory project.

=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc	2015-11-28 03:00:35 +0000
+++ src/ssl/PeerConnector.cc	2015-11-30 18:06:46 +0000
@@ -169,41 +169,42 @@
 Ssl::PeerConnector::sslFinalized()
 {
     if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
         const int fd = serverConnection()->fd;
         SSL *ssl = fd_table[fd].ssl;
 
         Ssl::CertValidationRequest validationRequest;
         // WARNING: Currently we do not use any locking for any of the
         // members of the Ssl::CertValidationRequest class. In this code the
         // Ssl::CertValidationRequest object used only to pass data to
         // Ssl::CertValidationHelper::submit method.
         validationRequest.ssl = ssl;
         validationRequest.domainName = request->url.host();
         if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
             // validationRequest disappears on return so no need to cbdataReference
             validationRequest.errors = errs;
         else
             validationRequest.errors = NULL;
         try {
             debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
-            Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, sslCrtvdHandleReplyWrapper, this);
+            AsyncCall::Pointer call = asyncCall(83,5, "Ssl::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Ssl::PeerConnector::sslCrtvdHandleReply, nullptr));
+            Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call);
             return false;
         } catch (const std::exception &e) {
             debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
                    "request for " << validationRequest.domainName <<
                    " certificate: " << e.what() << "; will now block to " <<
                    "validate that certificate.");
             // fall through to do blocking in-process generation.
             ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
 
             noteNegotiationDone(anErr);
             bail(anErr);
             serverConn->close();
             return true;
         }
     }
 
     noteNegotiationDone(NULL);
     return true;
 }
 
@@ -292,61 +293,56 @@
 }
 
 Ssl::BumpMode
 Ssl::PeekingPeerConnector::checkForPeekAndSpliceGuess() const
 {
     if (const ConnStateData *csd = request->clientConnectionManager.valid()) {
         const Ssl::BumpMode currentMode = csd->sslBumpMode;
         if (currentMode == Ssl::bumpStare) {
             debugs(83,5, "default to bumping after staring");
             return Ssl::bumpBump;
         }
         debugs(83,5, "default to splicing after " << currentMode);
     } else {
         debugs(83,3, "default to splicing due to missing info");
     }
 
     return Ssl::bumpSplice;
 }
 
 void
-Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
+Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse)
 {
-    Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data);
-    connector->sslCrtvdHandleReply(validationResponse);
-}
+    Must(validationResponse != NULL);
 
-void
-Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse)
-{
     Ssl::CertErrors *errs = NULL;
     Ssl::ErrorDetail *errDetails = NULL;
     bool validatorFailed = false;
     if (!Comm::IsConnOpen(serverConnection())) {
         return;
     }
 
-    debugs(83,5, request->url.host() << " cert validation result: " << validationResponse.resultCode);
+    debugs(83,5, request->url.host() << " cert validation result: " << validationResponse->resultCode);
 
-    if (validationResponse.resultCode == ::Helper::Error)
-        errs = sslCrtvdCheckForErrors(validationResponse, errDetails);
-    else if (validationResponse.resultCode != ::Helper::Okay)
+    if (validationResponse->resultCode == ::Helper::Error)
+        errs = sslCrtvdCheckForErrors(*validationResponse, errDetails);
+    else if (validationResponse->resultCode != ::Helper::Okay)
         validatorFailed = true;
 
     if (!errDetails && !validatorFailed) {
         noteNegotiationDone(NULL);
         callBack();
         return;
     }
 
     if (errs) {
         if (certErrors)
             cbdataReferenceDone(certErrors);
         certErrors = cbdataReference(errs);
     }
 
     ErrorState *anErr = NULL;
     if (validatorFailed) {
         anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
     }  else {
         anErr =  new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw());
         anErr->detail = errDetails;

=== modified file 'src/ssl/PeerConnector.h'
--- src/ssl/PeerConnector.h	2015-11-28 03:00:35 +0000
+++ src/ssl/PeerConnector.h	2015-11-30 17:02:11 +0000
@@ -1,46 +1,48 @@
 /*
  * 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_SSL_PEER_CONNECTOR_H
 #define SQUID_SSL_PEER_CONNECTOR_H
 
 #include "acl/Acl.h"
 #include "base/AsyncCbdataCalls.h"
 #include "base/AsyncJob.h"
+#include "CommCalls.h"
 #include "security/EncryptorAnswer.h"
 #include "ssl/support.h"
 #include <iosfwd>
 
 class HttpRequest;
 class ErrorState;
 
 namespace Ssl
 {
 
 class ErrorDetail;
 class CertValidationResponse;
+typedef RefCount<CertValidationResponse> CertValidationResponsePointer;
 
 /**
  \par
  * Connects Squid to SSL/TLS-capable peers or services.
  * Contains common code and interfaces of various specialized PeerConnectors,
  * including peer certificate validation code.
  \par
  * The caller receives a call back with Security::EncryptorAnswer. If answer.error
  * is not nil, then there was an error and the SSL connection to the SSL peer
  * was not fully established. The error object is suitable for error response
  * generation.
  \par
  * The caller must monitor the connection for closure because this
  * job will not inform the caller about such events.
  \par
  * PeerConnector class curently supports a form of SSL negotiation timeout,
  * which accounted only when sets the read timeout from SSL peer.
  * For a complete solution, the caller must monitor the overall connection
  * establishment timeout and close the connection on timeouts. This is probably
  * better than having dedicated (or none at all!) timeouts for peer selection,
@@ -143,48 +145,45 @@
 
     void bail(ErrorState *error); ///< Return an error to the PeerConnector caller
 
     /// If called the certificates validator will not used
     void bypassCertValidator() {useCertValidator_ = false;}
 
     HttpRequestPointer request; ///< peer connection trigger or cause
     Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
     /// Certificate errors found from SSL validation procedure or from cert
     /// validator
     Ssl::CertErrors *certErrors;
 private:
     PeerConnector(const PeerConnector &); // not implemented
     PeerConnector &operator =(const PeerConnector &); // not implemented
 
     /// Callback the caller class, and pass the ready to communicate secure
     /// connection or an error if PeerConnector failed.
     void callBack();
 
     /// Process response from cert validator helper
-    void sslCrtvdHandleReply(Ssl::CertValidationResponse const &);
+    void sslCrtvdHandleReply(Ssl::CertValidationResponsePointer);
 
     /// Check SSL errors returned from cert validator against sslproxy_cert_error access list
     Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
 
-    /// Callback function called when squid receive message from cert validator helper
-    static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &);
-
     /// A wrapper function for negotiateSsl for use with Comm::SetSelect
     static void NegotiateSsl(int fd, void *data);
     AsyncCall::Pointer callback; ///< we call this with the results
     AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
     time_t negotiationTimeout; ///< the SSL connection timeout to use
     time_t startTime; ///< when the peer connector negotiation started
     bool useCertValidator_; ///< whether the certificate validator should bypassed
 };
 
 /// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
 class BlindPeerConnector: public PeerConnector {
     CBDATA_CLASS(BlindPeerConnector);
 public:
     BlindPeerConnector(HttpRequestPointer &aRequest,
                        const Comm::ConnectionPointer &aServerConn,
                        AsyncCall::Pointer &aCallback, const time_t timeout = 0) :
         AsyncJob("Ssl::BlindPeerConnector"),
         PeerConnector(aServerConn, aCallback, timeout)
     {
         request = aRequest;

=== modified file 'src/ssl/cert_validate_message.h'
--- src/ssl/cert_validate_message.h	2015-09-14 16:25:05 +0000
+++ src/ssl/cert_validate_message.h	2015-11-30 16:58:52 +0000
@@ -1,60 +1,63 @@
 /*
  * 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_SSL_CERT_VALIDATE_MESSAGE_H
 #define SQUID_SSL_CERT_VALIDATE_MESSAGE_H
 
+#include "base/RefCount.h"
 #include "helper/ResultCode.h"
 #include "ssl/crtd_message.h"
 #include "ssl/support.h"
 
 #include <vector>
 
 namespace Ssl
 {
 
 /**
  * This class is used to hold the required informations to build
  * a request message for the certificate validator helper
  */
 class CertValidationRequest
 {
 public:
     SSL *ssl;
     CertErrors *errors; ///< The list of errors detected
     std::string domainName; ///< The server name
     CertValidationRequest() : ssl(NULL), errors(NULL) {}
 };
 
 /**
  * This class is used to store informations found in certificate validation
  * response messages read from certificate validator helper
  */
-class CertValidationResponse
+class CertValidationResponse: public RefCountable
 {
 public:
+    typedef RefCount<CertValidationResponse> Pointer;
+
     /**
      * This class used to hold error informations returned from
      * cert validator helper.
      */
     class  RecvdError
     {
     public:
         RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL) {}
         RecvdError(const RecvdError &);
         RecvdError & operator =(const RecvdError &);
         void setCert(X509 *);  ///< Sets cert to the given certificate
         int id; ///<  The id of the error
         ssl_error_t error_no; ///< The OpenSSL error code
         std::string error_reason; ///< A string describing the error
         Security::CertPointer cert; ///< The broken certificate
     };
 
     typedef std::vector<RecvdError> RecvdErrors;
 
     /// Search in errors list for the error item with id=errorId.

=== modified file 'src/ssl/helper.cc'
--- src/ssl/helper.cc	2015-11-19 05:51:49 +0000
+++ src/ssl/helper.cc	2015-11-30 18:07:12 +0000
@@ -2,41 +2,41 @@
  * 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 "../helper.h"
 #include "anyp/PortCfg.h"
 #include "fs_io.h"
 #include "helper/Reply.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/Config.h"
 #include "ssl/helper.h"
 #include "wordlist.h"
 
-LruMap<Ssl::CertValidationResponse> *Ssl::CertValidationHelper::HelperCache = NULL;
+Ssl::CertValidationHelper::LruCache *Ssl::CertValidationHelper::HelperCache = nullptr;
 
 #if USE_SSL_CRTD
 Ssl::Helper * Ssl::Helper::GetInstance()
 {
     static Ssl::Helper sslHelper;
     return &sslHelper;
 }
 
 Ssl::Helper::Helper() : ssl_crtd(NULL)
 {
 }
 
 Ssl::Helper::~Helper()
 {
     Shutdown();
 }
 
 void Ssl::Helper::Init()
 {
     assert(ssl_crtd == NULL);
@@ -156,123 +156,126 @@
         bool parseParams = true;
         while ((token = strwordtok(NULL, &tmp))) {
             if (parseParams) {
                 if (strncmp(token, "ttl=", 4) == 0) {
                     ttl = atoi(token + 4);
                     continue;
                 } else if (strncmp(token, "cache=", 6) == 0) {
                     cache = atoi(token + 6);
                     continue;
                 } else
                     parseParams = false;
             }
             wordlistAdd(&ssl_crt_validator->cmdline, token);
         }
         xfree(tmp_begin);
     }
     helperOpenServers(ssl_crt_validator);
 
     //WARNING: initializing static member in an object initialization method
     assert(HelperCache == NULL);
-    HelperCache = new LruMap<Ssl::CertValidationResponse>(ttl, cache);
+    HelperCache = new Ssl::CertValidationHelper::LruCache(ttl, cache);
 }
 
 void Ssl::CertValidationHelper::Shutdown()
 {
     if (!ssl_crt_validator)
         return;
     helperShutdown(ssl_crt_validator);
     wordlistDestroy(&ssl_crt_validator->cmdline);
     delete ssl_crt_validator;
     ssl_crt_validator = NULL;
 
     // CertValidationHelper::HelperCache is a static member, it is not good policy to
     // reset it here. Will work because the current Ssl::CertValidationHelper is
     // always the same static object.
     delete HelperCache;
     HelperCache = NULL;
 }
 
 class submitData
 {
     CBDATA_CLASS(submitData);
 
 public:
     std::string query;
-    Ssl::CertValidationHelper::CVHCB *callback;
-    void *data;
+    AsyncCall::Pointer callback;
     SSL *ssl;
 };
 CBDATA_CLASS_INIT(submitData);
 
 static void
 sslCrtvdHandleReplyWrapper(void *data, const ::Helper::Reply &reply)
 {
     Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY);
-    Ssl::CertValidationResponse *validationResponse = new Ssl::CertValidationResponse;
+    Ssl::CertValidationResponse::Pointer validationResponse = new Ssl::CertValidationResponse;
     std::string error;
 
     submitData *crtdvdData = static_cast<submitData *>(data);
     STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(crtdvdData->ssl);
     if (reply.result == ::Helper::BrokenHelper) {
         debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper error response: " << reply.other().content());
         validationResponse->resultCode = ::Helper::BrokenHelper;
     } else if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK ||
                !replyMsg.parseResponse(*validationResponse, peerCerts, error) ) {
         debugs(83, DBG_IMPORTANT, "WARNING: Reply from ssl_crtvd for " << " is incorrect");
         debugs(83, DBG_IMPORTANT, "Certificate cannot be validated. ssl_crtvd response: " << replyMsg.getBody());
         validationResponse->resultCode = ::Helper::BrokenHelper;
     } else
         validationResponse->resultCode = reply.result;
 
-    crtdvdData->callback(crtdvdData->data, *validationResponse);
+    Ssl::CertValidationHelper::CbDialer *dialer = dynamic_cast<Ssl::CertValidationHelper::CbDialer*>(crtdvdData->callback->getDialer());
+    Must(dialer);
+    dialer->arg1 = validationResponse;
+    ScheduleCallHere(crtdvdData->callback);
 
     if (Ssl::CertValidationHelper::HelperCache &&
             (validationResponse->resultCode == ::Helper::Okay || validationResponse->resultCode == ::Helper::Error)) {
-        Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), validationResponse);
-    } else
-        delete validationResponse;
+        Ssl::CertValidationResponse::Pointer *item = new Ssl::CertValidationResponse::Pointer(validationResponse);
+        Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), item);
+    }
 
-    cbdataReferenceDone(crtdvdData->data);
     SSL_free(crtdvdData->ssl);
     delete crtdvdData;
 }
 
-void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &request, Ssl::CertValidationHelper::CVHCB * callback, void * data)
+void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &request, AsyncCall::Pointer &callback)
 {
     assert(ssl_crt_validator);
 
     Ssl::CertValidationMsg message(Ssl::CrtdMessage::REQUEST);
     message.setCode(Ssl::CertValidationMsg::code_cert_validate);
     message.composeRequest(request);
     debugs(83, 5, "SSL crtvd request: " << message.compose().c_str());
 
     submitData *crtdvdData = new submitData;
     crtdvdData->query = message.compose();
     crtdvdData->query += '\n';
     crtdvdData->callback = callback;
-    crtdvdData->data = cbdataReference(data);
     crtdvdData->ssl = request.ssl;
     CRYPTO_add(&crtdvdData->ssl->references,1,CRYPTO_LOCK_SSL);
-    Ssl::CertValidationResponse const*validationResponse;
+    Ssl::CertValidationResponse::Pointer const*validationResponse;
 
     if (CertValidationHelper::HelperCache &&
             (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query.c_str()))) {
-        callback(data, *validationResponse);
-        cbdataReferenceDone(crtdvdData->data);
+
+        CertValidationHelper::CbDialer *dialer = dynamic_cast<CertValidationHelper::CbDialer*>(callback->getDialer());
+        dialer->arg1 = *validationResponse;
+        ScheduleCallHere(callback);
         SSL_free(crtdvdData->ssl);
         delete crtdvdData;
         return;
     }
 
     if (!ssl_crt_validator->trySubmit(crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData)) {
-        Ssl::CertValidationResponse resp;
-        resp.resultCode = ::Helper::BrokenHelper;
-        callback(data, resp);
+        Ssl::CertValidationResponse::Pointer resp = new Ssl::CertValidationResponse;;
+        resp->resultCode = ::Helper::BrokenHelper;
+        Ssl::CertValidationHelper::CbDialer *dialer = dynamic_cast<Ssl::CertValidationHelper::CbDialer*>(callback->getDialer());
+        dialer->arg1 = resp;
+        ScheduleCallHere(callback);
 
-        cbdataReferenceDone(crtdvdData->data);
         SSL_free(crtdvdData->ssl);
         delete crtdvdData;
         return;
     }
 }
 

=== modified file 'src/ssl/helper.h'
--- src/ssl/helper.h	2015-01-13 07:25:36 +0000
+++ src/ssl/helper.h	2015-11-30 17:19:25 +0000
@@ -1,63 +1,68 @@
 /*
  * 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_SSL_HELPER_H
 #define SQUID_SSL_HELPER_H
 
+#include "base/AsyncJobCalls.h"
 #include "base/LruMap.h"
 #include "helper/forward.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/crtd_message.h"
 
 namespace Ssl
 {
 /**
  * Set of thread for ssl_crtd. This class is singleton. Use this class only
  * over GetIntance() static method. This class use helper structure
  * for threads management.
  */
 #if USE_SSL_CRTD
 class Helper
 {
 public:
     static Helper * GetInstance(); ///< Instance class.
     void Init(); ///< Init helper structure.
     void Shutdown(); ///< Shutdown helper structure.
     /// Submit crtd message to external crtd server.
     void sslSubmit(CrtdMessage const & message, HLPCB * callback, void *data);
 private:
     Helper();
     ~Helper();
 
     helper * ssl_crtd; ///< helper for management of ssl_crtd.
 };
 #endif
 
+class PeerConnector;
 class CertValidationRequest;
 class CertValidationResponse;
 class CertValidationHelper
 {
 public:
+    typedef UnaryMemFunT<Ssl::PeerConnector, CertValidationResponse::Pointer> CbDialer;
+
     typedef void CVHCB(void *, Ssl::CertValidationResponse const &);
     static CertValidationHelper * GetInstance(); ///< Instance class.
     void Init(); ///< Init helper structure.
     void Shutdown(); ///< Shutdown helper structure.
     /// Submit crtd request message to external crtd server.
-    void sslSubmit(Ssl::CertValidationRequest const & request, CVHCB * callback, void *data);
+    void sslSubmit(Ssl::CertValidationRequest const & request, AsyncCall::Pointer &);
 private:
     CertValidationHelper();
     ~CertValidationHelper();
 
     helper * ssl_crt_validator; ///< helper for management of ssl_crtd.
 public:
-    static LruMap<Ssl::CertValidationResponse> *HelperCache; ///< cache for cert validation helper
+    typedef LruMap<Ssl::CertValidationResponse::Pointer, sizeof(Ssl::CertValidationResponse::Pointer) + sizeof(Ssl::CertValidationResponse)> LruCache;
+    static LruCache *HelperCache; ///< cache for cert validation helper
 };
 
 } //namespace Ssl
 #endif // SQUID_SSL_HELPER_H
 

_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to