diff options
Diffstat (limited to 'src/mongo/util')
26 files changed, 955 insertions, 338 deletions
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 0b794829149..429b0dfcb42 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -33,7 +33,7 @@ def fmtBuildInfo(data): fmtStr(env.subst(obj['value'])), fmtBool(obj['inBuildInfo']), fmtBool(obj['inVersion'])) - return ',\n'.join([fmtObj(obj) for _,obj in data.items()]) + return ',\n'.join([fmtObj(obj) for _, obj in data.items()]) buildInfoInitializer = fmtBuildInfo(env['MONGO_BUILDINFO_ENVIRONMENT_DATA']) @@ -564,6 +564,14 @@ env.Benchmark( ], ) +env.Benchmark( + target='tick_source_bm', + source=[ + 'tick_source_bm.cpp', + ], + LIBDEPS=[], +) + env.Library( target='future_util', source=[ @@ -771,6 +779,7 @@ icuEnv.CppUnitTest( 'processinfo', 'procparser' if env.TargetOSIs('linux') else [], 'progress_meter', + 'regex_util', 'safe_num', 'secure_zero_memory', 'summation', @@ -872,4 +881,3 @@ env.Benchmark( 'processinfo', ], ) - diff --git a/src/mongo/util/assert_util.h b/src/mongo/util/assert_util.h index d423fa3454f..d03605e6192 100644 --- a/src/mongo/util/assert_util.h +++ b/src/mongo/util/assert_util.h @@ -702,6 +702,19 @@ Status exceptionToStatus() noexcept; #define MONGO_UNREACHABLE_TASSERT(msgid) tasserted(msgid, "Hit a MONGO_UNREACHABLE_TASSERT!") /** + * Produces an invariant failure if executed. Subset of MONGO_UNREACHABLE, but specifically + * to indicate that the program has reached a function that is unimplemented and should be + * unreachable from production. + * Example: + * + * void myFuncToDo() { + * MONGO_UNIMPLEMENTED; + * } + */ +#define MONGO_UNIMPLEMENTED \ + ::mongo::invariantFailed("Hit a MONGO_UNIMPLEMENTED!", __FILE__, __LINE__); + +/** * Like `MONGO_UNIMPLEMENTED`, but triggers a `tassert` instead of an `invariant` */ #define MONGO_UNIMPLEMENTED_TASSERT(msgid) tasserted(msgid, "Hit a MONGO_UNIMPLEMENTED_TASSERT!") diff --git a/src/mongo/util/assert_util_test.cpp b/src/mongo/util/assert_util_test.cpp index d4740ea966e..68e45dff3e4 100644 --- a/src/mongo/util/assert_util_test.cpp +++ b/src/mongo/util/assert_util_test.cpp @@ -144,7 +144,7 @@ TEST(AssertUtils, UassertNamedCodeWithTwoCategories) { } MONGO_STATIC_ASSERT(!error_details::isNamedCode<19999>); -// ExceptionFor<ErrorCodes::Error(19999)> invalidType; // Must not compile. +// ExceptionFor<ErrorCodes::Error19999)> invalidType; // Must not compile. TEST(AssertUtils, UassertNumericCode) { ASSERT_CATCHES(19999, DBException); diff --git a/src/mongo/util/concurrency/ticketholder.cpp b/src/mongo/util/concurrency/ticketholder.cpp index 4de937b4bd6..f0ff7fdb4ce 100644 --- a/src/mongo/util/concurrency/ticketholder.cpp +++ b/src/mongo/util/concurrency/ticketholder.cpp @@ -40,13 +40,10 @@ #include <iostream> #include "mongo/logv2/log.h" -#include "mongo/util/fail_point.h" #include "mongo/util/str.h" namespace mongo { -MONGO_FAIL_POINT_DEFINE(hangTicketRelease); - TicketHolder::~TicketHolder() = default; #if defined(__linux__) @@ -147,12 +144,6 @@ boost::optional<Ticket> SemaphoreTicketHolder::waitForTicketUntil(OperationConte } void SemaphoreTicketHolder::release(AdmissionContext* admCtx, Ticket&& ticket) { - if (MONGO_unlikely(hangTicketRelease.shouldFail())) { - LOGV2(8435300, - "Hanging hangTicketRelease in release() due to 'hangTicketRelease' " - "failpoint"); - hangTicketRelease.pauseWhileSet(); - } check(sem_post(&_sem)); ticket.release(); } @@ -250,12 +241,6 @@ boost::optional<Ticket> SemaphoreTicketHolder::waitForTicketUntil(OperationConte } void SemaphoreTicketHolder::release(AdmissionContext* admCtx, Ticket&& ticket) { - if (MONGO_unlikely(hangTicketRelease.shouldFail())) { - LOGV2(8435301, - "Hanging hangTicketRelease in release() due to 'hangTicketRelease' " - "failpoint"); - hangTicketRelease.pauseWhileSet(); - } { stdx::lock_guard<Latch> lk(_mutex); _num++; diff --git a/src/mongo/util/future_test_utils.h b/src/mongo/util/future_test_utils.h index 8bbecfd1faa..ec7bd783030 100644 --- a/src/mongo/util/future_test_utils.h +++ b/src/mongo/util/future_test_utils.h @@ -71,12 +71,6 @@ class DummyInterruptible final : public Interruptible { // Must be implemented because it's called by Interruptible::waitForConditionOrInterrupt. return Status::OK(); } - IgnoreInterruptsState pushIgnoreInterrupts() override { - MONGO_UNREACHABLE; - } - void popIgnoreInterrupts(IgnoreInterruptsState iis) override { - MONGO_UNREACHABLE; - } DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override { MONGO_UNREACHABLE; } diff --git a/src/mongo/util/interruptible.h b/src/mongo/util/interruptible.h index 55cc25944af..1e9cc070c64 100644 --- a/src/mongo/util/interruptible.h +++ b/src/mongo/util/interruptible.h @@ -107,28 +107,6 @@ protected: * Returns the equivalent of Date_t::now() + waitFor for the InterruptibleBase's clock */ virtual Date_t getExpirationDateForWaitForValue(Milliseconds waitFor) = 0; - - struct IgnoreInterruptsState { - bool ignoreInterrupts; - DeadlineState deadline; - }; - - /** - * Pushes an ignore interruption critical section into the InterruptibleBase. - * Until an associated popIgnoreInterrupts() is invoked, the InterruptibleBase should ignore - * interruptions related to explicit interruption or previously set deadlines. - * - * Note that new deadlines can be set after this is called, which will again introduce the - * possibility of interruption. - * - * Returns state needed to pop interruption. - */ - virtual IgnoreInterruptsState pushIgnoreInterrupts() = 0; - - /** - * Pops the ignored interruption critical section introduced by push. - */ - virtual void popIgnoreInterrupts(IgnoreInterruptsState iis) = 0; }; /** @@ -194,44 +172,6 @@ private: return DeadlineGuard(*this, deadline, error); } - /** - * An interruption guard provides a region where interruption is ignored. - * - * Note that this causes the deadline to be reset to Date_t::max(), but that it can also be - * subsequently reduced in size after the fact. - */ - class IgnoreInterruptionsGuard { - public: - IgnoreInterruptionsGuard(const IgnoreInterruptionsGuard&) = delete; - IgnoreInterruptionsGuard& operator=(const IgnoreInterruptionsGuard&) = delete; - - IgnoreInterruptionsGuard(IgnoreInterruptionsGuard&& other) - : _interruptible(other._interruptible), _oldState(other._oldState) { - other._interruptible = nullptr; - } - - IgnoreInterruptionsGuard& operator=(IgnoreInterruptionsGuard&&) = delete; - - ~IgnoreInterruptionsGuard() { - if (_interruptible) { - _interruptible->popIgnoreInterrupts(_oldState); - } - } - - private: - friend Interruptible; - - explicit IgnoreInterruptionsGuard(Interruptible& interruptible) - : _interruptible(&interruptible), _oldState(_interruptible->pushIgnoreInterrupts()) {} - - Interruptible* _interruptible; - IgnoreInterruptsState _oldState; - }; - - IgnoreInterruptionsGuard makeIgnoreInterruptionsGuard() { - return IgnoreInterruptionsGuard(*this); - } - public: class WaitListener; @@ -306,24 +246,6 @@ public: } /** - * Invokes the passed callback with an interruption guard active. Additionally handles the - * dance of try/catching the invocation and checking checkForInterrupt with the guard inactive - * (to allow a higher level timeout to override a lower level one, or for top level interruption - * to propagate) - */ - template <typename Callback> - decltype(auto) runWithoutInterruptionExceptAtGlobalShutdown(Callback&& cb) { - try { - const auto guard = makeIgnoreInterruptionsGuard(); - return std::forward<Callback>(cb)(); - } catch (const ExceptionForCat<ErrorCategory::ExceededTimeLimitError>&) { - // May throw replacement exception - checkForInterrupt(); - throw; - } - } - - /** * Raises a AssertionException if this operation is in a killed state. */ void checkForInterrupt() { @@ -591,19 +513,6 @@ class Interruptible::NotInterruptible final : public Interruptible { return Status::OK(); } - // It's invalid to call the deadline or ignore interruption guards on a possibly noop - // Interruptible. - // - // The noop Interruptible should only be invoked as a default arg at the bottom of the call - // stack (with types that won't modify it's invocation) - IgnoreInterruptsState pushIgnoreInterrupts() override { - MONGO_UNREACHABLE; - } - - void popIgnoreInterrupts(IgnoreInterruptsState) override { - MONGO_UNREACHABLE; - } - DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override { MONGO_UNREACHABLE; } diff --git a/src/mongo/util/intrusive_counter.cpp b/src/mongo/util/intrusive_counter.cpp index e33cbc87da5..4c8efad7959 100644 --- a/src/mongo/util/intrusive_counter.cpp +++ b/src/mongo/util/intrusive_counter.cpp @@ -52,7 +52,8 @@ intrusive_ptr<const RCString> RCString::create(StringData s) { ptr->_size = s.size(); char* stringStart = reinterpret_cast<char*>(ptr.get()) + sizeof(RCString); - s.copyTo(stringStart, true); + s.copy(stringStart, s.size()); + stringStart[s.size()] = '\0'; return ptr; } diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index 272cd9740a1..f885a122325 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -252,9 +252,10 @@ if get_option('ssl') == 'on': env.CppUnitTest( target='util_net_ssl_test', source=[ - 'ssl_manager_test.cpp', - 'ssl_options_test.cpp', - 'sock_test.cpp', + "ssl_manager_test.cpp", + "ssl_options_test.cpp", + "sock_test.cpp", + "sock_test_utils.cpp", ], LIBDEPS=[ '$BUILD_DIR/mongo/client/connection_string', diff --git a/src/mongo/util/net/sock_test.cpp b/src/mongo/util/net/sock_test.cpp index ccb751ea2dd..84d91f5ba1b 100644 --- a/src/mongo/util/net/sock_test.cpp +++ b/src/mongo/util/net/sock_test.cpp @@ -31,176 +31,17 @@ #include "mongo/util/net/sock.h" -#ifndef _WIN32 -#include <netdb.h> -#include <sys/socket.h> -#include <sys/types.h> -#endif - #include "mongo/db/server_options.h" #include "mongo/stdx/thread.h" #include "mongo/unittest/unittest.h" #include "mongo/util/concurrency/notification.h" #include "mongo/util/fail_point.h" +#include "mongo/util/net/sock_test_utils.h" #include "mongo/util/net/socket_exception.h" namespace { using namespace mongo; -using std::shared_ptr; - -typedef std::shared_ptr<Socket> SocketPtr; -typedef std::pair<SocketPtr, SocketPtr> SocketPair; - -// On UNIX, make a connected pair of PF_LOCAL (aka PF_UNIX) sockets via the native 'socketpair' -// call. The 'type' parameter should be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, etc. -// For Win32, we don't have a native socketpair function, so we hack up a connected PF_INET -// pair on a random port. -SocketPair socketPair(int type, int protocol = 0); - -#if defined(_WIN32) -namespace detail { -void awaitAccept(SOCKET* acceptSock, SOCKET listenSock, Notification<void>& notify) { - *acceptSock = INVALID_SOCKET; - const SOCKET result = ::accept(listenSock, nullptr, 0); - if (result != INVALID_SOCKET) { - *acceptSock = result; - } - notify.set(); -} - -void awaitConnect(SOCKET* connectSock, const struct addrinfo& where, Notification<void>& notify) { - *connectSock = INVALID_SOCKET; - SOCKET newSock = ::socket(where.ai_family, where.ai_socktype, where.ai_protocol); - if (newSock != INVALID_SOCKET) { - int result = ::connect(newSock, where.ai_addr, where.ai_addrlen); - if (result == 0) { - *connectSock = newSock; - } - } - notify.set(); -} -} // namespace detail - -SocketPair socketPair(const int type, const int protocol) { - const int domain = PF_INET; - - // Create a listen socket and a connect socket. - const SOCKET listenSock = ::socket(domain, type, protocol); - if (listenSock == INVALID_SOCKET) - return SocketPair(); - - // Bind the listen socket on port zero, it will pick one for us, and start it listening - // for connections. - struct addrinfo hints, *res; - ::memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_INET; - hints.ai_socktype = type; - hints.ai_flags = AI_PASSIVE; - - int result = ::getaddrinfo(nullptr, "0", &hints, &res); - if (result != 0) { - closesocket(listenSock); - return SocketPair(); - } - - result = ::bind(listenSock, res->ai_addr, res->ai_addrlen); - if (result != 0) { - closesocket(listenSock); - ::freeaddrinfo(res); - return SocketPair(); - } - - // Read out the port to which we bound. - sockaddr_in bindAddr; - ::socklen_t len = sizeof(bindAddr); - ::memset(&bindAddr, 0, sizeof(bindAddr)); - result = ::getsockname(listenSock, reinterpret_cast<struct sockaddr*>(&bindAddr), &len); - if (result != 0) { - closesocket(listenSock); - ::freeaddrinfo(res); - return SocketPair(); - } - - result = ::listen(listenSock, 1); - if (result != 0) { - closesocket(listenSock); - ::freeaddrinfo(res); - return SocketPair(); - } - - struct addrinfo connectHints, *connectRes; - ::memset(&connectHints, 0, sizeof(connectHints)); - connectHints.ai_family = PF_INET; - connectHints.ai_socktype = type; - std::stringstream portStream; - portStream << ntohs(bindAddr.sin_port); - result = ::getaddrinfo(nullptr, portStream.str().c_str(), &connectHints, &connectRes); - if (result != 0) { - closesocket(listenSock); - ::freeaddrinfo(res); - return SocketPair(); - } - - // I'd prefer to avoid trying to do this non-blocking on Windows. Just spin up some - // threads to do the connect and acccept. - - Notification<void> accepted; - SOCKET acceptSock = INVALID_SOCKET; - stdx::thread acceptor([&] { detail::awaitAccept(&acceptSock, listenSock, accepted); }); - - Notification<void> connected; - SOCKET connectSock = INVALID_SOCKET; - stdx::thread connector([&] { detail::awaitConnect(&connectSock, *connectRes, connected); }); - - connected.get(); - connector.join(); - if (connectSock == INVALID_SOCKET) { - closesocket(listenSock); - ::freeaddrinfo(res); - ::freeaddrinfo(connectRes); - closesocket(acceptSock); - closesocket(connectSock); - return SocketPair(); - } - - accepted.get(); - acceptor.join(); - if (acceptSock == INVALID_SOCKET) { - closesocket(listenSock); - ::freeaddrinfo(res); - ::freeaddrinfo(connectRes); - closesocket(acceptSock); - closesocket(connectSock); - return SocketPair(); - } - - closesocket(listenSock); - ::freeaddrinfo(res); - ::freeaddrinfo(connectRes); - - SocketPtr first(new Socket(static_cast<int>(acceptSock), SockAddr())); - SocketPtr second(new Socket(static_cast<int>(connectSock), SockAddr())); - - return SocketPair(first, second); -} -#else -// We can just use ::socketpair and wrap up the result in a Socket. -SocketPair socketPair(const int type, const int protocol) { - // PF_LOCAL is the POSIX name for Unix domain sockets, while PF_UNIX - // is the name that BSD used. We use the BSD name because it is more - // widely supported (e.g. Solaris 10). - const int domain = PF_UNIX; - - int socks[2]; - const int result = ::socketpair(domain, type, protocol, socks); - if (result == 0) { - return SocketPair(SocketPtr(new Socket(socks[0], SockAddr())), - SocketPtr(new Socket(socks[1], SockAddr()))); - } - return SocketPair(); -} -#endif // This should match the name of the fail point declared in sock.cpp. const char kSocketFailPointName[] = "throwSockExcep"; diff --git a/src/mongo/util/net/sock_test_utils.cpp b/src/mongo/util/net/sock_test_utils.cpp new file mode 100644 index 00000000000..9164b8077ca --- /dev/null +++ b/src/mongo/util/net/sock_test_utils.cpp @@ -0,0 +1,199 @@ +/** + * Copyright (C) 2024-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/util/net/sock_test_utils.h" + +#ifndef _WIN32 +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> +#endif + +#include "mongo/stdx/thread.h" +#include "mongo/util/concurrency/notification.h" +#include "mongo/util/net/socket_exception.h" + +namespace mongo { +namespace { + +#if defined(_WIN32) +namespace detail { +void awaitAccept(SOCKET* acceptSock, SOCKET listenSock, Notification<void>& notify) { + *acceptSock = INVALID_SOCKET; + const SOCKET result = ::accept(listenSock, nullptr, 0); + if (result != INVALID_SOCKET) { + *acceptSock = result; + } + notify.set(); +} + +void awaitConnect(SOCKET* connectSock, const struct addrinfo& where, Notification<void>& notify) { + *connectSock = INVALID_SOCKET; + SOCKET newSock = ::socket(where.ai_family, where.ai_socktype, where.ai_protocol); + if (newSock != INVALID_SOCKET) { + int result = ::connect(newSock, where.ai_addr, where.ai_addrlen); + if (result == 0) { + *connectSock = newSock; + } + } + notify.set(); +} +} // namespace detail + +SocketPair socketPairImpl(const int type, const int protocol) { + const int domain = PF_INET; + + // Create a listen socket and a connect socket. + const SOCKET listenSock = ::socket(domain, type, protocol); + if (listenSock == INVALID_SOCKET) + return SocketPair(); + + // Bind the listen socket on port zero, it will pick one for us, and start it listening + // for connections. + struct addrinfo hints, *res; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_socktype = type; + hints.ai_flags = AI_PASSIVE; + + int result = ::getaddrinfo(nullptr, "0", &hints, &res); + if (result != 0) { + closesocket(listenSock); + return SocketPair(); + } + + result = ::bind(listenSock, res->ai_addr, res->ai_addrlen); + if (result != 0) { + closesocket(listenSock); + ::freeaddrinfo(res); + return SocketPair(); + } + + // Read out the port to which we bound. + sockaddr_in bindAddr; + ::socklen_t len = sizeof(bindAddr); + ::memset(&bindAddr, 0, sizeof(bindAddr)); + result = ::getsockname(listenSock, reinterpret_cast<struct sockaddr*>(&bindAddr), &len); + if (result != 0) { + closesocket(listenSock); + ::freeaddrinfo(res); + return SocketPair(); + } + + result = ::listen(listenSock, 1); + if (result != 0) { + closesocket(listenSock); + ::freeaddrinfo(res); + return SocketPair(); + } + + struct addrinfo connectHints, *connectRes; + ::memset(&connectHints, 0, sizeof(connectHints)); + connectHints.ai_family = PF_INET; + connectHints.ai_socktype = type; + std::stringstream portStream; + portStream << ntohs(bindAddr.sin_port); + result = ::getaddrinfo(nullptr, portStream.str().c_str(), &connectHints, &connectRes); + if (result != 0) { + closesocket(listenSock); + ::freeaddrinfo(res); + return SocketPair(); + } + + // I'd prefer to avoid trying to do this non-blocking on Windows. Just spin up some + // threads to do the connect and acccept. + + Notification<void> accepted; + SOCKET acceptSock = INVALID_SOCKET; + stdx::thread acceptor([&] { detail::awaitAccept(&acceptSock, listenSock, accepted); }); + + Notification<void> connected; + SOCKET connectSock = INVALID_SOCKET; + stdx::thread connector([&] { detail::awaitConnect(&connectSock, *connectRes, connected); }); + + connected.get(); + connector.join(); + if (connectSock == INVALID_SOCKET) { + closesocket(listenSock); + ::freeaddrinfo(res); + ::freeaddrinfo(connectRes); + closesocket(acceptSock); + closesocket(connectSock); + return SocketPair(); + } + + accepted.get(); + acceptor.join(); + if (acceptSock == INVALID_SOCKET) { + closesocket(listenSock); + ::freeaddrinfo(res); + ::freeaddrinfo(connectRes); + closesocket(acceptSock); + closesocket(connectSock); + return SocketPair(); + } + + closesocket(listenSock); + ::freeaddrinfo(res); + ::freeaddrinfo(connectRes); + + SocketPtr first = std::make_shared<Socket>(static_cast<int>(acceptSock), SockAddr()); + SocketPtr second = std::make_shared<Socket>(static_cast<int>(connectSock), SockAddr()); + return SocketPair(first, second); +} +#else +// We can just use ::socketpair and wrap up the result in a Socket. +SocketPair socketPairImpl(const int type, const int protocol) { + // PF_LOCAL is the POSIX name for Unix domain sockets, while PF_UNIX + // is the name that BSD used. We use the BSD name because it is more + // widely supported (e.g. Solaris 10). + const int domain = PF_UNIX; + + int socks[2]; + const int result = ::socketpair(domain, type, protocol, socks); + if (result == 0) { + return SocketPair(std::make_shared<Socket>(socks[0], SockAddr()), + std::make_shared<Socket>(socks[1], SockAddr())); + } + return SocketPair(); +} +#endif +} // namespace + +// On UNIX, make a connected pair of PF_LOCAL (aka PF_UNIX) sockets via the native 'socketpair' +// call. The 'type' parameter should be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, etc. +// For Win32, we don't have a native socketpair function, so we hack up a connected PF_INET +// pair on a random port. +SocketPair socketPair(int type, int protocol) { + return socketPairImpl(type, protocol); +} + +} // namespace mongo diff --git a/src/mongo/util/net/sock_test_utils.h b/src/mongo/util/net/sock_test_utils.h new file mode 100644 index 00000000000..f168edf1292 --- /dev/null +++ b/src/mongo/util/net/sock_test_utils.h @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2024-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/util/net/sock.h" + +namespace mongo { +using SocketPtr = std::shared_ptr<Socket>; +using SocketPair = std::pair<SocketPtr, SocketPtr>; + +// Create a connected pair of sockets for testing purposes. +SocketPair socketPair(int type, int protocol = 0); + +} // namespace mongo diff --git a/src/mongo/util/net/sockaddr.cpp b/src/mongo/util/net/sockaddr.cpp index 1a2b058cbf3..5bf2998df6d 100644 --- a/src/mongo/util/net/sockaddr.cpp +++ b/src/mongo/util/net/sockaddr.cpp @@ -149,7 +149,7 @@ void SockAddr::initUnixDomainSocket(StringData path, int port) { uassert( 13079, "path to unix socket too long", path.size() < sizeof(as<sockaddr_un>().sun_path)); as<sockaddr_un>().sun_family = AF_UNIX; - path.copyTo(as<sockaddr_un>().sun_path, /* includeEndingNull =*/true); + str::copyAsCString(as<sockaddr_un>().sun_path, path); addressSize = sizeof(sockaddr_un); _isValid = true; } diff --git a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp index 015a44fcee8..b863cb0e75a 100644 --- a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp +++ b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp @@ -158,10 +158,6 @@ bool engine::_initSSL(stream_base::handshake_type type, asio::error_code& ec) { } if (status == ::errSecSuccess) { - status = ::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(native_handle_type)); - } - - if (status == ::errSecSuccess) { status = ::SSLSetIOFuncs(_ssl.get(), read_func, write_func); } diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp index c13a4bb8eb8..dcde5542ca9 100644 --- a/src/mongo/util/net/ssl_manager_apple.cpp +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -1156,7 +1156,6 @@ public: } uassertOSStatusOK(::SSLSetConnection(_ssl.get(), static_cast<void*>(this))); - uassertOSStatusOK(::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(_ssl))); uassertOSStatusOK(::SSLSetIOFuncs(_ssl.get(), read_func, write_func)); uassertOSStatusOK(::SSLSetProtocolVersionMin(_ssl.get(), ctx->protoMin)); uassertOSStatusOK(::SSLSetProtocolVersionMax(_ssl.get(), ctx->protoMax)); @@ -1654,11 +1653,14 @@ Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( return swPeerSubjectName.getStatus(); } const auto peerSubjectName = std::move(swPeerSubjectName.getValue()); - LOGV2_DEBUG(23207, - 2, - "Accepted TLS connection from peer: {peerSubjectName}", - "Accepted TLS connection from peer", - "peerSubjectName"_attr = peerSubjectName); + // The cipher will be presented as a number. + ::SSLCipherSuite cipher; + uassertOSStatusOK(::SSLGetNegotiatedCipher(ssl, &cipher)); + + LOGV2_INFO(6723803, + "Accepted TLS connection from peer", + "peerSubjectName"_attr = peerSubjectName, + "cipher"_attr = cipher); // Server side. if (remoteHost.empty()) { diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 43c646bc16c..b27676156d6 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -319,23 +319,6 @@ X509* X509_OBJECT_get0_X509(const X509_OBJECT* a) { return a->data.x509; } -UniqueStackOfX509 SSLgetVerifiedChain(SSL* s) { - auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s)); - auto* peerChain = SSL_get_peer_cert_chain(s); - UniqueX509 peer(SSL_get_peer_certificate(s)); - - UniqueX509StoreCtx ctx(X509_STORE_CTX_new()); - if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) { - return nullptr; - } - - if (X509_verify_cert(ctx.get()) <= 0) { - return nullptr; - } - - return UniqueStackOfX509(X509_STORE_CTX_get1_chain(ctx.get())); -} - const OCSP_CERTID* OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP* single) { return single->certId; } @@ -368,16 +351,24 @@ static ASN1OID tlsFeatureOID("1.3.6.1.5.5.7.1.24", "tlsfeature", "TLS Feature"); static int const NID_tlsfeature = OBJ_create(tlsFeatureOID.identifier.c_str(), tlsFeatureOID.shortDescription.c_str(), tlsFeatureOID.longDescription.c_str()); +#endif -#else UniqueStackOfX509 SSLgetVerifiedChain(SSL* s) { - auto chain = SSL_get0_verified_chain(s); + auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s)); + auto* peerChain = SSL_get_peer_cert_chain(s); + UniqueX509 peer(SSL_get_peer_certificate(s)); - return UniqueStackOfX509(X509_chain_up_ref(chain)); -} + UniqueX509StoreCtx ctx(X509_STORE_CTX_new()); + if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) { + return nullptr; + } -#endif + if (X509_verify_cert(ctx.get()) <= 0) { + return nullptr; + } + return UniqueStackOfX509(X509_STORE_CTX_get1_chain(ctx.get())); +} SSLX509Name convertX509ToSSLX509Name(X509_NAME* x509Name) { std::vector<std::vector<SSLX509Name::Entry>> entries; @@ -2996,7 +2987,7 @@ bool SSLManagerOpenSSL::_setupCRL(SSL_CTX* context, const std::string& crlFile) X509_STORE* store = SSL_CTX_get_cert_store(context); fassert(16583, store); - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); fassert(16584, lookup); @@ -3010,15 +3001,7 @@ bool SSLManagerOpenSSL::_setupCRL(SSL_CTX* context, const std::string& crlFile) return false; } - if (status == 1) { - LOGV2(4652601, "ssl imported 1 revoked certificate from the revocation list."); - } else { - LOGV2(4652602, - "ssl imported {numberCerts} revoked certificates from the revocation list", - "SSL imported revoked certificates from the revocation list", - "numberCerts"_attr = status); - } - + LOGV2(4652602, "SSL imported certificate revocation list(s)", "numberCRLs"_attr = status); return true; } @@ -3280,11 +3263,11 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( // TODO: check optional cipher restriction, using cert. auto peerSubject = getCertificateSubjectX509Name(peerCert.get()); - LOGV2_DEBUG(23229, - 2, - "Accepted TLS connection from peer: {peerSubject}", - "Accepted TLS connection from peer", - "peerSubject"_attr = peerSubject); + const auto cipher = SSL_get_current_cipher(conn); + LOGV2_INFO(6723801, + "Accepted TLS connection from peer", + "peerSubject"_attr = peerSubject, + "cipher"_attr = SSL_CIPHER_get_name(cipher)); StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = _parsePeerRoles(peerCert.get()); diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp index 1dd6585f7c1..80e83af242a 100644 --- a/src/mongo/util/net/ssl_manager_test.cpp +++ b/src/mongo/util/net/ssl_manager_test.cpp @@ -29,15 +29,20 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest +#include <asio.hpp> +#include <boost/filesystem.hpp> #include <fstream> #include "mongo/config.h" #include "mongo/platform/basic.h" +#include "mongo/bson/json.h" #include "mongo/transport/service_entry_point.h" #include "mongo/transport/transport_layer_asio.h" #include "mongo/transport/transport_layer_manager.h" +#include "mongo/util/net/sock_test_utils.h" #include "mongo/util/net/ssl/context.hpp" +#include "mongo/util/net/ssl/stream.hpp" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" @@ -49,10 +54,36 @@ #include "mongo/util/net/ssl/context_openssl.hpp" #endif +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest + +namespace fs = boost::filesystem; namespace mongo { namespace { +#define TEST_CERTS_DIR "jstests/libs/" +// certs & CRLs rooted in ca.pem +constexpr const char* caFile = TEST_CERTS_DIR "ca.pem"; +constexpr const char* serverKeyFile = TEST_CERTS_DIR "server.pem"; +constexpr const char* clientKeyFile = TEST_CERTS_DIR "client.pem"; +constexpr const char* revokedClientKeyFile = TEST_CERTS_DIR "client_revoked.pem"; + +constexpr const char* intermediateACaFile = TEST_CERTS_DIR "intermediate-ca.pem"; +constexpr const char* intermediateALeafKeyFile = TEST_CERTS_DIR "server-intermediate-leaf.pem"; +constexpr const char* intermediateBCaFile = TEST_CERTS_DIR "intermediate-ca-B.pem"; +constexpr const char* intermediateBLeafKeyFile = TEST_CERTS_DIR "intermediate-ca-B-leaf.pem"; +constexpr const char* emptyCRL = TEST_CERTS_DIR "crl.pem"; +constexpr const char* expiredCRL = TEST_CERTS_DIR "crl_expired.pem"; +constexpr const char* clientRevokedCRL = TEST_CERTS_DIR "crl_client_revoked.pem"; +constexpr const char* intermediateBRevokedCRL = TEST_CERTS_DIR "crl_intermediate_ca_B_revoked.pem"; +constexpr const char* intermediateBCRL = TEST_CERTS_DIR "crl_from_intermediate_ca_B.pem"; + +// certs & CRLs rooted in trusted-ca.pem +constexpr const char* trustedCaFile = TEST_CERTS_DIR "trusted-ca.pem"; +constexpr const char* trustedServerKeyFile = TEST_CERTS_DIR "trusted-server.pem"; +constexpr const char* trustedClientKeyFile = TEST_CERTS_DIR "trusted-client.pem"; +constexpr const char* trustedEmptyCRL = TEST_CERTS_DIR "crl_from_trusted_ca.pem"; + // Test implementation needed by ASIO transport. class ServiceEntryPointUtil : public ServiceEntryPoint { public: @@ -103,7 +134,7 @@ public: } private: - mutable Mutex _mutex = MONGO_MAKE_LATCH("::_mutex"); + mutable Mutex _mutex; stdx::condition_variable _cv; std::vector<transport::SessionHandle> _sessions; transport::TransportLayer* _transport = nullptr; @@ -115,6 +146,76 @@ std::string loadFile(const std::string& name) { return str; } +// Reads the input stream until EOF or a valid PEM block is encountered. +// Skips private key PEM blocks if includePrivateKeys is true. +// Returns the parsed PEM block as a string (with newlines), or an empty +// string if none is found or a read error occurs. +std::string readOnePEMBlock(std::ifstream& inputStrm, bool includePrivateKeys) { + std::string line; + for (;;) { + std::stringstream output; + bool foundBegin = false; + bool foundEnd = false; + bool discard = false; + + while (!foundBegin && std::getline(inputStrm, line)) { + StringData lineSD(line); + foundBegin = (lineSD.startsWith("-----BEGIN ") && lineSD.endsWith("-----")); + } + if (!foundBegin) { + return ""; + } + + discard = (!includePrivateKeys && line.find("PRIVATE KEY") != std::string::npos); + output << line << std::endl; + + while (!foundEnd && std::getline(inputStrm, line)) { + StringData lineSD(line); + output << line << std::endl; + foundEnd = (lineSD.startsWith("-----END ") && lineSD.endsWith("-----")); + } + if (!foundEnd) { + return ""; + } + if (!discard) { + return output.str(); + } + } +} + +struct PEMFileSpec { + std::string path; + bool includePrivateKeys{false}; + void serialize(BSONObjBuilder* bob) const { + bob->append("path", path); + bob->append("includePrivateKeys", includePrivateKeys); + } +}; +// Given a list of PEM files, this concatenates the PEM blocks in those files +// (optionally filtering out private keys) and writes the result into a temporary +// file. Returns the path to the temp file. +std::string combinePEMFiles(const std::vector<PEMFileSpec>& pemSpecs) { + // make a temp file for the output + auto path = fs::temp_directory_path() / fs::unique_path("tmpfile_%%%%_%%%%_%%%%_%%%%.pem"); + std::ofstream outStream(path.string()); + invariant(outStream.is_open()); + + LOGV2( + 9476600, "Combining PEM files", "output"_attr = path.string(), "pemFiles"_attr = pemSpecs); + + // read & parse the PEM files; append PEM blocks to output + for (auto& pemSpec : pemSpecs) { + std::ifstream input(pemSpec.path); + std::string pemBlock; + do { + pemBlock = readOnePEMBlock(input, pemSpec.includePrivateKeys); + outStream << pemBlock; + } while (!pemBlock.empty()); + } + outStream.close(); + return path.string(); +} + TEST(SSLManager, matchHostname) { enum Expected : bool { match = true, mismatch = false }; const struct { @@ -734,6 +835,8 @@ TEST(SSLManager, TransientSSLParamsStressTestWithManager) { #endif // MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL +#ifdef MONGO_CONFIG_SSL + static bool isSanWarningWritten(const std::vector<std::string>& logLines) { for (const auto& line : logLines) { if (std::string::npos != @@ -780,6 +883,288 @@ TEST(SSLManager, InitContextNoSanWarning) { ASSERT_FALSE(isSanWarningWritten(getCapturedTextFormatLogMessages())); } +class SSLTestFixture { +public: + SSLTestFixture(const SSLParams& ingressParams, + const SSLParams& egressParams, + bool ingressIsServer = true, + bool egressIsServer = true, + const boost::optional<TransientSSLParams>& transientSSLParams = boost::none) { + auto serviceContext = ServiceContext::make(); + setGlobalServiceContext(std::move(serviceContext)); + + // SSLManagerWindows uses this global boolean to decide whether to + // use unique key container names when setting up the crypto context. + // This must be true in order for the handshake to work. + isSSLServer = true; + + serverSSLManager = SSLManagerInterface::create(ingressParams, ingressIsServer); + clientSSLManager = + SSLManagerInterface::create(egressParams, transientSSLParams, egressIsServer); + + serverSSLContext = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23); + clientSSLContext = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23); + uassertStatusOK( + serverSSLManager->initSSLContext(serverSSLContext->native_handle(), + ingressParams, + SSLManagerInterface::ConnectionDirection::kIncoming)); + uassertStatusOK( + clientSSLManager->initSSLContext(clientSSLContext->native_handle(), + egressParams, + SSLManagerInterface::ConnectionDirection::kOutgoing)); + } + + void doHandshake() { + auto socks = socketPair(SOCK_STREAM); + + serverConn = std::make_shared<ConnectionContext>(socks.first->rawFD(), *serverSSLContext); + clientConn = std::make_shared<ConnectionContext>(socks.second->rawFD(), *clientSSLContext); + Status serverStatus = Status::OK(); + Status clientStatus = Status::OK(); + + auto serverThread = stdx::thread([this, &serverStatus]() { + try { + serverConn->sslSocket->handshake(asio::ssl::stream_base::server); + } catch (const DBException& ex) { + serverStatus = ex.toStatus().withContext("Server handshake failed"); + } + }); + + try { + clientConn->sslSocket->handshake(asio::ssl::stream_base::client); + } catch (const DBException& ex) { + clientStatus = ex.toStatus().withContext("Client handshake failed"); + } + serverThread.join(); + + // rethrow any handshake errors with context + uassertStatusOK(serverStatus); + uassertStatusOK(clientStatus); + } + + struct IngressEgressValidationResult { + StatusWith<SSLPeerInfo> ingress; + StatusWith<SSLPeerInfo> egress; + }; + IngressEgressValidationResult runIngressEgressValidation(); + + class ConnectionContext { + public: + ConnectionContext(int fd, asio::ssl::context& ctx) : io_context() { + asio::ip::tcp::socket socket(io_context, asio::ip::tcp::v4(), fd); + sslSocket = + std::make_unique<asio::ssl::stream<decltype(socket)>>(std::move(socket), ctx, ""); + } + asio::io_context io_context; + std::unique_ptr<asio::ssl::stream<asio::ip::tcp::socket>> sslSocket; + }; + + std::shared_ptr<SSLManagerInterface> clientSSLManager; + std::shared_ptr<SSLManagerInterface> serverSSLManager; + + std::shared_ptr<asio::ssl::context> clientSSLContext; + std::shared_ptr<asio::ssl::context> serverSSLContext; + + std::shared_ptr<ConnectionContext> clientConn; + std::shared_ptr<ConnectionContext> serverConn; +}; + +SSLTestFixture::IngressEgressValidationResult SSLTestFixture::runIngressEgressValidation() { + static const HostAndPort hostForLogging("hostforlogging"); + + // Caller must doHandshake beforehand + invariant(serverConn); + invariant(clientConn); + + IngressEgressValidationResult result{SSLPeerInfo{}, SSLPeerInfo{}}; + + // do ingress (server) first + try { + result.ingress = + serverSSLManager + ->parseAndValidatePeerCertificate(serverConn->sslSocket->native_handle(), + boost::none, + "", + hostForLogging, + nullptr) + .get(); + } catch (const DBException& ex) { + result.ingress = ex.toStatus(); + } + + // do egress (client) next + try { + result.egress = + clientSSLManager + ->parseAndValidatePeerCertificate(clientConn->sslSocket->native_handle(), + boost::none, + "localhost", + hostForLogging, + nullptr) + .get(); + } catch (const DBException& ex) { + result.egress = ex.toStatus(); + } + + return result; +} + +struct CertValidationTestCase { + std::string cafile; + std::string clusterCaFile; + bool pass; + bool allowInvalidCerts{false}; + + void serialize(BSONObjBuilder* bob) const { + bob->append("CAFile", cafile); + bob->append("clusterCAFile", clusterCaFile); + bob->append("expectPass", pass); + bob->append("allowInvalidCerts", allowInvalidCerts); + } +}; + +void checkValidationResults(SSLTestFixture::IngressEgressValidationResult& result, + bool expectIngressPass, + bool expectEgressPass, + ErrorCodes::Error expectIngressCode = ErrorCodes::SSLHandshakeFailed, + ErrorCodes::Error expectEgressCode = ErrorCodes::SSLHandshakeFailed) { + ASSERT_EQ(result.ingress.isOK(), expectIngressPass) + << "Ingress validation status: " << result.ingress.getStatus(); + ASSERT_EQ(result.egress.isOK(), expectEgressPass) + << "Egress validation status: " << result.egress.getStatus(); + if (!result.ingress.isOK()) { + ASSERT_EQ(result.ingress.getStatus().code(), expectIngressCode) + << "Ingress validation status: " << result.ingress.getStatus(); + } + if (!result.egress.isOK()) { + ASSERT_EQ(result.egress.getStatus().code(), expectEgressCode) + << "Egress validation status: " << result.egress.getStatus(); + } +} + +// Tests that validation fails if configured CRL for the issuer of the peer certificate being +// validated has expired. +// Caveats: +// - Apple: CRL unsupported; test disabled +// - Windows: validation fails, but with misleading error message +#if MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE +TEST(SSLManager, expiredCRLTest) { + SSLParams clientParams; + clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + clientParams.sslAllowInvalidHostnames = true; + clientParams.sslCAFile = caFile; + clientParams.sslPEMKeyFile = clientKeyFile; + clientParams.sslCRLFile = expiredCRL; + + SSLParams serverParams; + serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + serverParams.sslAllowInvalidHostnames = true; + serverParams.sslCAFile = caFile; + serverParams.sslPEMKeyFile = serverKeyFile; + serverParams.sslCRLFile = expiredCRL; + + SSLTestFixture tf(serverParams, clientParams); + tf.doHandshake(); + auto result = tf.runIngressEgressValidation(); + checkValidationResults(result, false /*expectIngressPass*/, false /*expectEgressPass*/); + +#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS + constexpr const char* cause = "revocation server was offline"; +#else + constexpr const char* cause = "expired"; +#endif + ASSERT_NE(result.ingress.getStatus().reason().find(cause), std::string::npos); + ASSERT_NE(result.egress.getStatus().reason().find(cause), std::string::npos); +} + +// Tests basic CRL revocation works on ingress if the client is configured with a revoked key. +// Caveats: +// - Apple: CRL unsupported; test disabled +TEST(SSLManager, basicCRLRevocationTests) { + struct TestCase { + std::string serverCRLFile; + bool serverPass; + void serialize(BSONObjBuilder* bob) const { + bob->append("serverCRLFile", serverCRLFile); + bob->append("serverPass", serverPass); + } + }; + + SSLParams clientParams; + clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + clientParams.sslAllowInvalidHostnames = true; + clientParams.sslCAFile = trustedCaFile; + clientParams.sslPEMKeyFile = revokedClientKeyFile; + + SSLParams serverParams; + serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + serverParams.sslAllowInvalidHostnames = true; + serverParams.sslCAFile = caFile; + serverParams.sslPEMKeyFile = trustedServerKeyFile; + + { + serverParams.sslCRLFile = emptyCRL; + LOGV2(9476702, "Running test case", "CRLFile"_attr = emptyCRL, "pass"_attr = true); + SSLTestFixture tf(serverParams, clientParams); + tf.doHandshake(); + auto result = tf.runIngressEgressValidation(); + checkValidationResults(result, true, true /*expectEgressPass*/); + } + { + serverParams.sslCRLFile = clientRevokedCRL; + LOGV2(9476703, "Running test case", "CRLFile"_attr = clientRevokedCRL, "pass"_attr = false); + SSLTestFixture tf(serverParams, clientParams); + tf.doHandshake(); + auto result = tf.runIngressEgressValidation(); + checkValidationResults(result, false, true /*expectEgressPass*/); + ASSERT_NE(result.ingress.getStatus().reason().find("revoked"), std::string::npos); + } +} + +// Tests whether validation passes if an intermediate CA issuer cert is revoked, but +// the end-entity cert is not. +// Caveats: +// - Apple: CRL unsupported; test disabled +// - Windows: multiple CRLs (root CRL + intermediate CRL) is not allowed +// TODO: backport SERVER-95583 +TEST(SSLManager, revocationWithCRLsIntermediateTests) { + // intermediate-ca-B.pem + intermediate-ca-B-leaf.pem bundle + const std::string intermediateBLeafWithIssuerCertKeyFile = combinePEMFiles( + {{intermediateBLeafKeyFile, true /*includePrivKey*/}, {intermediateBCaFile}}); + // crl_from_intermediate_ca_B.pem + crl_intermediate_ca_B_revoked.pem + const std::string crlsFromRootAndIntermediateB = + combinePEMFiles({{intermediateBRevokedCRL}, {intermediateBCRL}}); + + SSLParams clientParams; + clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + clientParams.sslAllowInvalidHostnames = true; + clientParams.sslCAFile = caFile; + clientParams.sslPEMKeyFile = clientKeyFile; + clientParams.sslCRLFile = crlsFromRootAndIntermediateB; + + SSLParams serverParams; + serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL); + serverParams.sslAllowInvalidHostnames = true; + serverParams.sslCAFile = caFile; + serverParams.sslPEMKeyFile = intermediateBLeafWithIssuerCertKeyFile; + +#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS + ASSERT_THROWS_CODE_AND_WHAT( + SSLManagerInterface::create(clientParams, true), + DBException, + ErrorCodes::InvalidSSLConfiguration, + "CertAddCRLContextToStore Failed The object or property already exists."); +#else + SSLTestFixture tf(serverParams, clientParams); + tf.doHandshake(); + auto result = tf.runIngressEgressValidation(); + checkValidationResults(result, true, false); + ASSERT_NE(result.egress.getStatus().reason().find("revoked"), std::string::npos); +#endif +} + +#endif // MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE +#endif // MONGO_CONFIG_SSL } // namespace } // namespace mongo diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index b88ae85e5b4..7169e1eef1f 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -2062,10 +2062,19 @@ Future<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni)); } - LOGV2_DEBUG(23270, - 2, - "Accepted TLS connection from peer: {peerSubjectName}", - "peerSubjectName"_attr = peerSubjectName); + SecPkgContext_CipherInfo cipherInfo; + SECURITY_STATUS ssCipher = QueryContextAttributes(ssl, SECPKG_ATTR_CIPHER_INFO, &cipherInfo); + if (ssCipher != SEC_E_OK) { + return Status(ErrorCodes::SSLHandshakeFailed, + str::stream() + << "QueryContextAttributes for connection info failed with" << ssCipher); + } + const auto cipher = std::wstring(cipherInfo.szCipherSuite); + + LOGV2_INFO(6723802, + "Accepted TLS connection from peer", + "peerSubjectName"_attr = peerSubjectName, + "cipher"_attr = toUtf8String(cipher)); // If this is a server and client and server certificate are the same, log a warning. if (remoteHost.empty() && _sslConfiguration.serverSubjectName() == peerSubjectName) { diff --git a/src/mongo/util/processinfo_linux.cpp b/src/mongo/util/processinfo_linux.cpp index 3357687e6dd..7793011e901 100644 --- a/src/mongo/util/processinfo_linux.cpp +++ b/src/mongo/util/processinfo_linux.cpp @@ -427,7 +427,10 @@ public: /** * Get some details about the CPU */ - static void getCpuInfo(int& procCount, std::string& freq, std::string& features) { + static void getCpuInfo(int& procCount, + std::string& modelString, + std::string& freq, + std::string& features) { procCount = 0; @@ -439,6 +442,7 @@ public: {"features", [&](const std::string& value) { features = value; }}, #else {"processor", [&](const std::string& value) { procCount++; }}, + {"model name", [&](const std::string& value) { modelString = value; }}, {"cpu MHz", [&](const std::string& value) { freq = value; }}, {"flags", [&](const std::string& value) { features = value; }}, #endif @@ -664,6 +668,11 @@ void ProcessInfo::getExtraInfo(BSONObjBuilder& info) { appendNumber("voluntary_context_switches", ru.ru_nvcsw); appendNumber("involuntary_context_switches", ru.ru_nivcsw); + + LinuxProc p(_pid); + + // Append the number of thread in use + appendNumber("threads", p._nlwp); } /** @@ -710,13 +719,13 @@ unsigned long countNumaNodes() { void ProcessInfo::SystemInfo::collectSystemInfo() { utsname unameData; std::string distroName, distroVersion; - std::string cpuFreq, cpuFeatures; + std::string cpuString, cpuFreq, cpuFeatures; int cpuCount; int physicalCores; int cpuSockets; std::string verSig = LinuxSysHelper::readLineFromFile("/proc/version_signature"); - LinuxSysHelper::getCpuInfo(cpuCount, cpuFreq, cpuFeatures); + LinuxSysHelper::getCpuInfo(cpuCount, cpuString, cpuFreq, cpuFeatures); LinuxSysHelper::getNumPhysicalCores(physicalCores); cpuSockets = LinuxSysHelper::getNumCpuSockets(); LinuxSysHelper::getLinuxDistro(distroName, distroVersion); @@ -760,6 +769,7 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { bExtra.append("versionSignature", verSig); bExtra.append("kernelVersion", unameData.release); + bExtra.append("cpuString", cpuString); bExtra.append("cpuFrequencyMHz", cpuFreq); bExtra.append("cpuFeatures", cpuFeatures); bExtra.append("pageSize", static_cast<long long>(pageSize)); diff --git a/src/mongo/util/processinfo_test.cpp b/src/mongo/util/processinfo_test.cpp index 050835f97ac..ab35a6b7374 100644 --- a/src/mongo/util/processinfo_test.cpp +++ b/src/mongo/util/processinfo_test.cpp @@ -33,13 +33,30 @@ #include <iostream> #include <vector> +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/unittest/unittest.h" #include "mongo/util/processinfo.h" using boost::optional; -using mongo::ProcessInfo; -namespace mongo_test { +namespace mongo { + +namespace { +using StringMap = std::map<std::string, uint64_t>; + +StringMap toStringMap(BSONObj& obj) { + StringMap map; + + for (const auto& e : obj) { + map[e.fieldName()] = e.numberLong(); + } + + return map; +} + +#define ASSERT_KEY(_key) ASSERT_TRUE(stringMap.find(_key) != stringMap.end()); + TEST(ProcessInfo, SysInfoIsInitialized) { ProcessInfo processInfo; if (processInfo.supported()) { @@ -47,6 +64,20 @@ TEST(ProcessInfo, SysInfoIsInitialized) { } } +TEST(FTDCProcSysInfo, TestSysInfo) { + auto sysInfo = ProcessInfo(); + BSONObjBuilder builder; + sysInfo.appendSystemDetails(builder); + + BSONObj obj = builder.obj(); + auto stringMap = toStringMap(obj); + ASSERT_KEY("extra"); + + BSONObj extra = obj.getObjectField("extra"); + stringMap = toStringMap(extra); + ASSERT_KEY("cpuString"); +} + TEST(ProcessInfo, GetNumAvailableCores) { #if defined(__APPLE__) || defined(__linux__) || (defined(__sun) && defined(__SVR4)) || \ defined(_WIN32) @@ -59,4 +90,5 @@ TEST(ProcessInfo, GetNumAvailableCores) { TEST(ProcessInfo, GetNumCoresReturnsNonZeroNumberOfProcessors) { ASSERT_GREATER_THAN(ProcessInfo::getNumCores(), 0u); } -} // namespace mongo_test +} // namespace +} // namespace mongo diff --git a/src/mongo/util/processinfo_windows.cpp b/src/mongo/util/processinfo_windows.cpp index 51068027b51..5f3c0514949 100644 --- a/src/mongo/util/processinfo_windows.cpp +++ b/src/mongo/util/processinfo_windows.cpp @@ -39,6 +39,7 @@ #include "mongo/logv2/log.h" #include "mongo/util/processinfo.h" +#include "mongo/util/text.h" namespace mongo { @@ -248,6 +249,43 @@ bool getFileVersion(const char* filePath, DWORD& fileVersionMS, DWORD& fileVersi return true; } +std::string getCpuString() { + // get descriptive CPU string from registry + HKEY hKey; + LPCWSTR cpuKey = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + LPCWSTR valueName = L"ProcessorNameString"; + std::string cpuString; + + // Open the CPU key in the Windows Registry + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, cpuKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + ScopeGuard guard([hKey] { RegCloseKey(hKey); }); + WCHAR cpuModel[128]; + DWORD bufferSize = sizeof(cpuModel); + + // Retrieve the value of ProcessorNameString + if (RegQueryValueEx(hKey, + valueName, + nullptr, + nullptr, + reinterpret_cast<LPBYTE>(cpuModel), + &bufferSize) == ERROR_SUCCESS) { + cpuString = toUtf8String(cpuModel); + } else { + auto ec = lastSystemError(); + LOGV2_WARNING(7663101, + "Failed to retrieve CPU model name from the registry", + "error"_attr = errorMessage(ec)); + } + + // Close the registry key + } else { + auto ec = lastSystemError(); + LOGV2_WARNING( + 7663102, "Failed to open CPU key in the registry", "error"_attr = errorMessage(ec)); + } + return cpuString; +} + void ProcessInfo::SystemInfo::collectSystemInfo() { BSONObjBuilder bExtra; std::stringstream verstr; @@ -267,6 +305,12 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { pageSize = static_cast<unsigned long long>(ntsysinfo.dwPageSize); bExtra.append("pageSize", static_cast<long long>(pageSize)); + std::string cpuString = getCpuString(); + if (cpuString != nullptr) { + bExtra.append("cpuString", cpuString); + } + + // get memory info mse.dwLength = sizeof(mse); if (GlobalMemoryStatusEx(&mse)) { diff --git a/src/mongo/util/procparser_test.cpp b/src/mongo/util/procparser_test.cpp index 597c5dde272..259243264ef 100644 --- a/src/mongo/util/procparser_test.cpp +++ b/src/mongo/util/procparser_test.cpp @@ -41,6 +41,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/logv2/log.h" #include "mongo/unittest/unittest.h" +#include "mongo/util/processinfo.h" namespace mongo { diff --git a/src/mongo/util/str.h b/src/mongo/util/str.h index 7c364ab9c8d..bebdc4c1d0f 100644 --- a/src/mongo/util/str.h +++ b/src/mongo/util/str.h @@ -46,6 +46,7 @@ #include "mongo/bson/util/builder.h" #include "mongo/platform/bits.h" #include "mongo/util/ctype.h" +#include "mongo/util/str_basic.h" // IWYU pragma: export namespace mongo { namespace str { diff --git a/src/mongo/util/str_basic.h b/src/mongo/util/str_basic.h new file mode 100644 index 00000000000..1c6fb6d352c --- /dev/null +++ b/src/mongo/util/str_basic.h @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2024-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +/** + * This header would be part of str.h, but is separated to break an include cycle with + * bson/util/builder.h + */ + +#include <cstring> + +#include "mongo/base/string_data.h" +#include "mongo/util/assert_util.h" + +namespace mongo::str { +/** + * Throws if sd contains any bytes equal to '\0' within its range. + * + * Note: When a StringData is constructed from a C string or std::string, the final '\0' byte is NOT + * considered in range and so will not cause this to throw. + */ +inline void uassertNoEmbeddedNulBytes(StringData sd) { + uassert(9527900, "illegal embedded NUL byte", sd.find('\0') == std::string::npos); +} + +/** + * Copies the contents of sd to dest and appends a NUL byte. + * + * Throws if sd already contains a NUL byte. + * Returns a pointer to the next byte to write to (equivalently, the byte after the appended NUL). + */ +inline char* copyAsCString(char* dest, StringData sd) { + uassertNoEmbeddedNulBytes(sd); + dest += sd.copy(dest, sd.size()); + *dest++ = '\0'; + return dest; +} +} // namespace mongo::str diff --git a/src/mongo/util/str_test.cpp b/src/mongo/util/str_test.cpp index 22b7d81a0e2..74223b76540 100644 --- a/src/mongo/util/str_test.cpp +++ b/src/mongo/util/str_test.cpp @@ -325,4 +325,46 @@ TEST(StringUtilsTest, GetCodePointLength) { } } +TEST(StringUtilsTest, UassertNoEmbeddedNulBytes) { + // These shouldn't throw. + uassertNoEmbeddedNulBytes({nullptr, 0}); + uassertNoEmbeddedNulBytes(""_sd); + uassertNoEmbeddedNulBytes("hello"_sd); + uassertNoEmbeddedNulBytes("hello\0"_sd.substr(0, 5)); + + // These should throw. + ASSERT_THROWS_CODE(uassertNoEmbeddedNulBytes("\0"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(uassertNoEmbeddedNulBytes("\0hello"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(uassertNoEmbeddedNulBytes("hello\0"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(uassertNoEmbeddedNulBytes("hello\0world"_sd), DBException, 9527900); +} + +TEST(StringUtilsTest, CopyAsCString) { + char dest[100]; // big enough for anything we would reasonably add here. + + // Print address not contents on failures. + auto ptr = [](const char* p) { return static_cast<const void*>(p); }; + auto testValid = [&](StringData noNul, int line) { + // Make sure we write a nul byte. Without this, the test could pass if dest happened to have + // uninitialized zero bytes. + std::fill_n(dest, sizeof(dest), 0xff); + + ASSERT_EQ(ptr(copyAsCString(dest, noNul)), ptr(dest + noNul.size() + 1)) << "line:" << line; + ASSERT_EQ(dest[noNul.size()], '\0') << "line:" << line; + ASSERT_EQ(StringData(dest, noNul.size()), noNul) << "line:" << line; + }; + + // These shouldn't throw. + testValid({nullptr, 0}, __LINE__); + testValid(""_sd, __LINE__); + testValid("hello"_sd, __LINE__); + testValid("hello world"_sd.substr(0, 5), __LINE__); + + // These should throw. + ASSERT_THROWS_CODE(copyAsCString(dest, "\0"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(copyAsCString(dest, "\0hello"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(copyAsCString(dest, "hello\0"_sd), DBException, 9527900); + ASSERT_THROWS_CODE(copyAsCString(dest, "hello\0world"_sd), DBException, 9527900); +} + } // namespace mongo::str diff --git a/src/mongo/util/tick_source_bm.cpp b/src/mongo/util/tick_source_bm.cpp new file mode 100644 index 00000000000..ef6381b753d --- /dev/null +++ b/src/mongo/util/tick_source_bm.cpp @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/util/duration.h" +#include "mongo/util/system_tick_source.h" +#include "mongo/util/tick_source.h" +#include <benchmark/benchmark.h> + + +namespace mongo { + +static void BM_getTicks(benchmark::State& state) { + auto tickSource = SystemTickSource::get(); + for (auto _ : state) { + benchmark::DoNotOptimize(tickSource->getTicks()); + } + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK(BM_getTicks); + +} // namespace mongo diff --git a/src/mongo/util/uuid.h b/src/mongo/util/uuid.h index 078f658d07c..ce10692d1de 100644 --- a/src/mongo/util/uuid.h +++ b/src/mongo/util/uuid.h @@ -160,6 +160,11 @@ public: return _uuid >= rhs._uuid; } + template <typename H> + friend H AbslHashValue(H h, const UUID& uuid) { + return H::combine(std::move(h), uuid._uuid); + } + /** * Returns true only if the UUID is the RFC 4122 variant, v4 (random). */ |
