diff --git a/src/tests/TestSSLLocks/TestSSLLocks.cpp b/src/tests/TestSSLLocks/TestSSLLocks.cpp new file mode 100644 index 00000000000..9963c43506a --- /dev/null +++ b/src/tests/TestSSLLocks/TestSSLLocks.cpp @@ -0,0 +1,106 @@ +// Copyright 2005-2017 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#include +#include + +#include "SSL.h" +#include "QAtomicIntCompat.h" + +#include + +/// SSLRacer is a thread that runs operations on OpenSSL's +/// RAND infrastructure, in an attempt to crash/segfault +/// the test process. +/// +/// The thread can either query OpenSSL for random bytes, or +/// seed the RAND system. This is controlled by the |seed| +/// parameter to the constructor. +class SSLRacer : public QThread { +public: + bool m_seed; + QAtomicInt *m_running; + + SSLRacer(QAtomicInt *running, bool seed) + : m_seed(seed) + , m_running(running) { + } + + void run() { + unsigned char buf[64]; + while (QAtomicIntLoad(*m_running) == 1) { + for (int i = 0; i < 1024; i++) { + if (m_seed) { + RAND_seed(buf, sizeof(buf)); + } else { + RAND_bytes(buf, sizeof(buf)); + } + } + } + } +}; + +class TestSSLLocks : public QObject { + Q_OBJECT + private slots: + void initTestCase(); + void cleanupTestCase(); + void stress(); +}; + +void TestSSLLocks::initTestCase() { + // For OpenSSL < 1.1, if you comment out this line, + // you'll be running OpenSSL without locking callbacks + // enabled. That'll make this test fail by crashing/segfaulting. + MumbleSSL::initialize(); +} + +void TestSSLLocks::cleanupTestCase() { + MumbleSSL::destroy(); +} + +/// Stress test that our locking callbacks for OpenSSL are set up and +/// working correctly. +/// +/// We do this by spawning a bunch of SSLStresser threads. Some of the +/// threads will try to read random bytes in a tight loop. Other threads +/// will, at the same time, try to add/seed random bytes to the OpenSSL +/// RAND system. +/// +/// The idea is that without proper locking, the data races we're causing +/// should quite quickly cause the process to crash. +void TestSSLLocks::stress() { + std::vector racers; + QAtomicInt running(1); + + // Spawn 24 threads in total. 12 readers, and 12 writers. + // Don't be too careful about cleaning up the threads. We'll either + // pass or crash, so the threads will be cleaned up either way. + int nthreads = 24; + for (int i = 0; i < nthreads; i++) { + bool seeder = i % 2; + SSLRacer *racer = new SSLRacer(&running, seeder); + racers.push_back(racer); + racer->start(); + } + + // Wait 2 seconds for a crash/segfault. + // If we don't crash within 2 seconds, we expect + // that our locking implementation works. + QTest::qSleep(2000); + + // Signal to the racers that they should stop. + running.fetchAndStoreOrdered(0); + + // Wait for all racers to complete. + for (size_t i = 0; i < racers.size(); i++) { + SSLRacer *racer = racers.at(i); + racer->wait(); + delete racer; + } +} + +QTEST_MAIN(TestSSLLocks) +#include "TestSSLLocks.moc" diff --git a/src/tests/TestSSLLocks/TestSSLLocks.pro b/src/tests/TestSSLLocks/TestSSLLocks.pro new file mode 100644 index 00000000000..443c1b25703 --- /dev/null +++ b/src/tests/TestSSLLocks/TestSSLLocks.pro @@ -0,0 +1,12 @@ +# Copyright 2005-2017 The Mumble Developers. All rights reserved. +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file at the root of the +# Mumble source tree or at . + +include(../test.pri) + +QT += network + +TARGET = TestSSLLocks +SOURCES = SSL.cpp SSLLocks.cpp TestSSLLocks.cpp +HEADERS = SSL.h SSLLocks.h diff --git a/src/tests/tests.pro b/src/tests/tests.pro index 2b608453baf..5ba7cf603a4 100644 --- a/src/tests/tests.pro +++ b/src/tests/tests.pro @@ -16,4 +16,5 @@ SUBDIRS += \ TestUnresolvedServerAddress \ TestServerAddress \ TestServerResolver \ - TestSelfSignedCertificate + TestSelfSignedCertificate \ + TestSSLLocks