On 21/04/17 15:58, Alex Rousskov wrote:
On 04/20/2017 04:50 PM, Amos Jeffries wrote:
These two patches begin the refactoring work to replace OpenSSL
SSL_set/get_ex_data() API with a generic libsecurity API.
Thank you for working on this. I think the patch is on the right track
overall, but I found one bug, and I believe the proposed GetFrom() API
needs to be revised. I also object to adding any code that calls TLS
connections "sessions", especially when we already have code that
calls TLS sessions "sessions". If at all possible, please fix the
naming in the existing code (as already discussed elsewhere) before
adding any new stuff that makes the problem worse.
Lets not get bogged down in that again.
Details below.
+/// extra data which we attach to a TLS session
s/session/connection/
No this data is explicitly not part of the "TLS connection". It persists
long after the "TLS connection" is ended and may be shared across
multiple if and when the "TLS session" does so.
+/// extra data which we attach to a TLS session +class ExtraData
Please avoid names that end with State, Data, Info, Object, Status,
and similar noise/placeholder words without any specific meaning. In
this particular case, the class should be named ConnectionExtras and
described as "Squid-specific TLS connection details" or something like
that. The variables holding class objects should then become "extras"
instead of "data".
OpenSSL calls this "ex_data" and GnuTLS calls it a "datum pointer", in
both cases it is documented as being attached to a session. They are
definitely NOT destructed when the connection is ended.
+ /// \retval nullptr only if session is nullptr + static
Security::ExtraData *GetFrom(const SessionPointer &);
The patch contains: * code that asserts if GetFrom() returns nil *
code that crashes if GetFrom() returns nil * code that pointlessly
checks whether GetFrom() returns nil I did not see any code that
actually expects a nil connection pointer and can handle it correctly.
Specific alternatives would be useful. I'm looking at a mix of code that
can only occur when a SessionPointer is available [use without checks],
code that can occur with or without one [check with an if], and code
that I'm not sure of its status [check with an if where possible, or an
assert as alternative to risking segfault].
The method itself does not store the TLS connection so it should not
need a pointer to one.
Since when was it a requirement that all function parameters were
stored? it is being _used_.
Regardless of what that parameter is called it is the object that our
class is being attached to. So yes it is absolutely needed.
To avoid multiplying these problems beyond the patch, please change
the method to accept a reference to the connection (instead of a
pointer) and return a reference to ConnectionExtras (instead of a
pointer).
Er, did you read the pt1 code? The pointer received is passed on to the
underlying library API. Using a reference to an object instance of
unknown type is not doable. And we also agreed that mixing smart and raw
pointers in our API was a bad idea.
The connection is not accessible in most places this function is called.
What we have reliably is a pointer to the session state where our object
is (to be) associated. Sometimes that session has a connection, but half
of the calls are at times before the TLS session initiates a TLS connection.
Please s/GetFrom/Of/ or, if you insist, s/GetFrom/From/ to shorten the
name and emphasize that this is not just an existing extras retrieval
method. From the caller point of view, all connections have a valid
extras object (and that is a good thing!).
- if (hostName) - SSL_set_ex_data(serverSession.get(),
ssl_ex_index_server, (void*)hostName); + if (hostName.isEmpty()) {
You want the opposite condition. AFAICT, this bug implies that the
changes were essentially untested. Please explicitly disclose untested
patches.
Thanks for the catch.
That code path is untested by me, yes. I cannot test the OpenSSL code
changes, and thought I'd stated that enough by now. It is one of the
reasons I'm going about these patches so slowly and piece-wise.
- SBuf *hostName = NULL; + SBuf hostName;
Or perhaps we can do the following instead? SBuf &hostName = extras->sni;
Nice. Done.
+ Security::SessionPointer session(ssl, [](SSL *){/* do not destroy
*/});
This ugly hack will no longer be needed after the above changes AFAICT.
No such luck. At least it is part of the callback state preparation.
+class ExtraData +{ +public: + SBuf sni;
This member may not actually contain an SNI extension value (neither
sent nor received). Let's not lie to ourselves and call it serverName.
Please document new data members. Consider: /// suspected or actual
connection destination (from various sources) SBuf serverName;
Sigh. Ironicly it started as that. Then realized the value being stored
there was SNI or an SNI-to-be value.
- sniServer = !request->url.hostIsNumeric() ? request->url.host() :
NULL; + sniServer = !request->url.hostIsNumeric() ?
request->url.host() : nullptr;
An out-of-scope change.
// validationRequest disappears on return so no need to
cbdataReference validationRequest.errors = errs; + try {
An out-of-scope change. Thank you, Alex.
Removed those.
Amos
=== modified file 'src/security/Session.cc'
--- src/security/Session.cc 2017-02-05 06:12:19 +0000
+++ src/security/Session.cc 2017-04-21 12:06:49 +0000
@@ -122,6 +122,8 @@
gnutls_session_t tmp;
errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
Security::SessionPointer session(tmp, [](gnutls_session_t p) {
+ // free any Security::Extras object associated with this session
+ delete static_cast<Security::Extras *>(gnutls_session_get_ptr(p));
debugs(83, 5, "gnutls_deinit session=" << (void*)p);
gnutls_deinit(p);
});
@@ -201,6 +203,49 @@
}
}
+#if USE_OPENSSL
+/// "free" function for Security::Extras
+static void
+free_Extras(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<Security::Extras *>(ptr);
+}
+#endif
+
+Security::Extras *
+Security::Extras::From(const SessionPointer &session)
+{
+ if (!session)
+ return nullptr;
+
+ Security::Extras *extras = nullptr;
+
+ // get any data we attached to the session earlier
+#if USE_OPENSSL
+ // initialize extended-data registration with OpenSSL if needed
+ static int extras_index = -1;
+ if (extras_index == -1) {
+ extras_index = SSL_get_ex_new_index(0, const_cast<char *>("Security::Extras"), nullptr, nullptr, free_Extras);
+ }
+
+ extras = static_cast<Security::Extras *>(SSL_get_ex_data(session.get(), extras_index));
+#elif USE_GNUTLS
+ extras = static_cast<Security::Extras *>(gnutls_session_get_ptr(session.get()));
+#endif
+
+ // allocate and attach a new data object if missing
+ if (!extras) {
+ extras = new Security::Extras;
+#if USE_OPENSSL
+ SSL_set_ex_data(session.get(), extras_index, extras);
+#elif USE_GNUTLS
+ gnutls_session_set_ptr(session.get(), extras);
+#endif
+ }
+
+ return extras;
+}
+
bool
Security::SessionIsResumed(const Security::SessionPointer &s)
{
=== modified file 'src/security/Session.h'
--- src/security/Session.h 2017-02-10 13:35:05 +0000
+++ src/security/Session.h 2017-04-21 11:11:18 +0000
@@ -11,6 +11,7 @@
#include "base/HardFun.h"
#include "comm/forward.h"
+#include "sbuf/SBuf.h"
#include "security/LockingPointer.h"
#include <memory>
@@ -56,6 +57,20 @@
#endif
+/// extra data which we attach to a TLS session
+class Extras
+{
+public:
+ SBuf serverName;
+
+public:
+ /// The extra data (if any) associated with a session.
+ /// \note Guaranteed to return an object if the session pointer is set,
+ /// will create if necessary.
+ /// \retval nullptr only if session is nullptr
+ static Security::Extras *From(const SessionPointer &);
+};
+
/// send the shutdown/bye notice for an active TLS session.
void SessionSendGoodbye(const Security::SessionPointer &);
=== modified file 'src/tests/stub_libsecurity.cc'
--- src/tests/stub_libsecurity.cc 2017-02-05 06:12:19 +0000
+++ src/tests/stub_libsecurity.cc 2017-04-21 11:12:59 +0000
@@ -98,6 +98,7 @@
bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *) STUB_RETVAL(false)
bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *) STUB_RETVAL(false)
void SessionSendGoodbye(const Security::SessionPointer &) STUB
+Security::Extras *Security::Extras::From(const Security::SessionPointer &) STUB_RETVAL(nullptr)
bool SessionIsResumed(const Security::SessionPointer &) STUB_RETVAL(false)
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &) STUB
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &) STUB
=== modified file 'src/adaptation/icap/Xaction.cc'
--- src/adaptation/icap/Xaction.cc 2017-02-16 11:51:56 +0000
+++ src/adaptation/icap/Xaction.cc 2017-04-21 11:13:20 +0000
@@ -712,13 +712,14 @@
return false;
assert(!icapService->cfg().secure.sslDomain.isEmpty());
+ auto *extras = Security::Extras::From(serverSession);
+ assert(extras);
+ extras->serverName = icapService->cfg().secure.sslDomain;
+
#if USE_OPENSSL
- SBuf *host = new SBuf(icapService->cfg().secure.sslDomain);
- SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, host);
-
ACLFilledChecklist *check = static_cast<ACLFilledChecklist *>(SSL_get_ex_data(serverSession.get(), ssl_ex_index_cert_error_check));
if (check)
- check->dst_peer_name = *host;
+ check->dst_peer_name = extras->serverName;
#endif
Security::SetSessionResumeData(serverSession, icapService->sslSession);
=== modified file 'src/globals.h'
--- src/globals.h 2017-01-01 00:12:22 +0000
+++ src/globals.h 2017-04-19 06:38:34 +0000
@@ -98,7 +98,6 @@
extern unsigned int WIN32_run_mode; /* _WIN_SQUID_RUN_MODE_INTERACTIVE */
#endif
-extern int ssl_ex_index_server; /* -1 */
extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */
extern int ssl_ex_index_cert_error_check; /* -1 */
extern int ssl_ex_index_ssl_error_detail; /* -1 */
=== modified file 'src/security/BlindPeerConnector.cc'
--- src/security/BlindPeerConnector.cc 2017-01-12 13:26:45 +0000
+++ src/security/BlindPeerConnector.cc 2017-04-21 11:13:35 +0000
@@ -37,22 +37,19 @@
return false;
}
+ auto *extras = Security::Extras::From(serverSession);
+ assert(extras);
+
if (const CachePeer *peer = serverConnection()->getPeer()) {
assert(peer);
// NP: domain may be a raw-IP but it is now always set
assert(!peer->secure.sslDomain.isEmpty());
-
-#if USE_OPENSSL
- // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
- SBuf *host = new SBuf(peer->secure.sslDomain);
- SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, host);
+ extras->serverName = peer->secure.sslDomain;
Security::SetSessionResumeData(serverSession, peer->sslSession);
} else {
- SBuf *hostName = new SBuf(request->url.host());
- SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, (void*)hostName);
-#endif
+ extras->serverName = request->url.host();
}
debugs(83, 5, "success");
=== modified file 'src/security/PeerConnector.cc'
--- src/security/PeerConnector.cc 2017-02-15 03:10:55 +0000
+++ src/security/PeerConnector.cc 2017-04-22 10:49:44 +0000
@@ -233,8 +233,10 @@
// Ssl::CertValidationRequest object used only to pass data to
// Ssl::CertValidationHelper::submit method.
validationRequest.ssl = session;
- if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
- validationRequest.domainName = dName->c_str();
+ if (auto *extras = Security::Extras::From(session)) {
+ if (!extras->serverName.isEmpty())
+ validationRequest.domainName = extras->serverName.c_str();
+ }
if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
// validationRequest disappears on return so no need to cbdataReference
validationRequest.errors = errs;
@@ -277,8 +279,8 @@
if (Debug::Enabled(83, 5)) {
Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
- SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
- debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode);
+ const auto *extras = Security::Extras::From(ssl);
+ debugs(83,5, "host=" << extras->serverName << " cert validation result: " << validationResponse->resultCode);
}
if (validationResponse->resultCode == ::Helper::Error) {
=== modified file 'src/ssl/PeekingPeerConnector.cc'
--- src/ssl/PeekingPeerConnector.cc 2017-02-05 05:57:32 +0000
+++ src/ssl/PeekingPeerConnector.cc 2017-04-22 03:37:18 +0000
@@ -143,27 +143,26 @@
// client connection is required in the case we need to splice
// or terminate client and server connections
assert(clientConn != NULL);
- SBuf *hostName = NULL;
+
+ auto *extras = Security::Extras::From(serverSession);
+ SBuf &hostName = extras->serverName;
//Enable Status_request TLS extension, required to bump some clients
SSL_set_tlsext_status_type(serverSession.get(), TLSEXT_STATUSTYPE_ocsp);
const Security::TlsDetails::Pointer details = csd->tlsParser.details;
if (details && !details->serverName.isEmpty())
- hostName = new SBuf(details->serverName);
+ hostName = details->serverName;
- if (!hostName) {
+ if (hostName.isEmpty()) {
// While we are peeking at the certificate, we may not know the server
// name that the client will request (after interception or CONNECT)
// unless it was the CONNECT request with a user-typed address.
const bool isConnectRequest = !csd->port->flags.isIntercepted();
if (!request->flags.sslPeek || isConnectRequest)
- hostName = new SBuf(request->url.host());
+ hostName = request->url.host();
}
- if (hostName)
- SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, (void*)hostName);
-
Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
auto clientSession = fd_table[clientConn->fd].ssl.get();
@@ -189,10 +188,10 @@
// to the origin server and we know the server host name.
const char *sniServer = NULL;
const bool redirected = request->flags.redirected && ::Config.onoff.redir_rewrites_host;
- if (!hostName || redirected)
+ if (hostName.isEmpty() || redirected)
sniServer = !request->url.hostIsNumeric() ? request->url.host() : NULL;
else
- sniServer = hostName->c_str();
+ sniServer = hostName.c_str();
if (sniServer)
Ssl::setClientSNI(serverSession.get(), sniServer);
=== modified file 'src/ssl/support.cc'
--- src/ssl/support.cc 2017-02-05 05:57:32 +0000
+++ src/ssl/support.cc 2017-04-21 11:12:24 +0000
@@ -253,7 +253,8 @@
char buffer[256] = "";
SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
SSL_CTX *sslctx = SSL_get_SSL_CTX(ssl);
- SBuf *server = (SBuf *)SSL_get_ex_data(ssl, ssl_ex_index_server);
+ Security::SessionPointer session(ssl, [](SSL *){/* do not destroy */});
+ auto *extras = Security::Extras::From(session);
void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain);
ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check);
X509 *peeked_cert = (X509 *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_peeked_cert);
@@ -283,9 +284,9 @@
debugs(83, 5, "SSL Certificate signature OK: " << buffer);
// Check for domain mismatch only if the current certificate is the peer certificate.
- if (!dont_verify_domain && server && peer_cert.get() == X509_STORE_CTX_get_current_cert(ctx)) {
- if (!Ssl::checkX509ServerValidity(peer_cert.get(), server->c_str())) {
- debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << server);
+ if (!dont_verify_domain && !extras->serverName.isEmpty() && peer_cert.get() == X509_STORE_CTX_get_current_cert(ctx)) {
+ if (!Ssl::checkX509ServerValidity(peer_cert.get(), extras->serverName.c_str())) {
+ debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << extras->serverName);
ok = 0;
error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
}
@@ -450,15 +451,6 @@
X509_free(cert);
}
-// "free" function for SBuf
-static void
-ssl_free_SBuf(void *, void *ptr, CRYPTO_EX_DATA *,
- int, long, void *)
-{
- SBuf *buf = static_cast <SBuf *>(ptr);
- delete buf;
-}
-
void
Ssl::Initialize(void)
{
@@ -491,7 +483,6 @@
if (!Ssl::DefaultSignHash)
fatalf("Sign hash '%s' is not supported\n", defName);
- ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, ssl_free_SBuf);
ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL);
ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist);
ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail);
_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev