Skip to content

fix: S26.02 MbedTlsStream Open unwinds inner transport and SSL state on failure#440

Merged
DavidCozens merged 11 commits into
mainfrom
fix/s26-02-mbedtls-open-unwind
May 24, 2026
Merged

fix: S26.02 MbedTlsStream Open unwinds inner transport and SSL state on failure#440
DavidCozens merged 11 commits into
mainfrom
fix/s26-02-mbedtls-open-unwind

Conversation

@DavidCozens

@DavidCozens DavidCozens commented May 24, 2026

Copy link
Copy Markdown
Owner

Closes #420.

Summary

  • MbedTlsStream_Open now unwinds on every failure path after the first allocating step: a single tail if (!ok) MbedTlsStream_Close(base); closes the inner transport and frees both mbedTLS structs (ssl_free + ssl_config_free). Close is idempotent — the eager mbedtls_ssl_init / _config_init in Initialise guarantees the frees are safe whether or not Open reached its allocating step.
  • Five new typed error codes in SolidSyslogMbedTlsStreamErrors.h (DEFAULTS_NOT_APPLIED, SESSION_INIT_FAILED, SERVER_NAME_NOT_SET, HANDSHAKE_REJECTED, HANDSHAKE_TIMEOUT) emitted via SolidSyslog_Error at severity ERROR. Messages are protocol-level ("TLS handshake rejected by peer or local verification; connection not established") not C-call-level. Pattern to be reused by S26.03 (OpenSSL parity, deferred until this story merges).
  • PerformHandshake's combined "not-retryable OR budget-exhausted" exit branch split into two else if arms so each emits its own typed code.
  • 6 new / extended tests pin the contract (5 per-failure-point + 1 recovery), built around a new CHECK_OPEN_UNWOUND_WITH_ERROR(transport, code) macro extracted at slice 4 once duplication became obvious. Existing tests OpenFailsImmediatelyOnHardSslError and OpenFailsWhenHandshakeNeverCompletes were renamed to the OpenClosesTransportAndFreesSslStateWhen… convention while keeping their original retry/sleep assertions.
  • 6 cppcheck-misra suppressions for MbedTlsStream.c moved to their new line numbers (no new suppressions; same baseline as main, verified locally with the CI include set).

Story scope

Audit of the path from StreamSender.Send through MbedTlsStream down to the socket found three resources that leaked on Open failure:

  1. Transport fd — the next StreamSender reconnect tick re-entered PosixTcpStream_Open, which clobbered self->Fd with a fresh socket() without closing the prior fd.
  2. SslContext inner allocations from mbedtls_ssl_setup — only freed by mbedtls_ssl_free inside Close.
  3. SslConfig inner allocations from mbedtls_ssl_config_defaults — only freed by mbedtls_ssl_config_free inside Close.

All three close with the tail Close call on the failure path.

Test plan

  • All 3 mbedTLS test suites green under debug (42 unit + 10 pool + 7 live integration, 10.8s)
  • All 3 mbedTLS test suites green under sanitize (ASan + UBSan), no leaks/UB
  • Coverage 96.1% line / 96.9% function (gate is 90%)
  • cppcheck-misra clean against full CI include set (matches main baseline; no new suppressions, only line shifts)
  • clang-format dry-run clean on all touched files
  • fd-leak smoke check: 50 iterations of HandshakeFailsWhenServerCertSignedByUntrustedCa under ulimit -n 32 — all pass, no EMFILE
  • IWYU (run separately by maintainer in cpputest-clang container) — clean
  • Full CI suite

Out of scope

  • OpenSSL parity (deferred to S26.03)
  • Defensive close-before-open in PosixTcpStream (would mask future contract violations; the contract today is "every Stream_Open is preceded by Stream_Close", honoured by StreamSender's disconnect-before-reconnect)
  • WinsockTcpStream / FreeRtosTcpStream hardening (same shape, not reachable once mbedTLS unwinds correctly)
  • StreamSender reconnect flow changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced error reporting for TLS stream failures with five new diagnostic error codes covering configuration, initialization, hostname setup, handshake rejection, and timeout scenarios.
  • Bug Fixes

    • Improved cleanup behavior when TLS stream setup fails, ensuring proper resource unwinding before error reporting.
  • Tests

    • Added comprehensive tests validating error code emission and resource cleanup across all TLS stream failure scenarios.

Review Change Stack

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.
…faults 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.
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.
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.
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.
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.
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.
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).
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.
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.
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

MbedTlsStream_Open now explicitly unwinds resources (transport, SSL config, SSL context) on every failure path after allocation, emits five new protocol-level error codes via error handlers at each failure point, and is verified by test cases that inject failures at each stage and assert full unwinding and error emission.

Changes

S26.02: MbedTlsStream Open() failure unwinding and error reporting

