diff options
| author | Matt Caswell <[email protected]> | 2026-10-19 11:58:18 +0200 |
|---|---|---|
| committer | Tomas Mraz <[email protected]> | 2026-10-21 11:21:35 +0200 |
| commit | 63bd920445f5339aa0ccaf07475df7abac2c8632 (patch) | |
| tree | 5a8824f3850812d2254a1520b9e6e16dc72a4d99 | |
| parent | Don't dereference a NULL rbio in dtls1_read_failed() (diff) | |
| download | openssl-63bd920445f5339aa0ccaf07475df7abac2c8632.tar.gz openssl-63bd920445f5339aa0ccaf07475df7abac2c8632.zip | |
Don't report held-back DTLS app data as readable in SSL_poll()
ossl_dtls_conn_poll_events() reported SSL_POLL_EVENT_R whenever
SSL_has_pending() was true. For DTLS that includes records in
buffered_app_data, which dtls1_read_bytes() only releases once the
handshake has finished.
In DTLS 1.3 the sender of a KeyUpdate stays in init until it is ACKed.
If the peer sends application data without ACKing, e.g.:
1. Server sends KeyUpdate, enters TLS_ST_SW_KEY_UPDATE.
2. Client sends a record before reading the KeyUpdate.
3. Server's SSL_read() buffers the record, returns WANT_READ.
then every SSL_poll() returns at once with SSL_POLL_EVENT_R and every
SSL_read() returns SSL_ERROR_WANT_READ. An event loop spins until the
ACK arrives or the KeyUpdate retransmissions run out (483s). The same
happens to a client, and in DTLS 1.2 for data received between
ChangeCipherSpec and Finished.
Only count buffered_app_data once the handshake has finished, and
otherwise wait for incoming datagrams as usual.
Discovered by Anthropic's OSS Scanner (finding ANT-2026-QCQMEE2S).
Assisted-by: Claude Code:claude-opus-5-5
Reviewed-by: Mounir Idrassi <[email protected]>
Reviewed-by: Ryan Hooper <[email protected]>
Merge-date: Wed Oct 21 09:21:56 2026
Merged-from: https://github.com/openssl/openssl/pull/33262
| -rw-r--r-- | ssl/d1_lib.c | 12 | ||||
| -rw-r--r-- | ssl/record/rec_layer_d1.c | 19 | ||||
| -rw-r--r-- | ssl/record/record.h | 1 | ||||
| -rw-r--r-- | ssl/ssl_lib.c | 14 | ||||
| -rw-r--r-- | test/dtlsssllistenertest.c | 159 |
5 files changed, 193 insertions, 12 deletions
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c index c4b01779c93..c4a3d32bcd8 100644 --- a/ssl/d1_lib.c +++ b/ssl/d1_lib.c @@ -3385,7 +3385,17 @@ int ossl_dtls_conn_poll_events(SSL *s, uint64_t events, int do_tick, SSL_handle_events(s); if ((events & SSL_POLL_EVENT_R) != 0) { - if (SSL_has_pending(s) || SSL_pending(s) > 0) { + /* + * Application data buffered during a handshake cannot be read until + * the handshake finishes. Until then, reporting it would make + * SSL_poll() return immediately while SSL_read() keeps failing with + * SSL_ERROR_WANT_READ, so wait for the datagrams that complete the + * handshake instead. + */ + if (RECORD_LAYER_processed_read_pending(&sc->rlayer) + || RECORD_LAYER_read_pending(&sc->rlayer) + || (SSL_is_init_finished(s) + && DTLS_RECORD_LAYER_buffered_app_data_pending(&sc->rlayer))) { result |= SSL_POLL_EVENT_R; } else if (sc->d1->rx != NULL) { /* Listener-based connection: check URXE queue */ diff --git a/ssl/record/rec_layer_d1.c b/ssl/record/rec_layer_d1.c index 19072af9cdb..1c9ae2e1381 100644 --- a/ssl/record/rec_layer_d1.c +++ b/ssl/record/rec_layer_d1.c @@ -72,6 +72,25 @@ void DTLS_RECORD_LAYER_clear(RECORD_LAYER *rl) d->buffered_app_data = buffered_app_data; } +/* + * Checks for application data set aside while a handshake was in progress. + * dtls1_read_bytes() only returns it once the handshake has finished. + */ +int DTLS_RECORD_LAYER_buffered_app_data_pending(const RECORD_LAYER *rl) +{ + TLS_RECORD *rdata; + pitem *item, *iter; + + iter = pqueue_iterator(rl->d->buffered_app_data); + while ((item = pqueue_next(&iter)) != NULL) { + rdata = item->data; + if (rdata->length > 0) + return 1; + } + + return 0; +} + static int dtls_buffer_record(SSL_CONNECTION *s, TLS_RECORD *rec) { TLS_RECORD *rdata; diff --git a/ssl/record/record.h b/ssl/record/record.h index 13b709c2bf9..c622c1f0b40 100644 --- a/ssl/record/record.h +++ b/ssl/record/record.h @@ -165,6 +165,7 @@ __owur int ssl3_read_bytes(SSL *s, uint8_t type, uint8_t *recvd_type, int DTLS_RECORD_LAYER_new(RECORD_LAYER *rl); void DTLS_RECORD_LAYER_free(RECORD_LAYER *rl); void DTLS_RECORD_LAYER_clear(RECORD_LAYER *rl); +int DTLS_RECORD_LAYER_buffered_app_data_pending(const RECORD_LAYER *rl); __owur int dtls1_read_bytes(SSL *s, uint8_t type, uint8_t *recvd_type, unsigned char *buf, size_t len, int peek, size_t *readbytes); diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index c71d7f59c63..b40783a21e1 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -2116,17 +2116,9 @@ int SSL_has_pending(const SSL *s) sc = SSL_CONNECTION_FROM_CONST_SSL(s); /* Check buffered app data if any first */ - if (SSL_CONNECTION_IS_DTLS(sc)) { - TLS_RECORD *rdata; - pitem *item, *iter; - - iter = pqueue_iterator(sc->rlayer.d->buffered_app_data); - while ((item = pqueue_next(&iter)) != NULL) { - rdata = item->data; - if (rdata->length > 0) - return 1; - } - } + if (SSL_CONNECTION_IS_DTLS(sc) + && DTLS_RECORD_LAYER_buffered_app_data_pending(&sc->rlayer)) + return 1; if (RECORD_LAYER_processed_read_pending(&sc->rlayer)) return 1; diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c index 06c783cff98..79bf863d006 100644 --- a/test/dtlsssllistenertest.c +++ b/test/dtlsssllistenertest.c @@ -3551,6 +3551,162 @@ end: return testresult; } +#ifndef OPENSSL_NO_DTLS1_3 +static int poll_conn_readable(SSL *s, int *readable) +{ + SSL_POLL_ITEM item; + struct timeval timeout = { 0, 0 }; + size_t count; + + item.desc.type = BIO_POLL_DESCRIPTOR_TYPE_SSL; + item.desc.value.ssl = s; + item.events = SSL_POLL_EVENT_R; + item.revents = 0; + + if (!TEST_true(SSL_poll(&item, 1, sizeof(item), &timeout, 0, &count))) + return 0; + *readable = (item.revents & SSL_POLL_EVENT_R) != 0; + return 1; +} + +/* + * Test: SSL_poll() does not report SSL_POLL_EVENT_R for application data that + * cannot be read until a handshake in progress completes. + * + * In DTLS 1.3 the sender of a KeyUpdate stays in init until it is ACKed, and + * application data arriving meanwhile is held back until then. If SSL_poll() + * reports that data as readable, an event loop spins: every SSL_read() returns + * SSL_ERROR_WANT_READ and every SSL_poll() returns immediately, until the peer + * sends its ACK or the retransmissions run out. + * + * idx 0: the server (a listener connection) sends the KeyUpdate + * idx 1: the client (a standalone connection) sends the KeyUpdate + */ +static int test_dtls13_poll_conn_held_app_data(int idx) +{ + SSL_CTX *sctx = NULL, *cctx = NULL; + SSL *listener = NULL; + SSL *serverssl = NULL, *clientssl = NULL; + SSL *updater, *peer; + BIO_ADDR *client_addr = NULL; + unsigned char buf[16]; + size_t n; + int ret, readable = 0; + int testresult = 0; + int abortctr = 0; + SSL_POLL_ITEM poll_item; + struct timeval poll_timeout; + size_t poll_result; + + if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(), + DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION, + &sctx, &cctx, cert, privkey))) + goto end; + + if (!TEST_true(create_dtls_listener_and_client_mem(sctx, cctx, + SSL_LISTENER_FLAG_SINGLE_THREAD, + &listener, &clientssl, &client_addr))) + goto end; + + SSL_set_connect_state(clientssl); + while (serverssl == NULL) { + if (++abortctr > 100) { + TEST_error("Connection loop did not converge"); + goto end; + } + + ret = SSL_connect(clientssl); + if (ret <= 0 && SSL_get_error(clientssl, ret) != SSL_ERROR_WANT_READ) { + TEST_error("SSL_connect failed"); + goto end; + } + + poll_item.desc.type = BIO_POLL_DESCRIPTOR_TYPE_SSL; + poll_item.desc.value.ssl = listener; + poll_item.events = SSL_POLL_EVENT_IC; + poll_item.revents = 0; + poll_timeout.tv_sec = 0; + poll_timeout.tv_usec = 0; + + if (!TEST_true(SSL_poll(&poll_item, 1, sizeof(poll_item), &poll_timeout, 0, &poll_result))) + goto end; + + if (poll_result > 0 && (poll_item.revents & SSL_POLL_EVENT_IC) != 0) + serverssl = SSL_accept_connection(listener, SSL_ACCEPT_CONNECTION_NO_BLOCK); + } + + if (!TEST_ptr(serverssl) + || !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) + goto end; + + /* Consume the tickets and their ACKs so that nothing is left to read. */ + ret = SSL_read(clientssl, buf, sizeof(buf)); + if (!TEST_int_eq(SSL_get_error(clientssl, ret), SSL_ERROR_WANT_READ)) + goto end; + ret = SSL_read(serverssl, buf, sizeof(buf)); + if (!TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_WANT_READ)) + goto end; + + updater = idx == 0 ? serverssl : clientssl; + peer = idx == 0 ? clientssl : serverssl; + + if (!TEST_true(SSL_key_update(updater, SSL_KEY_UPDATE_NOT_REQUESTED))) + goto end; + ret = SSL_do_handshake(updater); + if (!TEST_int_eq(SSL_get_error(updater, ret), SSL_ERROR_WANT_READ) + || !TEST_int_eq(SSL_get_state(updater), + idx == 0 ? TLS_ST_SW_KEY_UPDATE : TLS_ST_CW_KEY_UPDATE)) + goto end; + + /* The peer sends data before it has read, and so ACKed, the KeyUpdate. */ + if (!TEST_true(SSL_write_ex(peer, "x", 1, &n))) + goto end; + + /* The updater reads that record but must hold it back until the ACK. */ + ret = SSL_read(updater, buf, sizeof(buf)); + if (!TEST_int_eq(SSL_get_error(updater, ret), SSL_ERROR_WANT_READ) + || !TEST_true(SSL_in_init(updater)) + || !TEST_true(SSL_has_pending(updater))) + goto end; + + if (!poll_conn_readable(updater, &readable) + || !TEST_false(readable)) + goto end; + + /* The peer reads the KeyUpdate and ACKs it. */ + ret = SSL_read(peer, buf, sizeof(buf)); + if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ)) + goto end; + + /* + * The waiting ACK makes the updater readable. SSL_poll() does not process + * it, SSL_read() does: that completes the KeyUpdate and releases the held + * record. + */ + if (!poll_conn_readable(updater, &readable) + || !TEST_true(readable) + || !TEST_true(SSL_in_init(updater)) + || !TEST_true(SSL_read_ex(updater, buf, sizeof(buf), &n)) + || !TEST_mem_eq(buf, n, "x", 1) + || !TEST_false(SSL_in_init(updater))) + goto end; + + if (!poll_conn_readable(updater, &readable) + || !TEST_false(readable)) + goto end; + + testresult = 1; +end: + SSL_free(serverssl); + SSL_free(clientssl); + SSL_free(listener); + BIO_ADDR_free(client_addr); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + return testresult; +} +#endif /* OPENSSL_NO_DTLS1_3 */ + /* * Test: SSL_poll() on listener with multiple event types. * @@ -6686,6 +6842,9 @@ int setup_tests(void) ADD_TEST(test_dtls_poll_conn_event_w); ADD_TEST(test_dtls_poll_conn_dgram_pair_readable); ADD_TEST(test_dtls_poll_conn_no_events_before_data); +#ifndef OPENSSL_NO_DTLS1_3 + ADD_ALL_TESTS(test_dtls13_poll_conn_held_app_data, 2); +#endif ADD_TEST(test_dtls_poll_listener_multiple_events); ADD_TEST(test_dtls_poll_conn_event_ec); ADD_TEST(test_dtls_poll_null_item); |
