From 7dcbe60fe62739662303348f7f0cb0de56960309 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:02:50 +0000 Subject: [PATCH 01/11] test(mbedtls-fake): add return-value setter for ssl_config_defaults Slice 1 of S26.02 (#420). Pure additive: new file-scope return value, Reset clears it, setter installs the value, mbedtls_ssl_config_defaults fake returns it instead of hard-coded 0. No production caller yet. --- Tests/Support/MbedTlsFake.c | 9 ++++++++- Tests/Support/MbedTlsFake.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/Support/MbedTlsFake.c b/Tests/Support/MbedTlsFake.c index d30455cb..149cfcde 100644 --- a/Tests/Support/MbedTlsFake.c +++ b/Tests/Support/MbedTlsFake.c @@ -20,6 +20,7 @@ static mbedtls_ssl_config* lastSslConfigDefaultsConfigArg; static int lastSslConfigDefaultsEndpoint; static int lastSslConfigDefaultsTransport; static int lastSslConfigDefaultsPreset; +static int sslConfigDefaultsReturn; /* mbedtls_ssl_init */ static int sslInitCallCount; @@ -118,6 +119,7 @@ void MbedTlsFake_Reset(void) lastSslConfigDefaultsEndpoint = 0; lastSslConfigDefaultsTransport = 0; lastSslConfigDefaultsPreset = 0; + sslConfigDefaultsReturn = 0; sslInitCallCount = 0; lastSslInitArg = NULL; sslSetupCallCount = 0; @@ -205,6 +207,11 @@ int MbedTlsFake_LastSslConfigDefaultsPreset(void) return lastSslConfigDefaultsPreset; } +void MbedTlsFake_SetSslConfigDefaultsReturn(int value) +{ + sslConfigDefaultsReturn = value; +} + int MbedTlsFake_SslInitCallCount(void) { return sslInitCallCount; @@ -475,7 +482,7 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config* conf, int endpoint, int tran lastSslConfigDefaultsEndpoint = endpoint; lastSslConfigDefaultsTransport = transport; lastSslConfigDefaultsPreset = preset; - return 0; + return sslConfigDefaultsReturn; } void mbedtls_ssl_init(mbedtls_ssl_context* ssl) diff --git a/Tests/Support/MbedTlsFake.h b/Tests/Support/MbedTlsFake.h index 84d8dc5e..fc4ac7da 100644 --- a/Tests/Support/MbedTlsFake.h +++ b/Tests/Support/MbedTlsFake.h @@ -28,6 +28,7 @@ EXTERN_C_BEGIN int MbedTlsFake_LastSslConfigDefaultsEndpoint(void); int MbedTlsFake_LastSslConfigDefaultsTransport(void); int MbedTlsFake_LastSslConfigDefaultsPreset(void); + void MbedTlsFake_SetSslConfigDefaultsReturn(int value); /* mbedtls_ssl_init */ int MbedTlsFake_SslInitCallCount(void); From abe8906a34055aa1db85515427f473ee1ce82a06 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:09:54 +0000 Subject: [PATCH 02/11] fix(mbedtls): emit DEFAULTS_NOT_APPLIED and unwind when ssl_config_defaults fails Slice 2 of S26.02 (#420). When MbedTlsStream_Open's first allocating step (mbedtls_ssl_config_defaults) fails, the inner transport stayed open and the next StreamSender reconnect tick re-entered the transport's Open, clobbering the prior fd. Two changes close the leak: - ApplySslConfigDefaults emits MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED via SolidSyslog_Error at severity ERROR so the integrator sees a protocol-level diagnostic rather than a silent failure. - Open's failure tail calls MbedTlsStream_Close, which unwinds both the SSL state (ssl_free + ssl_config_free) and the inner transport (a no-op if Transport.Open was never reached). Close is idempotent: eager mbedtls_ssl_init / mbedtls_ssl_config_init in Initialise guarantees the *_free calls are safe even when no allocating step succeeded, and Transport.Close is guarded inside the transport. Tests 2-5 in subsequent slices pin the same shape for the remaining failure points. --- .../SolidSyslogMbedTlsStreamErrors.h | 1 + .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 29 +++++++++++++++---- .../Source/SolidSyslogMbedTlsStreamMessages.c | 2 ++ .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 25 ++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index 513eb0a6..d320060c 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -11,6 +11,7 @@ EXTERN_C_BEGIN { MBEDTLSSTREAM_ERROR_POOL_EXHAUSTED, MBEDTLSSTREAM_ERROR_UNKNOWN_DESTROY, + MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, MBEDTLSSTREAM_ERROR_MAX }; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 78632c53..b973777e 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -4,9 +4,13 @@ #include #include #include +#include +#include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsStreamErrors.h" #include "SolidSyslogMbedTlsStreamPrivate.h" #include "SolidSyslogNullStream.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" @@ -97,17 +101,30 @@ static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const stru MbedTlsStream_InstallTransportCallbacks(self); ok = MbedTlsStream_PerformHandshake(self); } + if (!ok) + { + MbedTlsStream_Close(base); + } return ok; } static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self) { - return mbedtls_ssl_config_defaults( - &self->SslConfig, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT - ) == 0; + bool ok = mbedtls_ssl_config_defaults( + &self->SslConfig, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) == 0; + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &MbedTlsStreamErrorSource, + (uint8_t) MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED + ); + } + return ok; } /* TLS policy owned by the library — set per-ssl_config so it cannot leak diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c index ce3531c9..187ebfd5 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c @@ -10,6 +10,8 @@ static const char* MbedTlsStreamError_AsString(uint8_t code) "SolidSyslogMbedTlsStream_Create pool exhausted; returning fallback stream", [MBEDTLSSTREAM_ERROR_UNKNOWN_DESTROY] = "SolidSyslogMbedTlsStream_Destroy called with a handle not issued by this pool", + [MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED] = + "TLS client could not be configured with its default protocol settings; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) MBEDTLSSTREAM_ERROR_MAX) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index bbc17b99..997bd517 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -5,8 +5,11 @@ extern "C" #include #include +#include "ErrorHandlerFake.h" #include "MbedTlsFake.h" #include "SolidSyslogMbedTlsStream.h" +#include "SolidSyslogMbedTlsStreamErrors.h" +#include "SolidSyslogPrival.h" #include "AddressFake.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" @@ -37,6 +40,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) void setup() override { MbedTlsFake_Reset(); + ErrorHandlerFake_Install(nullptr); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -216,6 +220,27 @@ TEST(SolidSyslogMbedTlsStream, OpenFailsImmediatelyOnHardSslError) CALLED_FUNCTION(NoOpSleep, NEVER); } +/* ------------------------------------------------------------------------- + * Open failure unwind + error reporting (S26.02). Every failure path after + * the first allocating operation must close the inner transport, free both + * mbedTLS structs, and emit the matching typed error code so the integrator + * sees a protocol-level diagnostic. + * ------------------------------------------------------------------------- */ + +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenSslConfigDefaultsFails) +{ + MbedTlsFake_SetSslConfigDefaultsReturn(-1); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); + LONGS_EQUAL(1, MbedTlsFake_SslFreeCallCount()); + LONGS_EQUAL(1, MbedTlsFake_SslConfigFreeCallCount()); + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + POINTERS_EQUAL(&MbedTlsStreamErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, ErrorHandlerFake_LastCode()); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); +} + TEST(SolidSyslogMbedTlsStream, SendForwardsBufferToSslWrite) { const unsigned char payload[] = {0x10, 0x20, 0x30}; From 60f68169d1e4ed4913a21398ce92d97efa21bfb2 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:24:30 +0000 Subject: [PATCH 03/11] test(mbedtls-fake): add return-value setter for ssl_setup Slice 3 of S26.02 (#420). Same pure-additive shape as slice 1: new file-scope return value defaulting to 0 (success), Reset clears it, setter installs the value, mbedtls_ssl_setup fake returns it instead of hard-coded 0. No production caller yet. --- Tests/Support/MbedTlsFake.c | 9 ++++++++- Tests/Support/MbedTlsFake.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/Support/MbedTlsFake.c b/Tests/Support/MbedTlsFake.c index 149cfcde..d64bedd9 100644 --- a/Tests/Support/MbedTlsFake.c +++ b/Tests/Support/MbedTlsFake.c @@ -30,6 +30,7 @@ static mbedtls_ssl_context* lastSslInitArg; static int sslSetupCallCount; static mbedtls_ssl_context* lastSslSetupContextArg; static const mbedtls_ssl_config* lastSslSetupConfigArg; +static int sslSetupReturn; /* mbedtls_ssl_set_bio */ static int sslSetBioCallCount; @@ -125,6 +126,7 @@ void MbedTlsFake_Reset(void) sslSetupCallCount = 0; lastSslSetupContextArg = NULL; lastSslSetupConfigArg = NULL; + sslSetupReturn = 0; sslSetBioCallCount = 0; lastSslSetBioContextArg = NULL; lastSslSetBioPBioArg = NULL; @@ -237,6 +239,11 @@ const mbedtls_ssl_config* MbedTlsFake_LastSslSetupConfigArg(void) return lastSslSetupConfigArg; } +void MbedTlsFake_SetSslSetupReturn(int value) +{ + sslSetupReturn = value; +} + int MbedTlsFake_SslSetBioCallCount(void) { return sslSetBioCallCount; @@ -496,7 +503,7 @@ int mbedtls_ssl_setup(mbedtls_ssl_context* ssl, const mbedtls_ssl_config* conf) sslSetupCallCount++; lastSslSetupContextArg = ssl; lastSslSetupConfigArg = conf; - return 0; + return sslSetupReturn; } void mbedtls_ssl_set_bio( diff --git a/Tests/Support/MbedTlsFake.h b/Tests/Support/MbedTlsFake.h index fc4ac7da..6795dadf 100644 --- a/Tests/Support/MbedTlsFake.h +++ b/Tests/Support/MbedTlsFake.h @@ -38,6 +38,7 @@ EXTERN_C_BEGIN int MbedTlsFake_SslSetupCallCount(void); struct mbedtls_ssl_context* MbedTlsFake_LastSslSetupContextArg(void); const struct mbedtls_ssl_config* MbedTlsFake_LastSslSetupConfigArg(void); + void MbedTlsFake_SetSslSetupReturn(int value); /* mbedtls_ssl_set_bio */ int MbedTlsFake_SslSetBioCallCount(void); From de1ef21a2e65ad5f977e55077bf4e4b89cb7c517 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:32:56 +0000 Subject: [PATCH 04/11] fix(mbedtls): emit SESSION_INIT_FAILED when ssl_setup fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 4 of S26.02 (#420). BindContextToConfig now emits MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED via SolidSyslog_Error at severity ERROR when mbedtls_ssl_setup returns non-zero. The unwind itself (Close on Open failure) already covers this path from slice 2 — this slice only adds the protocol-level diagnostic. Refactor: with two failure-point tests sharing an identical 8-line assertion block, extracted CHECK_OPEN_UNWOUND_WITH_ERROR(transport, expectedCode) macro near the top of the test file. The macro pins the four-resource unwind (transport closed, both mbedTLS structs freed) plus the error tuple (source, code, severity). Tests 1 and 2 collapse to two lines each; future failure-point tests reuse the same shape. --- .../SolidSyslogMbedTlsStreamErrors.h | 1 + .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 11 ++++++- .../Source/SolidSyslogMbedTlsStreamMessages.c | 2 ++ .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 30 ++++++++++++++----- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index d320060c..644c10c2 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -12,6 +12,7 @@ EXTERN_C_BEGIN MBEDTLSSTREAM_ERROR_POOL_EXHAUSTED, MBEDTLSSTREAM_ERROR_UNKNOWN_DESTROY, MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, + MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED, MBEDTLSSTREAM_ERROR_MAX }; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index b973777e..606a8bbd 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -142,7 +142,16 @@ static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsStream* self) { - return mbedtls_ssl_setup(&self->SslContext, &self->SslConfig) == 0; + bool ok = mbedtls_ssl_setup(&self->SslContext, &self->SslConfig) == 0; + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &MbedTlsStreamErrorSource, + (uint8_t) MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED + ); + } + return ok; } static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbedTlsStream* self) diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c index 187ebfd5..37e4bb3e 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c @@ -12,6 +12,8 @@ static const char* MbedTlsStreamError_AsString(uint8_t code) "SolidSyslogMbedTlsStream_Destroy called with a handle not issued by this pool", [MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED] = "TLS client could not be configured with its default protocol settings; connection not established", + [MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED] = + "TLS session could not be initialised against the configured context; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) MBEDTLSSTREAM_ERROR_MAX) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 997bd517..15c77a96 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -20,6 +20,20 @@ extern "C" using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER/TWICE into scope for CALLED_FUNCTION / CALLED_FAKE +// 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)); \ + LONGS_EQUAL(1, MbedTlsFake_SslFreeCallCount()); \ + LONGS_EQUAL(1, MbedTlsFake_SslConfigFreeCallCount()); \ + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); \ + POINTERS_EQUAL(&MbedTlsStreamErrorSource, 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) + static int NoOpSleepCallCount; static int g_lastSleepMs; @@ -232,13 +246,15 @@ TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenSslConfigD MbedTlsFake_SetSslConfigDefaultsReturn(-1); CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); - LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); - LONGS_EQUAL(1, MbedTlsFake_SslFreeCallCount()); - LONGS_EQUAL(1, MbedTlsFake_SslConfigFreeCallCount()); - CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); - POINTERS_EQUAL(&MbedTlsStreamErrorSource, ErrorHandlerFake_LastSource()); - UNSIGNED_LONGS_EQUAL(MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, ErrorHandlerFake_LastCode()); - LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED); +} + +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenSslSetupFails) +{ + MbedTlsFake_SetSslSetupReturn(-1); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED); } TEST(SolidSyslogMbedTlsStream, SendForwardsBufferToSslWrite) From 5c8a0cac8d9a3cca268da43944e392ef9d8b8a9a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:35:07 +0000 Subject: [PATCH 05/11] test(mbedtls-fake): add return-value setter for ssl_set_hostname Slice 5 of S26.02 (#420). Final fake setter needed for the per-step unwind audit. Same pure-additive shape as slices 1 and 3: new file-scope return value defaulting to 0 (success), Reset clears it, setter installs the value, mbedtls_ssl_set_hostname fake returns it instead of hard-coded 0. No production caller yet. --- Tests/Support/MbedTlsFake.c | 9 ++++++++- Tests/Support/MbedTlsFake.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/Support/MbedTlsFake.c b/Tests/Support/MbedTlsFake.c index d64bedd9..5c1042a6 100644 --- a/Tests/Support/MbedTlsFake.c +++ b/Tests/Support/MbedTlsFake.c @@ -100,6 +100,7 @@ static void* lastSslConfRngContextArg; static int sslSetHostnameCallCount; static mbedtls_ssl_context* lastSslSetHostnameContextArg; static const char* lastSslSetHostnameNameArg; +static int sslSetHostnameReturn; /* mbedtls_ssl_conf_own_cert */ static int sslConfOwnCertCallCount; @@ -168,6 +169,7 @@ void MbedTlsFake_Reset(void) sslSetHostnameCallCount = 0; lastSslSetHostnameContextArg = NULL; lastSslSetHostnameNameArg = NULL; + sslSetHostnameReturn = 0; sslConfOwnCertCallCount = 0; lastSslConfOwnCertConfigArg = NULL; lastSslConfOwnCertCertArg = NULL; @@ -450,6 +452,11 @@ const char* MbedTlsFake_LastSslSetHostnameNameArg(void) return lastSslSetHostnameNameArg; } +void MbedTlsFake_SetSslSetHostnameReturn(int value) +{ + sslSetHostnameReturn = value; +} + int MbedTlsFake_SslConfOwnCertCallCount(void) { return sslConfOwnCertCallCount; @@ -624,5 +631,5 @@ int mbedtls_ssl_set_hostname(mbedtls_ssl_context* ssl, const char* hostname) sslSetHostnameCallCount++; lastSslSetHostnameContextArg = ssl; lastSslSetHostnameNameArg = hostname; - return 0; + return sslSetHostnameReturn; } diff --git a/Tests/Support/MbedTlsFake.h b/Tests/Support/MbedTlsFake.h index 6795dadf..c8aa0398 100644 --- a/Tests/Support/MbedTlsFake.h +++ b/Tests/Support/MbedTlsFake.h @@ -105,6 +105,7 @@ EXTERN_C_BEGIN int MbedTlsFake_SslSetHostnameCallCount(void); struct mbedtls_ssl_context* MbedTlsFake_LastSslSetHostnameContextArg(void); const char* MbedTlsFake_LastSslSetHostnameNameArg(void); + void MbedTlsFake_SetSslSetHostnameReturn(int value); /* mbedtls_ssl_conf_own_cert (mTLS client identity wiring) */ int MbedTlsFake_SslConfOwnCertCallCount(void); From e76863f96337b4946b87f32f50c1b0f58a2a5bfa Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:38:35 +0000 Subject: [PATCH 06/11] fix(mbedtls): emit SERVER_NAME_NOT_SET when ssl_set_hostname fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 6 of S26.02 (#420). ConfigureExpectedHostname now emits MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET via SolidSyslog_Error at severity ERROR when mbedtls_ssl_set_hostname returns non-zero. The emission stays inside the ServerName != NULL guard — a caller who never sets ServerName never reaches mbedtls_ssl_set_hostname, so no spurious error is reported. Unwind already in place from slice 2. Test 3 uses ReCreateHandleWithUpdatedConfig to set config.ServerName before the new Create. The helper now also re-creates the transport, re-installs ErrorHandlerFake, and resets MbedTlsFake — so tests using it (including CHECK_OPEN_UNWOUND_WITH_ERROR's == 1 assertions) observe counts from the post-ReCreate Open onwards only. The existing five ReCreate callers were unaffected: each only asserted counts produced by the new Open, so the extra reset is invisible to them. --- .../SolidSyslogMbedTlsStreamErrors.h | 1 + .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 8 +++++++ .../Source/SolidSyslogMbedTlsStreamMessages.c | 2 ++ .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 23 ++++++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index 644c10c2..84f0c1d7 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -13,6 +13,7 @@ EXTERN_C_BEGIN MBEDTLSSTREAM_ERROR_UNKNOWN_DESTROY, MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED, + MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET, MBEDTLSSTREAM_ERROR_MAX }; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 606a8bbd..97bcd209 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -160,6 +160,14 @@ static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbe if (self->Config.ServerName != NULL) { ok = mbedtls_ssl_set_hostname(&self->SslContext, self->Config.ServerName) == 0; + if (!ok) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &MbedTlsStreamErrorSource, + (uint8_t) MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET + ); + } } return ok; } diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c index 37e4bb3e..261b20e7 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c @@ -14,6 +14,8 @@ static const char* MbedTlsStreamError_AsString(uint8_t code) "TLS client could not be configured with its default protocol settings; connection not established", [MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED] = "TLS session could not be initialised against the configured context; connection not established", + [MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET] = + "TLS server name (SNI / cert match) could not be configured; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) MBEDTLSSTREAM_ERROR_MAX) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 15c77a96..5360c507 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -72,10 +72,19 @@ TEST_GROUP(SolidSyslogMbedTlsStream) } /* Tests needing config tweaks (CaChain, Rng, ServerName, …) call this - * to release setup()'s pool slot, mutate `config`, then re-Create. */ + * to release setup()'s pool slot, mutate `config`, then re-Create. + * Fully resets the fixture (transport, MbedTls fake 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 ReCreateHandleWithUpdatedConfig() { SolidSyslogMbedTlsStream_Destroy(handle); + StreamFake_Destroy(transport); + MbedTlsFake_Reset(); + ErrorHandlerFake_Install(nullptr); + transport = StreamFake_Create(); + config.Transport = transport; handle = SolidSyslogMbedTlsStream_Create(&config); } @@ -257,6 +266,18 @@ TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenSslSetupFa CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED); } +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenSetHostnameFails) +{ + /* ServerName must be set for ConfigureExpectedHostname to invoke + * mbedtls_ssl_set_hostname — otherwise the helper short-circuits to true. */ + config.ServerName = "syslog.example.com"; + ReCreateHandleWithUpdatedConfig(); + MbedTlsFake_SetSslSetHostnameReturn(-1); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET); +} + TEST(SolidSyslogMbedTlsStream, SendForwardsBufferToSslWrite) { const unsigned char payload[] = {0x10, 0x20, 0x30}; From 4b641a5739d1d7bcd1cef792b01f8f31fef7a7d1 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:42:36 +0000 Subject: [PATCH 07/11] fix(mbedtls): emit HANDSHAKE_REJECTED on hard handshake failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 7 of S26.02 (#420). Split PerformHandshake's combined "not-retryable OR budget-exhausted" exit branch into two distinct arms so each exit can emit its own protocol-level diagnostic. The hard-error arm (non-WANT mbedTLS return) emits MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED — covers the common cases (peer rejected, cert chain untrusted, protocol incompatible, server name mismatch) which all surface as a single fatal handshake rc to the client side. The budget-exhausted arm is left silent for now; slice 8 fills in HANDSHAKE_TIMEOUT. Test 4 (renamed from OpenFailsImmediatelyOnHardSslError to the OpenClosesTransportAndFreesSslState convention) keeps the existing once-only handshake and never-Sleep assertions and adds CHECK_OPEN_UNWOUND_WITH_ERROR to pin the unwind and emission. --- .../Interface/SolidSyslogMbedTlsStreamErrors.h | 1 + .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 15 +++++++++++++-- .../Source/SolidSyslogMbedTlsStreamMessages.c | 2 ++ Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index 84f0c1d7..614b3e31 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -14,6 +14,7 @@ EXTERN_C_BEGIN MBEDTLSSTREAM_ERROR_DEFAULTS_NOT_APPLIED, MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED, MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET, + MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED, MBEDTLSSTREAM_ERROR_MAX }; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 97bcd209..f8fe2d72 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -181,7 +181,9 @@ static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbe * Each call may return WANT_READ/WANT_WRITE while waiting for the multi-RTT * handshake to progress; we sleep briefly between attempts (avoiding a busy * spin) until either the handshake completes, hits a hard error, or the - * bounded budget expires. Same shape as OpenSSL's TlsStream_PerformHandshake. */ + * bounded budget expires. Each non-success exit emits a distinct + * protocol-level error code so the integrator can tell rejection from + * timeout. Same shape as OpenSSL's TlsStream_PerformHandshake. */ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self) { int totalSleptMs = 0; @@ -196,7 +198,16 @@ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStrea result = true; done = true; } - else if (!MbedTlsStream_IsRetryableHandshakeRc(rc) || MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) + else if (!MbedTlsStream_IsRetryableHandshakeRc(rc)) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &MbedTlsStreamErrorSource, + (uint8_t) MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED + ); + done = true; + } + else if (MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) { done = true; } diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c index 261b20e7..a19163f2 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c @@ -16,6 +16,8 @@ static const char* MbedTlsStreamError_AsString(uint8_t code) "TLS session could not be initialised against the configured context; connection not established", [MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET] = "TLS server name (SNI / cert match) could not be configured; connection not established", + [MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED] = + "TLS handshake rejected by peer or local verification; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) MBEDTLSSTREAM_ERROR_MAX) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 5360c507..4801614b 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -232,7 +232,7 @@ TEST(SolidSyslogMbedTlsStream, OpenFailsWhenHandshakeNeverCompletes) CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); } -TEST(SolidSyslogMbedTlsStream, OpenFailsImmediatelyOnHardSslError) +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeFailsHard) { /* Non-WANT error (e.g. a verify/connection failure) is fail-fast — no * retry budget burn, no Sleep. */ @@ -241,6 +241,7 @@ TEST(SolidSyslogMbedTlsStream, OpenFailsImmediatelyOnHardSslError) CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); CALLED_FAKE(MbedTlsFake_SslHandshake, ONCE); CALLED_FUNCTION(NoOpSleep, NEVER); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED); } /* ------------------------------------------------------------------------- From 0704c9a76198b3e4158213035cf2ab4eeb074213 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 10:58:43 +0000 Subject: [PATCH 08/11] fix(mbedtls): emit HANDSHAKE_TIMEOUT when handshake budget exhausts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 8 of S26.02 (#420). The budget-exhausted exit branch from slice 7's PerformHandshake refactor now emits MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT — distinct from HANDSHAKE_REJECTED so the integrator can tell a wedged peer (no progress within 5 s) from a peer that actively rejected the handshake. Same severity (ERROR) and same unwind path (slice 2's tail Close). Test 5 (renamed from OpenFailsWhenHandshakeNeverCompletes to the OpenClosesTransportAndFreesSslState convention) gets the CHECK_OPEN_UNWOUND_WITH_ERROR assertion. Completes the audit of all five Open-failure points (DEFAULTS_NOT_APPLIED, SESSION_INIT_FAILED, SERVER_NAME_NOT_SET, HANDSHAKE_REJECTED, HANDSHAKE_TIMEOUT). --- Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h | 1 + Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c | 5 +++++ Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c | 2 ++ Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index 614b3e31..8d64091e 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -15,6 +15,7 @@ EXTERN_C_BEGIN MBEDTLSSTREAM_ERROR_SESSION_INIT_FAILED, MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET, MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED, + MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT, MBEDTLSSTREAM_ERROR_MAX }; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index f8fe2d72..34b80b64 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -209,6 +209,11 @@ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStrea } else if (MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &MbedTlsStreamErrorSource, + (uint8_t) MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT + ); done = true; } else diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c index a19163f2..eb8abb14 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c @@ -18,6 +18,8 @@ static const char* MbedTlsStreamError_AsString(uint8_t code) "TLS server name (SNI / cert match) could not be configured; connection not established", [MBEDTLSSTREAM_ERROR_HANDSHAKE_REJECTED] = "TLS handshake rejected by peer or local verification; connection not established", + [MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT] = + "TLS handshake did not complete within the bounded retry budget; connection not established", }; const char* result = "unknown"; if (code < (uint8_t) MBEDTLSSTREAM_ERROR_MAX) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 4801614b..4999f73c 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -223,13 +223,14 @@ TEST(SolidSyslogMbedTlsStream, OpenRetriesHandshakeOnWantWrite) CALLED_FAKE(MbedTlsFake_SslHandshake, TWICE); } -TEST(SolidSyslogMbedTlsStream, OpenFailsWhenHandshakeNeverCompletes) +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeBudgetExhausts) { /* mbedtls_ssl_handshake always returns WANT_READ — handshake never makes * progress, so the bounded budget should expire and Open returns false. */ ArrangePersistentHandshakeError(MBEDTLS_ERR_SSL_WANT_READ); CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT); } TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeFailsHard) From d8fdbbe8827621d87cc29588bed9b7777970946e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 11:08:01 +0000 Subject: [PATCH 09/11] test(mbedtls): pin Open-Close-Open recovery after failed Open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 9 of S26.02 (#420). Pins the recovery contract that the per- failure-point unwinds (slices 2, 4, 6, 7, 8) enable: after a failed Open, the next Open is a clean Open-Close-Open cycle on the inner transport. Without the unwind tail added in slice 2, the inner transport would stay open and the next StreamSender reconnect tick would re-enter PosixTcpStream_Open, clobbering the prior fd. Arrangement uses MbedTlsFake_SetSslHandshakeReturnSequence to make the first handshake fail (hard error, fail-fast) and the second succeed. Asserts Transport.OpenCount == 2, Transport.CloseCount == 1, second Open returns true. Passed on arrival — no production change. Completes the 6-test contract called out in the story acceptance criteria. --- Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 4999f73c..68246d53 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -233,6 +233,23 @@ TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeB CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT); } +TEST(SolidSyslogMbedTlsStream, SecondOpenAfterFailedFirstOpenSucceeds) +{ + /* The recovery contract that the per-failure-point unwinds enable: once + * Open's failure tail Closes the transport and frees the SSL state, the + * next Open is a clean Open-Close-Open cycle on the transport — Connected + * goes false, StreamSender's next reconnect tick re-enters, and the + * second handshake completes. Without the unwind, the inner transport + * would stay open and PosixTcpStream_Open would clobber its fd. */ + int handshakeSequence[] = {MBEDTLS_ERR_SSL_BAD_INPUT_DATA, 0}; + MbedTlsFake_SetSslHandshakeReturnSequence(handshakeSequence, 2); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); + LONGS_EQUAL(2, StreamFake_OpenCallCount(transport)); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); +} + TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeFailsHard) { /* Non-WANT error (e.g. a verify/connection failure) is fail-fast — no From 77e1337e450118b7ec33941419121e565c3e3cb0 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 11:44:00 +0000 Subject: [PATCH 10/11] chore(misra): shift MbedTlsStream suppression line numbers for S26.02 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 10 of S26.02 (#420). MbedTlsStream.c grew by 50 lines in slices 2-8 (new emissions in ApplySslConfigDefaults, BindContextToConfig, ConfigureExpectedHostname, and two PerformHandshake exit branches; unwind tail in Open). All six existing suppressions for the file moved to their new line numbers: 60 -> 64 (11.3, SelfFromBase cast) 190 -> 240 (11.5, BioSend ctx cast) 202 -> 252 (11.5, BioRecv ctx cast) 227 -> 277 (11.5, Send buffer cast) 246 -> 296 (11.5, Read buffer cast) 14 -> 18 (5.7, anonymous-enum opener) No new suppressions; no new MISRA violations introduced by the unwind or the emission helpers. Verified with cppcheck-misra against the full CI include set — clean exit, matches main baseline. --- misra_suppressions.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 9db2e8df..fdd0bb9d 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -44,7 +44,7 @@ misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c:47 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:60 +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/Posix/Source/SolidSyslogPosixAddress.c:10 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:20 @@ -67,8 +67,8 @@ misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsMutex.c:34 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:94 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:160 misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:205 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:190 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:202 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:240 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:252 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:138 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:322 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:342 @@ -118,7 +118,7 @@ misra-c2012-5.7:Core/Source/SolidSyslogStreamSender.c:19 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:14 +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/Posix/Source/SolidSyslogGetAddrInfoResolver.c:20 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixDatagram.c:21 @@ -169,5 +169,5 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:20 # D.013 — Rule 11.5: void* ↔ unsigned char* at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:227 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:246 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:277 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:296 From be140f28d996184fb3ec9d6f9fbec7d345eefed5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 24 May 2026 11:57:37 +0000 Subject: [PATCH 11/11] docs: update DEVLOG for S26.02 mbedTLS Open unwind --- DEVLOG.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 47087376..422552e3 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -11743,3 +11743,71 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None. + +## 2026-05-24 — S26.02 MbedTlsStream Open unwinds + per-failure-point error reporting + +### Decisions + +- **Single-line tail unwind, not per-helper unwind.** Open's failure + tail is `if (!ok) MbedTlsStream_Close(base);` — one statement, one + exit point. `Close` is already idempotent (eager `mbedtls_ssl_init` + / `_config_init` in `Initialise` means the `*_free` calls are safe + whether or not Open reached its allocating step) so it does the + right thing regardless of which step tripped. Considered extracting + a `MbedTlsStream_UnwindOnFailure` helper; rejected — one statement + is one statement. +- **Five protocol-level error codes, not five C-call codes.** New + `MbedTlsStreamErrors.h` entries (`DEFAULTS_NOT_APPLIED`, + `SESSION_INIT_FAILED`, `SERVER_NAME_NOT_SET`, `HANDSHAKE_REJECTED`, + `HANDSHAKE_TIMEOUT`) describe what the negotiation failed to + achieve, not which mbedTLS function returned non-zero. David's + guidance: the integrator should see "TLS handshake rejected by + peer or local verification" — not "mbedtls_ssl_handshake returned + MBEDTLS_ERR_SSL_BAD_INPUT_DATA". Sets the pattern for S26.03 + (OpenSSL parity). +- **PerformHandshake `||` split into two `else if` branches.** Was + one combined exit condition for both hard-error and budget- + exhausted exits; now two branches so each can emit its own typed + code. clang-tidy's `bugprone-branch-clone` does not fire — the + two emissions take different enum constants. +- **Inner-transport Open failure is not our error to report.** + When `SolidSyslogStream_Open(transport, addr)` returns false (the + first `&&` short-circuit in MbedTlsStream_Open), MbedTlsStream + emits nothing and just returns false. The transport owns its own + diagnostics; the unwind tail still runs and closes the not-quite- + opened transport (idempotent on `INVALID_FD`). +- **`ReCreateHandleWithUpdatedConfig` extended to fully reset the + fixture.** Was destroy-handle-only; now also destroys+recreates + the transport, resets MbedTlsFake counters, and re-installs the + ErrorHandlerFake. Required so the new + `CHECK_OPEN_UNWOUND_WITH_ERROR(transport, code)` macro's `== 1` + count assertions see counts from the post-ReCreate Open only. The + existing five callers (CaChain / Rng / hostname / OwnCert tests) + were unaffected — they only ever asserted counts produced by the + one Open they invoke. +- **No defensive close-before-open in PosixTcpStream.** Considered + hardening `PosixTcpStream_Open` to close any pre-existing fd + before allocating a new one (would mask the bug class entirely). + Rejected per David: the contract today is "every `Stream_Open` is + preceded by `Stream_Close`", StreamSender honours it, and the + unwind added by this story restores the contract for the mbedTLS + failure path. Defensive close at the transport would paper over + future contract violations rather than surface them. + +### Deferred + +- **OpenSSL parity (S26.03).** Same shape: `TlsStream_Open` has the + same chained-`&&` failure paths, the same SSL state to free, the + same socket fd to close. Story to be filed after this one merges, + using the test pattern (`CHECK_OPEN_UNWOUND_WITH_ERROR` macro, + protocol-level error codes, recovery test) proven here. +- **`mbedtls_ssl_close_notify` on a never-handshaked context.** + Today's `Close` calls close_notify unconditionally; on a context + where `ssl_setup` was never reached, close_notify returns + `MBEDTLS_ERR_SSL_BAD_INPUT_DATA` which we silently swallow. + Harmless but noisy in mbedTLS debug builds. Could add a "session + ever live?" guard; out of scope for S26.02. + +### Open questions + +- None.