Do not leak [SSL] objects tied to http_port and https_port on reconfigure.

PortCfg objects were not destroyed at all (no delete call) and were
incorrectly stored (excessive cbdata locking). This change adds
destruction and removes excessive locking to allow the destructed
object to be freed. It also cleans up forgotten(?) clientca and crlfile
PortCfg members.

This change fixes a serious leak but also carries an elevated risk:
There is a lot of code throughout Squid that does not check the pointers
to the objects that are now properly destroyed. It is possible that some
of that code will crash some time after reconfigure. It is not possible
to ensure that this does not happen without rewriting/fixing the
offending code to use refcounting. Such a rewrite would be a relatively
large change outside this patch scope. We may decide that it is better
to leak than to take this additional risk.

Alex.

Do not leak [SSL] objects tied to http_port and https_port on reconfigure

PortCfg objects were not destroyed at all (no delete call) and were
incorrectly stored (excessive cbdata locking). This change adds destruction
and removes excessive locking to allow the destructed object to be freed.
It also cleans up forgotten(?) clientca and crlfile PortCfg members.

This change fixes a serious leak but also carries an elevated risk:
There is a lot of code throughout Squid that does not check the pointers to
the objects that are now properly destroyed. It is possible that some of 
that code will crash some time after reconfigure. It is not possible to 
ensure that this does not happen without rewriting/fixing the offending
code to use refcounting. Such a rewrite would be a relatively large 
change outside this patch scope. We may decide that it is better to leak
than to take this additional risk.