Layer / File(s) Summary
Error contract: five new failure codes and messages
Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h, Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c
Five new enum constants (DEFAULTS_NOT_APPLIED, SESSION_INIT_FAILED, SERVER_NAME_NOT_SET, HANDSHAKE_REJECTED, HANDSHAKE_TIMEOUT) are added to report protocol-level TLS failures, and corresponding strings are mapped in the error-to-message lookup.
MbedTlsStream_Open failure paths with unwinding and error reporting
Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c
Open now calls Close on the failure tail after any allocating operation; four configuration helpers emit SolidSyslog_Error with specific codes when TLS setup, hostname, or config operations fail; the handshake loop distinguishes hard errors from timeout exhaustion and emits the matching typed codes.
Test infrastructure: failure injection setters and fixture reset
Tests/Support/MbedTlsFake.h, Tests/Support/MbedTlsFake.c, Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp
MbedTlsFake adds state variables and setter functions to control the return codes of mbedtls_ssl_config_defaults, mbedtls_ssl_setup, and mbedtls_ssl_set_hostname; test fixture setup/helper now install and reset ErrorHandlerFake per test to scope error assertions.
Test cases: unwinding and error-reporting verification
Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp
New CHECK_OPEN_UNWOUND_WITH_ERROR macro bundles assertions on transport close, SSL frees, and error handler invocation; tests verify Open returns false and unwinding/error-emission occur at each failure point (config_defaults, ssl_setup, hostname, handshake hard error, handshake timeout); a recovery test asserts second Open succeeds cleanly after first one failed.
Documentation and MISRA suppression updates
DEVLOG.md, misra_suppressions.txt
S26.02 section documents unwind strategy, error-code design, and test-fixture changes; MISRA suppression line numbers are updated for SolidSyslogMbedTlsStream.c modifications.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 When TLS connections fail to bloom,
Close the doors, sweep out the room—
Five new codes now tell the tale,
Of timeouts, rejects, handshakes that pale.
With unwinding paths and error cheer,
Resources leak no longer here! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: MbedTlsStream_Open now unwinds inner transport and SSL state on failure, which is the core objective of this PR.
Description check ✅ Passed The PR description comprehensively covers Purpose (issue #420), Change Description (unwind tail, error codes, PerformHandshake refactoring, test macro), Test Evidence (multiple test suites, coverage, sanitizers, leak checks), and Areas Affected (MbedTlsStream, error codes, tests).
Linked Issues check ✅ Passed All five failure points from issue #420 are addressed: ssl_config_defaults, ssl_setup, ssl_set_hostname, handshake hard error, and handshake budget exhaustion. Five new error codes added, error emissions implemented, six tests cover per-failure-point and recovery scenarios, and MbedTlsFake extended with return-value setters.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #420 scope: unwind implementation, typed error codes, test macro and test cases, MbedTlsFake extensions, and MISRA suppression line adjustments. No unrelated refactoring or scope creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/s26-02-mbedtls-open-unwind

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1300 passed, 🙈 2 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1528 passed, 🙈 2 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1252 passed, 🙈 2 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1252 passed, 🙈 2 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-linux-mbedtls: 100% successful (✔️ 7 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 86% successful (✔️ 42 passed, 🙈 7 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 1136 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1252 passed, 🙈 2 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: 1 warning (low: 1)


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@misra_suppressions.txt`:
- Line 47: The suppression list contains a stale entry for misra-c2012-5.7 at
line 18 that no longer corresponds to a cppcheck-reported violation; open
misra_suppressions.txt and remove or update the misra-c2012-5.7 entry (line 18)
so it no longer suppresses a non-existent issue, and while you’re there verify
that the misra-c2012-11.3 entry referencing
Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c and the misra-c2012-11.5
entries (lines 240, 252, 277, 296) accurately match the current cppcheck
findings — adjust file paths or line numbers to the actual locations reported by
cppcheck or add a brief comment explaining why each suppression is still
required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ae79ecf-3339-4102-8550-df5b30245004

📥 Commits

Reviewing files that changed from the base of the PR and between e254400 and be140f2.

📒 Files selected for processing (8)
  • DEVLOG.md
  • Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h
  • Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c
  • Platform/MbedTls/Source/SolidSyslogMbedTlsStreamMessages.c
  • Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp
  • Tests/Support/MbedTlsFake.c
  • Tests/Support/MbedTlsFake.h
  • misra_suppressions.txt

Comment thread misra_suppressions.txt
@DavidCozens DavidCozens merged commit fb56b54 into main May 24, 2026
21 checks passed
@DavidCozens DavidCozens deleted the fix/s26-02-mbedtls-open-unwind branch May 24, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

S26.02: MbedTlsStream Open() unwinds inner transport and SSL state on failure

1 participant