Skip to content

Fix BIO socket error handling - #3459

Open
justsmth wants to merge 3 commits into
aws:mainfrom
justsmth:fix/CWE-390-connect-bio-error
Open

justsmth wants to merge 3 commits into
aws:mainfrom
justsmth:fix/CWE-390-connect-bio-error

Conversation

@justsmth

Copy link
Copy Markdown
Contributor

Context and motivation

A non-blocking BIO_s_connect could report success after the peer had already refused the connection. BIO_do_connect classified the completed attempt using the thread-local socket error left by the original EINPROGRESS/WSAEWOULDBLOCK connect(), so a real SO_ERROR failure was treated as "try again." Reading SO_ERROR also clears it, so the next BIO_do_connect saw a clean socket and transitioned to BIO_CONN_S_OK. A caller polling BIO_do_connect would observe 0 once and then 1, on a socket that never connected and with an empty error queue.

Description of changes

Classify the blocked-connect path from the SO_ERROR value, not the leftover thread-local error. getsockopt failure is now a hard error (-1) rather than a fake errno 1.

Add a terminal BIO_CONN_S_ERROR state. Once the failure has been reported, further BIO_do_connect / read / write calls keep failing until BIO_reset. Repeat attempts only push BIO_R_NBIO_CONNECT_ERROR: the original SO_ERROR was consumed and errno no longer describes it.

Testing

BIOTest.SocketNonBlockingConnectFailure drives a non-blocking connect at a just-released loopback port, plants a stale retryable socket error after the attempt completes, and checks the first hard failure, a second BIO_do_connect (must not flip to success), and a subsequent write.

Off Windows the test also checks ECONNREFUSED as the SYS reason. Winsock codes do not survive ERR_GET_REASON's 12-bit pack, so that assert is POSIX-only.

Review considerations

No public API or ABI change. The new state is internal; bio_info_cb may now observe it. Not in the FIPS module boundary.

WaitForSocket now has a kConnect mode that also watches POLLERR/POLLHUP (and the Windows exception set) and uses a 30s timeout. Windows can take ~2s to refuse a loopback connect.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.

Use the SO_ERROR result for retry classification and error reporting instead of a stale thread-local socket error. Add a regression test covering the false-success sequence.
No functional change. Pass the -1 explicitly to bio_socket_should_retry and
document that it classifies the thread's last socket error rather than its
argument, so the preceding bio_socket_set_error call is clearly load-bearing.

Fold WaitForConnect into WaitForSocket as a kConnect wait type, and give that
wait a longer timeout: Windows takes ~2s to report a refused loopback
connection, which left little margin against the previous 5s.

Explain why the SYS reason code is only checked off Windows, and sort the
socket_helper.c include block.
Reading SO_ERROR to diagnose the failure also clears it, so the connect state
machine stayed in BIO_CONN_S_BLOCKED_CONNECT and the next BIO_do_connect saw a
clear socket error and transitioned to BIO_CONN_S_OK. A caller polling
BIO_do_connect would see 0 once and then 1, on a connection that never
completed and with an empty error queue.

Add a terminal BIO_CONN_S_ERROR state so the failure sticks until BIO_CTRL_RESET.
Repeat attempts, and reads and writes, now keep reporting failure. No system
error is pushed on the repeat path because errno no longer describes the
original failure.

Extend the regression test to cover the repeated call and a subsequent write.
@justsmth
justsmth requested a review from a team as a code owner August 31, 2026 17:37
@github-actions

Copy link
Copy Markdown
Contributor

🔒 Security ReviewView Report

Please review before merging.

@prasden
prasden self-requested a review August 31, 2026 17:57
@codecov-commenter

codecov-commenter commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.57143% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.08%. Comparing base (51cbecc) to head (27821f6).
⚠️ Report is 47 commits behind head on main.

Files with missing lines Patch % Lines
crypto/bio/connect.c 58.82% 7 Missing ⚠️
crypto/bio/bio_socket_test.cc 90.90% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3459      +/-   ##
==========================================
+ Coverage   78.06%   78.08%   +0.01%     
==========================================
  Files         700      700              
  Lines      124705   124743      +38     
  Branches    17326    17326              
==========================================
+ Hits        97352    97403      +51     
+ Misses      26484    26469      -15     
- Partials      869      871       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread crypto/bio/connect.c
Comment on lines +214 to +217
// |bio_socket_should_retry| classifies the thread's last socket
// error, not its argument, so publish |i| first.
bio_socket_set_error(i);
if (bio_socket_should_retry(-1)) {

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.

NP: seems a bit awkward to pass in -1 into bio_socket_should_retry just to get the errno right after setting bio_socket_set_error(i) above.

Another internal method like bio_errno_is_retryable to distinguish from failures like ECONNREFUSED could help.

Comment thread crypto/bio/connect.c
Comment on lines 191 to 195
OPENSSL_PUT_SYSTEM_ERROR();
OPENSSL_PUT_ERROR(BIO, BIO_R_CONNECT_ERROR);
ERR_add_error_data(4, "host=", c->param_hostname, ":",
c->param_port);
}

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.

Should we set c-> state = BIO_CONN_S_ERROR here as well?

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.

3 participants