Skip to content

Fix 3 SSL_poll bugs - #5

Open
mattcaswell wants to merge 131 commits into
feature/dtls-1.3from
fix-dtls-ssl-poll-bugs
Open

Fix 3 SSL_poll bugs#5
mattcaswell wants to merge 131 commits into
feature/dtls-1.3from
fix-dtls-ssl-poll-bugs

Conversation

@mattcaswell

Copy link
Copy Markdown
Owner
Checklist
  • documentation is added or updated
  • tests are added or updated

fwh-dc added 30 commits July 7, 2026 12:25
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from openssl#22259)
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from openssl#22259)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22260)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22260)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22260)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22261)
…n 5.9

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22416)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22416)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22273)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22273)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22273)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22366)

Updated the logic in ssl_cipher_list_to_bytes to take account of the changes
from PR#24161

Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#24226)
DTLS 1.3 session id must not be sent by client unless
it has a cached id. And DTLS 1.3 servers must not echo
a session id from a client.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22366)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22366)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22366)
SSL_CONNECTION_IS_VERSION13 macro is used where appropriate.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22364)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#23375)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22362)
…ches ssl3_read_bytes and ssl3_write_bytes

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22360)
…ync with their tls counterparts.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22360)
…ite() for tls1.3

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22360)
…tes()

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22360)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22380)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22401)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22936)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from openssl#22936)
…er()

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22376)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#22378)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#24293)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from openssl#24293)
rsith71 and others added 9 commits July 9, 2026 14:49
Fixing up two tests that are not putting skip's in a SKIP
block. These were identified by a FreeBSD image.

Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
MergeDate: Thu May  7 12:27:29 2026
(Merged from openssl#31089)
Added an ossl-guid-dtlsv13 file to describe OpenSSL's DTLSv1.3
implementation.

Fixes: openssl/project#1831
Fixes: openssl/project#1830

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu May 21 08:50:49 2026
(Merged from openssl#31032)
Added a DTLS 1.3 demo based off of the SSL echo demo. The client
takes input from the user and sends it to the server. The server
echos that input back.

There was a bug if the server retransmitted New Session tickets
cause it didn't receive the ACK for the first set of New Session
tickets. The changes outside of the demo application are to fix
that bug.

Fixes: openssl/project#1943

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Wed May 27 08:02:57 2026
(Merged from openssl#31042)
DTLSv1_listen will not support DTLSv1.3. Instead its max version is
DTLSv1.2

Adding DTLS SSL Listener that utilizes a similar DEMUX approach as
QUIC and passes traffic through the URXE structure.

This will establish full connection and an application can ask for
a ready SSL connection. The DTLS SSL listener also supports HVR
and HRR cookie validation.

Fixes: openssl/project#1946

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Mon Jul  6 09:29:09 2026
(Merged from openssl#31137)
Several DTLS 1.3 test cases driven through TLSProxy currently fail or
hang. To keep the tree buildable and testable while the DTLS 1.3 work
is under review, skip the affected sub-tests (or whole DTLS runs) with
TODO(DTLS1.3) markers. These are to be addressed in a follow-up.

Skipped for DTLS:
- 70-test_tls13psk.t: "Remove sig algs", "Too many PSKs" and
  "Bad PSK with no handshake fallback"
- 70-test_tls13cookie.t: the DTLS run
- 70-test_sslrecords.t: "Empty app data in DTLS"
- 70-test_tls13downgrade.t: the DTLS run
- 70-test_tls13messages.t: the DTLS run

Also skipped in 70-test_tls13messages.t: "HelloRequest rejected in
TLSv1.3" and "HelloRequest ignored in (D)TLSv1.2". These fail for TLS
as well as DTLS, so the failure is not proxy-specific and needs
separate investigation.

Assisted-by: Claude Code:claude-opus-4-8

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Mon Jul 13 14:24:27 2026
(Merged from openssl#31896)
Listener-created DTLS connections could not complete a handshake: the
peer address was applied to the initial plaintext record layers by
SSL_set1_initial_peer_addr(), but the write record layer recreated when
encryption is enabled started with no peer, so tls_retry_write_records()
fell back from BIO_sendmmsg() to BIO_write() on the shared listener BIO
and the bytes went nowhere.

This adaptation was lost when the branch was rebased: the change was
originally written when the new_record_layer record method callback took
a peer argument, whereas current record layers set the peer via the
separate set1_peer callback. Re-apply the peer to every DTLS record
layer as it is created in ssl_set_new_record_layer(), mirroring the
existing use_urxe handling.

Fixes test_dtlsssllistener and test_dtls_multithread.

Assisted-by: Claude Code:claude-opus-4-8

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Mon Jul 13 14:24:18 2026
(Merged from openssl#31896)
Need to fix a deprecated API in dtlsecho. Also updated the sslecho
usage information since it is no longer correct.

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 16 07:29:15 2026
(Merged from openssl#31935)
DTLSv1.3 suppport was added into SSL_stateless, but it
doesn't work properly. The reason why it doesn't work
properly is that it doesn't handle concurrent DTLS
sessions being established at the same time.
If you want something that allows DTLS 1.3 connections
being establish at the same time use DTLS SSL Listener.

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Wed Jul 22 15:55:14 2026
(Merged from openssl#31364)
In DTLS 1.3 the handshake sequence number is not reset to 0 after the
connection is establish. This means New Session tickets will not
send out with a handshake seq of 0. Instead it will pick up where it
left off.

Fixes: openssl/project#2008
Assisted-by: Claude:claude-opus-4-7

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Mon Jul 27 12:20:10 2026
(Merged from openssl#31978)
@mattcaswell
mattcaswell force-pushed the fix-dtls-ssl-poll-bugs branch 9 times, most recently from a333d53 to 449b499 Compare August 7, 2026 15:33
rsith71 and others added 5 commits August 11, 2026 16:41
Now when a buffer BIO is created for the initial DTLS handshake
from an SSL listener messages are now buffered until a flush
is called.

Fixes: openssl/project#1982
Assisted-by: Claude:claude-opus-4-7

Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
MergeDate: Tue Aug 11 14:41:07 2026
(Merged from openssl#31979)
To prevent resource exhaustion we have to cap the number
of pending connections. The default is 256 pending connections
but there are APIs to configure the max number of pending
connections.

Fixes: openssl/project#1983
Fixes: openssl/project#2035
Assisted-by: Claude:claude-opus-4-7

Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
MergeDate: Tue Aug 11 14:50:11 2026
(Merged from openssl#31980)
Updating the CHANGES.md for all the changes added via the DTLS1.3
feature branch.

Fixes: openssl/project#2039
Assisted-by: Claude:claude-opus-4-8

Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
MergeDate: Tue Aug 11 14:52:30 2026
(Merged from openssl#32182)
The listener's notifier was signalled when the demux routed a datagram into a
connection's receive queue, but not when dtls_listener_drive_pending() moved
a completed connection onto the accept queue - even though the latter is
precisely the readiness event a thread waiting in SSL_poll() for
SSL_POLL_EVENT_IC is waiting for.

Usually the injection signal covers for this. Every connection reaching the
accept queue got there because a datagram was demuxed into its receive queue,
so the waiter is woken by that, ticks the listener during its own readout,
and finds the connection. The exception is the window in which the injection
and the queue push straddle a waiter registering, since signalling only
happens if there is a waiter at the time:

  1. Accept thread A polls the listener for SSL_POLL_EVENT_IC. Its readout
     ticks the listener, finds nothing, and it decides to block. It is not a
     registered waiter yet.
  2. Worker thread B polls one of its own connections, which also ticks the
     listener. The pump reads a client's final ClientHello and injects it into
     that pending connection's queue. There are no waiters, so nothing is
     signalled.
  3. A enters the blocking section. Its re-check runs without ticking, so it
     sees only the accept queue, still empty, and blocks.
  4. B's tick reaches dtls_listener_drive_pending(), which completes the
     connection against the buffered ClientHello and pushes it onto the accept
     queue.

With no signal at step 4, A sleeps on while a validated connection sits ready
to be accepted, until an unrelated datagram makes the socket readable again.
B consumed the only one in flight and the client is now waiting on the server,
so on a quiet listener that means waiting for the client to retransmit its
handshake.

Factor the signalling out of dtls_listener_packet_handler() into a helper and
call it from both places. Both call sites already hold the listener mutex,
which the helper requires.

Assisted-by: Claude Code:claude-opus-5
A DTLS listener added only the network socket to the poll set. Three things
followed from that. The notifier was not in the poll set, so a thread blocked
in SSL_poll() on the listener could not be woken by it. The thread never
called ossl_dtls_listener_enter_blocking_section(), so cur_blocking_waiters
stayed at zero - and since signalling is conditional on there being a waiter,
no other thread even attempted to signal. And there was no re-check after
registering, so readiness arising between the readout deciding nothing was
ready and the wait actually starting was lost.

Polling the socket alone is not sufficient, though not because a wakeup can
be missed outright. poll() reports whatever is currently sitting in the
socket buffer and returns immediately if there is any, so a thread cannot
miss a datagram just by being outside poll() when it arrives. What it can
miss is a datagram another thread has already taken. With several threads
polling the one shared socket that happens constantly: an arriving datagram
wakes all of them, only one gets it, and the rest find nothing. Any of them
can be the one that takes it, because SSL_read() on a connection pumps the
demux and SSL_poll() on a connection ticks the whole listener.

  1. Accept thread A polls the listener for SSL_POLL_EVENT_IC. Its readout
     ticks the listener, finds nothing, and it decides to block.
  2. A client's final ClientHello lands on the shared socket.
  3. Worker thread B, polling one of its own connections, ticks the listener
     and is the one that takes the datagram. Its tick completes the pending
     connection and pushes it onto the accept queue. Signalling is attempted,
     but A never registered as a waiter, so nothing is signalled.
  4. A reaches its poll, watching the socket alone. B drained it, so it is
     empty. A sleeps with a validated connection sitting on the accept queue,
     and nothing can wake it until an unrelated datagram arrives or its
     deadline expires.

A's readout, back at step 1, would have found that connection had it run
after step 3 rather than before it.

Add the notifier FD to the poll set and bracket the wait with the enter/leave
blocking section calls, mirroring what the listener-based connection path
already does. As there, re-check readiness once inside the section, since it
is only from that point that a readiness event is guaranteed to make the
notifier readable, and abort blocking if the listener became ready in the
meantime. The re-check removes the dependency on that ordering; the notifier
covers anything arising after it.

Note that the signal raised when a connection is pushed onto the accept queue
is itself conditional on a registered waiter, so it does nothing for a thread
polling the listener until that thread registers here. The two changes are
complementary and neither is sufficient alone.

Assisted-by: Claude Code:claude-opus-5
@mattcaswell
mattcaswell force-pushed the fix-dtls-ssl-poll-bugs branch from de5e751 to 6298f57 Compare August 12, 2026 07:33
When SSL_poll() decides to block it computes a wakeup deadline from the
per-object event timeout, so that timer driven work is not delayed by the
wait. This was done for QUIC objects but not for DTLS connections, whose
timeout is the handshake retransmission timer.

The consequence was that a poll with no user timeout would sleep until a
datagram arrived, straight through the point at which the connection should
have retransmitted. If the peer had itself lost the message we were waiting
for, neither side would make progress.

Fold SSL_get_event_timeout() into the deadline for DTLS connections as the
QUIC branch already does. It reports the DTLS timer already, so no new
plumbing is needed there.

Waking at the deadline is only useful if something then services the timer.
Unlike the QUIC case, where the readout ticks the reactor and that handles
timeouts, the DTLS readout only pumped the listener's demux. Nothing
retransmitted, and because an expired timer reports zero time remaining the
recomputed deadline would be "now" on every subsequent wait, turning the
sleep into a spin. Call SSL_handle_events() from the readout as well, which
is what a caller polling without SSL_POLL_FLAG_NO_HANDLE_EVENTS is asking
for, and which for a DTLS connection services that timer.

Both of those are the calls the documentation asks new code to use:
DTLSv1_get_timeout(3) and DTLSv1_handle_timeout(3) each record that
SSL_get_event_timeout(3) and SSL_handle_events(3) respectively supersede all
of their use cases. The test asserts its precondition through
SSL_get_event_timeout() for the same reason, which has the side benefit of
using the same call SSL_poll() uses to compute the deadline being tested.

Assisted-by: Claude Code:claude-opus-5
dtls1_handle_timeout() fails the connection with SSLfatal() once
dtls1_check_timeout_num() reports that DTLS1_TMO_ALERT_COUNT unanswered
retransmissions have been sent. It returns at that point without reaching
dtls1_start_timer(), so next_timeout is left holding a time in the past.

Nothing re-arms or clears it afterwards, the connection being finished, so
every later dtls1_get_timeout() computes a negative remaining time, clamps it
to zero and reports the timeout as due immediately. A caller which waits on
that timeout therefore never waits at all. The documented
DTLSv1_get_timeout() plus select() loop turns into a busy loop on any
connection which has given up retransmitting, consuming a core until the
application gives up on the connection itself.

Stop the timer instead. dtls1_stop_timer() zeroes next_timeout, which
dtls1_get_timeout() already reports as "no timer running", so callers see no
pending timeout and wait on whatever other deadline they have.

The test drives this without waiting by forcing a retransmission timeout of
1ms, which is below the 15ms floor at which dtls1_get_timeout() treats a
timer as already expired. The server therefore retransmits on every read
attempt and exhausts its budget inside SSL_accept().

Assisted-by: Claude Code:claude-opus-5
poll_translate_ssl_dtls_conn() adds no file descriptor to the poll set when
the connection's BIO cannot supply one, which is the case for the memory and
datagram-pair BIOs the test suite is built on. Unlike the QUIC and DTLS
listener paths it does not report that as an error, and rightly so: a DTLS
connection with no pollable BIO has no readiness to wait for, but it still
has a retransmission deadline to wake for, so a wait with nothing to watch is
meaningful.

The resulting empty descriptor set was then handed to the operating system,
where the behaviour differs. poll() treats it as a plain sleep, so this
worked on Unix. Windows' select() rejects a call with no descriptors, so
ossl_rio_poll_builder_poll() returned failure and SSL_poll() failed with it.

Sleep out the deadline explicitly rather than relying on the platform. With
no descriptors and no deadline nothing could ever provide a wakeup, so that
combination is reported as a failure instead of sleeping for ever.

This has no test of its own: on any platform whose poll() already sleeps, the
behaviour before and after is identical, so only the Windows CI builds
distinguish them.

Assisted-by: Claude Code:claude-opus-5
@mattcaswell
mattcaswell force-pushed the fix-dtls-ssl-poll-bugs branch from 6298f57 to 501af84 Compare August 12, 2026 07:51
Compare short_timer_cb_count with the matching macro. It is an
unsigned int, so TEST_uint_gt() is the right one rather than
TEST_size_t_gt().
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.

5 participants