=== modified file 'src/anyp/PortCfg.cc'
--- src/anyp/PortCfg.cc	2014-04-16 21:58:08 +0000
+++ src/anyp/PortCfg.cc	2014-04-24 20:19:11 +0000
@@ -57,44 +57,46 @@ AnyP::PortCfg::PortCfg() :
         sslContextFlags(0),
         sslOptions(0)
 #endif
 {
     memset(&tcp_keepalive, 0, sizeof(tcp_keepalive));
 }
 
 AnyP::PortCfg::~PortCfg()
 {
     if (Comm::IsConnOpen(listenConn)) {
         listenConn->close();
         listenConn = NULL;
     }
 
     safe_free(name);
     safe_free(defaultsite);
 
 #if USE_OPENSSL
     safe_free(cert);
     safe_free(key);
-    safe_free(options);
     safe_free(cipher);
+    safe_free(options);
+    safe_free(clientca);
     safe_free(cafile);
     safe_free(capath);
+    safe_free(crlfile);
     safe_free(dhfile);
     safe_free(sslflags);
     safe_free(sslContextSessionId);
 #endif
 }
 
 AnyP::PortCfg *
 AnyP::PortCfg::clone() const
 {
     AnyP::PortCfg *b = new AnyP::PortCfg();
     b->s = s;
     if (name)
         b->name = xstrdup(name);
     if (defaultsite)
         b->defaultsite = xstrdup(defaultsite);
 
     b->transport = transport;
     b->flags = flags;
     b->allow_direct = allow_direct;
     b->vhost = vhost;

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2014-04-12 17:32:34 +0000
+++ src/cache_cf.cc	2014-04-24 20:23:43 +0000
@@ -3761,43 +3766,43 @@ parse_port_option(AnyP::PortCfg * s, cha
         s->generateHostCertificates = true;
     } else if (strcmp(token, "generate-host-certificates=off") == 0) {
         s->generateHostCertificates = false;
     } else if (strncmp(token, "dynamic_cert_mem_cache_size=", 28) == 0) {
         parseBytesOptionValue(&s->dynamicCertMemCacheSize, B_BYTES_STR, token + 28);
 #endif
     } else {
         debugs(3, DBG_CRITICAL, "FATAL: Unknown http(s)_port option '" << token << "'.");
         self_destruct();
     }
 }
 
 void
 add_http_port(char *portspec)
 {
     AnyP::PortCfg *s = new AnyP::PortCfg();
     s->setTransport("HTTP");
     parsePortSpecification(s, portspec);
     // we may need to merge better if the above returns a list with clones
     assert(s->next == NULL);
-    s->next = cbdataReference(Config.Sockaddr.http);
-    cbdataReferenceDone(Config.Sockaddr.http);
-    Config.Sockaddr.http = cbdataReference(s);
+    // no cbdata locking here: Config owns the ports (deleted in free_PortCfg)
+    s->next = Config.Sockaddr.http;
+    Config.Sockaddr.http = s;
 }
 
 static void
 parsePortCfg(AnyP::PortCfg ** head, const char *optionName)
 {
     const char *protocol = NULL;
     if (strcmp(optionName, "http_port") == 0 ||
             strcmp(optionName, "ascii_port") == 0)
         protocol = "http";
     else if (strcmp(optionName, "https_port") == 0)
         protocol = "https";
     if (!protocol) {
         self_destruct();
         return;
     }
 
     char *token = ConfigParser::NextToken();
 
     if (!token) {
         self_destruct();
@@ -3820,42 +3825,42 @@ parsePortCfg(AnyP::PortCfg ** head, cons
         if (s->flags.tunnelSslBumping && !hijacked) {
             debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy/intercept which is missing.");
             self_destruct();
         }
         if (hijacked && !s->flags.tunnelSslBumping) {
             debugs(3, DBG_CRITICAL, "FATAL: tproxy/intercept on https_port requires ssl-bump which is missing.");
             self_destruct();
         }
     }
 #endif
 
     if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && s->s.isAnyAddr()) {
         // clone the port options from *s to *(s->next)
         s->next = cbdataReference(s->clone());
         s->next->s.setIPv4();
         debugs(3, 3, AnyP::UriScheme(s->transport.protocol).c_str() << "_port: clone wildcard address for split-stack: " << s->s << " and " << s->next->s);
     }
 
     while (*head)
         head = &(*head)->next;
-
-    *head = cbdataReference(s);
+    // no cbdata locking here: Config owns the ports (deleted in free_PortCfg)
+    *head = s;
 }
 
 static void
 dump_generic_port(StoreEntry * e, const char *n, const AnyP::PortCfg * s)
 {
     char buf[MAX_IPSTRLEN];
 
     storeAppendPrintf(e, "%s %s",
                       n,
                       s->s.toUrl(buf,MAX_IPSTRLEN));
 
     // MODES and specific sub-options.
     if (s->flags.natIntercept)
         storeAppendPrintf(e, " intercept");
 
     else if (s->flags.tproxyIntercept)
         storeAppendPrintf(e, " tproxy");
 
     else if (s->flags.accelSurrogate) {
         storeAppendPrintf(e, " accel");
@@ -3965,50 +3970,51 @@ dump_generic_port(StoreEntry * e, const 
 #endif
 }
 
 static void
 dump_PortCfg(StoreEntry * e, const char *n, const AnyP::PortCfg * s)
 {
     while (s) {
         dump_generic_port(e, n, s);
         storeAppendPrintf(e, "\n");
         s = s->next;
     }
 }
 
 static void
 free_PortCfg(AnyP::PortCfg ** head)
 {
     AnyP::PortCfg *s;
 
     while ((s = *head) != NULL) {
         *head = s->next;
-        cbdataReferenceDone(s);
+        delete s;
     }
 }
 
 void
 configFreeMemory(void)
 {
     free_all();
 #if USE_OPENSSL
     SSL_CTX_free(Config.ssl_client.sslContext);
+    Config.ssl_client.sslContext = NULL;
 #endif
 }
 
 void
 requirePathnameExists(const char *name, const char *path)
 {
 
     struct stat sb;
     char pathbuf[BUFSIZ];
     assert(path != NULL);
 
     if (Config.chroot_dir && (geteuid() == 0)) {
         snprintf(pathbuf, BUFSIZ, "%s/%s", Config.chroot_dir, path);
         path = pathbuf;
     }
 
     if (stat(path, &sb) < 0) {
         debugs(0, DBG_CRITICAL, (opt_parse_cfg_only?"FATAL ":"") << "ERROR: " << name << " " << path << ": " << xstrerror());
         // keep going to find more issues if we are only checking the config file with "-k parse"
         if (opt_parse_cfg_only)

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2014-03-31 06:57:27 +0000
+++ src/client_side.cc	2014-04-24 20:12:30 +0000
@@ -3704,43 +3704,48 @@ ConnStateData::sslCrtdHandleReplyWrapper
     ConnStateData * state_data = (ConnStateData *)(data);
     state_data->sslCrtdHandleReply(reply);
 }
 
 void
 ConnStateData::sslCrtdHandleReply(const HelperReply &reply)
 {
     if (reply.result == HelperReply::BrokenHelper) {
         debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply);
     } else if (!reply.other().hasContent()) {
         debugs(1, DBG_IMPORTANT, HERE << "\"ssl_crtd\" helper returned <NULL> reply.");
     } else {
         Ssl::CrtdMessage reply_message(Ssl::CrtdMessage::REPLY);
         if (reply_message.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK) {
             debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslConnectHostOrIp << " is incorrect");
         } else {
             if (reply.result != HelperReply::Okay) {
                 debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody());
             } else {
                 debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
-                SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
-                getSslContextDone(ctx, true);
-                return;
+                if (cbdataReferenceValid(port)) {
+                    SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
+                    getSslContextDone(ctx, true);
+                    return;
+                } else {
+                    debugs(33, 3, "but our port is gone");
+                    // getSslContextDone() will cleanup
+                }
             }
         }
     }
     getSslContextDone(NULL);
 }
 
 void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties)
 {
     certProperties.commonName =  sslCommonName.size() > 0 ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf();
 
     // fake certificate adaptation requires bump-server-first mode
     if (!sslServerBump) {
         assert(port->signingCert.get());
         certProperties.signWithX509.resetAndLock(port->signingCert.get());
         if (port->signPkey.get())
             certProperties.signWithPkey.resetAndLock(port->signPkey.get());
         certProperties.signAlgorithm = Ssl::algSignTrusted;
         return;
     }
 
@@ -3806,41 +3811,41 @@ void ConnStateData::buildSslCertGenerati
         assert(port->untrustedSigningCert.get());
         certProperties.signWithX509.resetAndLock(port->untrustedSigningCert.get());
         certProperties.signWithPkey.resetAndLock(port->untrustedSignPkey.get());
     } else {
         assert(port->signingCert.get());
         certProperties.signWithX509.resetAndLock(port->signingCert.get());
 
         if (port->signPkey.get())
             certProperties.signWithPkey.resetAndLock(port->signPkey.get());
     }
     signAlgorithm = certProperties.signAlgorithm;
 }
 
 void
 ConnStateData::getSslContextStart()
 {
     assert(areAllContextsForThisConnection());
     freeAllContexts();
     /* careful: freeAllContexts() above frees request, host, etc. */
 
-    if (port->generateHostCertificates) {
+    if (cbdataReferenceValid(port) && port->generateHostCertificates) {
         Ssl::CertificateProperties certProperties;
         buildSslCertGenerationParams(certProperties);
         sslBumpCertKey = certProperties.dbKey().c_str();
         assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0');
 
         debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
         Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
         SSL_CTX * dynCtx = NULL;
         Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL;
         if (cachedCtx && (dynCtx = cachedCtx->get())) {
             debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
             if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
                 debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
                 getSslContextDone(dynCtx);
                 return;
             } else {
                 debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
                 if (ssl_ctx_cache)
                     ssl_ctx_cache->del(sslBumpCertKey.termedBuf());
             }
@@ -3860,40 +3865,48 @@ ConnStateData::getSslContextStart()
         } catch (const std::exception &e) {
             debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " <<
                    "request for " << certProperties.commonName <<
                    " certificate: " << e.what() << "; will now block to " <<
                    "generate that certificate.");
             // fall through to do blocking in-process generation.
         }
 #endif // USE_SSL_CRTD
 
         debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName);
         dynCtx = Ssl::generateSslContext(certProperties, *port);
         getSslContextDone(dynCtx, true);
         return;
     }
     getSslContextDone(NULL);
 }
 
 void
 ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew)
 {
+    if (!cbdataReferenceValid(port)) { // reconfigure may delete our port
+        debugs(83, 2, "Closing SSL " << clientConnection->remote << " for lost port");
+        if (sslContext && Comm::IsConnOpen(clientConnection))
+            fd_table[clientConnection->fd].dynamicSslContext = sslContext;
+        clientConnection->close();
+        return;
+    }
+
     // Try to add generated ssl context to storage.
     if (port->generateHostCertificates && isNew) {
 
         if (signAlgorithm == Ssl::algSignTrusted) {
             // Add signing certificate to the certificates chain
             X509 *cert = port->signingCert.get();
             if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) {
                 // increase the certificate lock
                 CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509);
             } else {
                 const int ssl_error = ERR_get_error();
                 debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL));
             }
             Ssl::addChainToSslContext(sslContext, port->certsToChain.get());
         }
         //else it is self-signed or untrusted do not attrach any certificate
 
         Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
         assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0');
         if (sslContext) {

Reply via email to