summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMårten Nordheim <[email protected]>2022-08-16 13:33:15 +0200
committerMårten Nordheim <[email protected]>2022-08-17 19:55:18 +0000
commit1b68e0b71786ce509cadf0771a1c4c1d71a7294b (patch)
tree0b427c5c091774c5cb34b23cc7a28e0265d078bf
parentfb4123f36a88f6e890ed2e7f9b462665bfdcd6c0 (diff)
QSslServer: Check that first byte is ClientHello
SecureTransport ignores any content that comes in until it is large enough to be a handshake. So a plaintext client may be left hanging while it is waiting for a response. Pick-to: 6.4 Change-Id: I501ae61d89d516765c7ba5f0d916d9246fde5d4d Reviewed-by: Timur Pocheptsov <[email protected]>
-rw-r--r--src/network/ssl/qsslserver.cpp59
-rw-r--r--src/network/ssl/qsslserver_p.h25
-rw-r--r--tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp23
3 files changed, 105 insertions, 2 deletions
diff --git a/src/network/ssl/qsslserver.cpp b/src/network/ssl/qsslserver.cpp
index a26e79e315e..1611046d0ee 100644
--- a/src/network/ssl/qsslserver.cpp
+++ b/src/network/ssl/qsslserver.cpp
@@ -256,6 +256,8 @@ void QSslServer::incomingConnection(qintptr socket)
pSslSocket->deleteLater();
});
connect(pSslSocket, &QSslSocket::encrypted, this, [this, pSslSocket]() {
+ Q_D(QSslServer);
+ d->removeSocketData(quintptr(pSslSocket));
pSslSocket->disconnect(this);
addPendingConnection(pSslSocket);
});
@@ -278,10 +280,63 @@ void QSslServer::incomingConnection(qintptr socket)
Q_EMIT handshakeInterruptedOnError(pSslSocket, error);
});
- Q_EMIT startedEncryptionHandshake(pSslSocket);
+ d_func()->initializeHandshakeProcess(pSslSocket);
+ }
+}
+
+void QSslServerPrivate::initializeHandshakeProcess(QSslSocket *socket)
+{
+ Q_Q(QSslServer);
+ QMetaObject::Connection readyRead = QObject::connect(
+ socket, &QSslSocket::readyRead, q, [this]() { checkClientHelloAndContinue(); });
+
+ QMetaObject::Connection destroyed =
+ QObject::connect(socket, &QSslSocket::destroyed, q, [this](QObject *obj) {
+ // This cast is not safe to use since the socket is inside the
+ // QObject dtor, but we only use the pointer value!
+ removeSocketData(quintptr(obj));
+ });
+ socketData.emplace(quintptr(socket), readyRead, destroyed);
+}
+
+// This function may be called while in the socket's QObject dtor, __never__ use
+// the socket for anything other than a lookup!
+void QSslServerPrivate::removeSocketData(quintptr socket)
+{
+ auto it = socketData.find(socket);
+ if (it != socketData.end()) {
+ it->disconnectSignals();
+ socketData.erase(it);
+ }
+}
- pSslSocket->startServerEncryption();
+void QSslServerPrivate::checkClientHelloAndContinue()
+{
+ Q_Q(QSslServer);
+ QSslSocket *socket = qobject_cast<QSslSocket *>(q->sender());
+ if (Q_UNLIKELY(!socket) || socket->bytesAvailable() <= 0)
+ return;
+
+ char byte = '\0';
+ if (socket->peek(&byte, 1) != 1) {
+ socket->deleteLater();
+ return;
}
+
+ auto it = socketData.find(quintptr(socket));
+ const bool foundData = it != socketData.end();
+ if (foundData && it->readyReadConnection)
+ QObject::disconnect(std::exchange(it->readyReadConnection, {}));
+
+ constexpr char CLIENT_HELLO = 0x16;
+ if (byte != CLIENT_HELLO) {
+ socket->disconnectFromHost();
+ socket->deleteLater();
+ return;
+ }
+
+ socket->startServerEncryption();
+ Q_EMIT q->startedEncryptionHandshake(socket);
}
QT_END_NAMESPACE
diff --git a/src/network/ssl/qsslserver_p.h b/src/network/ssl/qsslserver_p.h
index b4b6490ed4b..3c7cce03551 100644
--- a/src/network/ssl/qsslserver_p.h
+++ b/src/network/ssl/qsslserver_p.h
@@ -16,8 +16,13 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+#include <QtCore/qhash.h>
+
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/private/qtcpserver_p.h>
+#include <utility>
QT_BEGIN_NAMESPACE
@@ -27,6 +32,26 @@ public:
Q_DECLARE_PUBLIC(QSslServer)
QSslServerPrivate();
+ void checkClientHelloAndContinue();
+ void initializeHandshakeProcess(QSslSocket *socket);
+ void removeSocketData(quintptr socket);
+
+ struct SocketData {
+ QMetaObject::Connection readyReadConnection;
+ QMetaObject::Connection destroyedConnection;
+
+ SocketData(QMetaObject::Connection readyRead, QMetaObject::Connection destroyed)
+ : readyReadConnection(readyRead), destroyedConnection(destroyed)
+ {
+ }
+
+ void disconnectSignals()
+ {
+ QObject::disconnect(std::exchange(readyReadConnection, {}));
+ QObject::disconnect(std::exchange(destroyedConnection, {}));
+ }
+ };
+ QHash<quintptr, SocketData> socketData;
QSslConfiguration sslConfiguration;
};
diff --git a/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
index b13114cb474..fb8a74d8dec 100644
--- a/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
+++ b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
@@ -23,6 +23,7 @@ private slots:
void testHandshakeInterruptedOnError();
void testPreSharedKeyAuthenticationRequired();
#endif
+ void plaintextClient();
private:
QString testDataDir;
@@ -436,6 +437,28 @@ void tst_QSslServer::testPreSharedKeyAuthenticationRequired()
#endif
+void tst_QSslServer::plaintextClient()
+{
+ QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
+ SslServerSpy server(serverConfiguration);
+ QVERIFY(server.server.listen());
+
+ QTcpSocket socket;
+ QSignalSpy socketDisconnectedSpy(&socket, &QTcpSocket::disconnected);
+ socket.connectToHost(QHostAddress::LocalHost, server.server.serverPort());
+ QVERIFY(socket.waitForConnected());
+ QTest::qWait(100);
+ // No disconnect from short break...:
+ QCOMPARE(socket.state(), QAbstractSocket::SocketState::ConnectedState);
+
+ // ... but we write some plaintext data...:
+ socket.write("Hello World!");
+ socket.waitForBytesWritten();
+ // ... and quickly get disconnected:
+ QTRY_COMPARE_GT(socketDisconnectedSpy.count(), 0);
+ QCOMPARE(socket.state(), QAbstractSocket::SocketState::UnconnectedState);
+}
+
QTEST_MAIN(tst_QSslServer)
#include "tst_qsslserver.moc"