Commit ec444cb914 for openssl.org
commit ec444cb914aa3d4b21ce108e7953743998436369
Author: baoyi84930 <zhangcheng170@huawei.com>
Date: Fri Jul 24 10:47:51 2026 +0800
dtls: fix DTLSv1_listen record sequence after cookie verify
DTLSv1_listen() copied the ClientHello record sequence into the
HelloVerifyRequest, but after a valid cookie it advanced the write
sequence by one. If the client retransmitted ClientHello, the following
ServerHello could be sent with the wrong record sequence.
Seed the DTLS write record sequence from the valid-cookie ClientHello
before continuing the handshake. Also make DTLS 1.2 write sequence
increments fail at the 48-bit wire sequence boundary instead of carrying
into the unused high bytes.
Add tests for the ServerHello record sequence and DTLS 1.2 sequence wrap
handling.
Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Merge-date: Mon Sep 7 08:15:23 2026
Merged-from: https://github.com/openssl/openssl/pull/32064
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index ca3c09aca8..c61edad7d2 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -614,6 +614,7 @@ int DTLSv1_listen(SSL *ssl, BIO_ADDR *client)
const unsigned char *data;
unsigned char *buf = NULL, *wbuf;
size_t fragoff, fraglen, msglen;
+ uint64_t record_sequence = 0;
unsigned int rectype, versmajor, versminor, msgseq, msgtype, clientvers, cookielen;
BIO *rbio, *wbio;
BIO_ADDR *tmpclient = NULL;
@@ -774,6 +775,12 @@ int DTLSv1_listen(SSL *ssl, BIO_ADDR *client)
ERR_raise(ERR_LIB_SSL, SSL_R_UNEXPECTED_MESSAGE);
goto end;
}
+ record_sequence = ((uint64_t)seq[2]) << 40;
+ record_sequence |= ((uint64_t)seq[3]) << 32;
+ record_sequence |= ((uint64_t)seq[4]) << 24;
+ record_sequence |= ((uint64_t)seq[5]) << 16;
+ record_sequence |= ((uint64_t)seq[6]) << 8;
+ record_sequence |= ((uint64_t)seq[7]);
/* Get a pointer to the raw message for the later callback */
data = PACKET_data(&msgpkt);
@@ -1028,7 +1035,13 @@ int DTLSv1_listen(SSL *ssl, BIO_ADDR *client)
s->d1->handshake_read_seq = 1;
s->d1->handshake_write_seq = 1;
s->d1->next_handshake_write_seq = 1;
- s->rlayer.wrlmethod->increment_sequence_ctr(s->rlayer.wrl);
+ if (s->rlayer.wrlmethod->set_sequence == NULL
+ || !s->rlayer.wrlmethod->set_sequence(s->rlayer.wrl,
+ record_sequence)) {
+ ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
+ ret = -1;
+ goto end;
+ }
/*
* We are doing cookie exchange, so make sure we set that option in the
diff --git a/ssl/record/methods/dtls_meth.c b/ssl/record/methods/dtls_meth.c
index df155ad75b..ec895b9b6e 100644
--- a/ssl/record/methods/dtls_meth.c
+++ b/ssl/record/methods/dtls_meth.c
@@ -15,6 +15,8 @@
OSSL_SAFE_MATH_UNSIGNED(uint64_t, uint64_t)
+static int dtls_increment_sequence_ctr(OSSL_RECORD_LAYER *rl);
+
/* mod 128 saturating subtract of two 64-bit values */
static int satsub64(uint64_t l1, uint64_t l2)
{
@@ -944,7 +946,21 @@ int dtls_post_encryption_processing(OSSL_RECORD_LAYER *rl,
return 0;
}
- return tls_increment_sequence_ctr(rl);
+ return dtls_increment_sequence_ctr(rl);
+}
+
+static int dtls_increment_sequence_ctr(OSSL_RECORD_LAYER *rl)
+{
+ if (rl->version == DTLS1_3_VERSION)
+ return tls_increment_sequence_ctr(rl);
+
+ if (rl->sequence >= 0xffffffffffffULL) {
+ RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, SSL_R_SEQUENCE_CTR_WRAPPED);
+ return 0;
+ }
+
+ rl->sequence++;
+ return 1;
}
static size_t dtls_get_max_record_overhead(OSSL_RECORD_LAYER *rl)
@@ -1041,7 +1057,7 @@ const OSSL_RECORD_METHOD ossl_dtls_record_method = {
tls_get_compression,
tls_set_max_frag_len,
dtls_get_max_record_overhead,
- tls_increment_sequence_ctr,
+ dtls_increment_sequence_ctr,
dtls_get_sequence_number,
dtls_set_sequence_number,
dtls_get_epoch,
diff --git a/test/dtlsv1listentest.c b/test/dtlsv1listentest.c
index e01126bf62..36106ef541 100644
--- a/test/dtlsv1listentest.c
+++ b/test/dtlsv1listentest.c
@@ -7,12 +7,14 @@
* https://www.openssl.org/source/license.html
*/
+#include <limits.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/conf.h>
#include "internal/nelem.h"
+#include "internal/ssl_unwrap.h"
#include "../ssl/ssl_local.h"
#include "helpers/ssltestlib.h"
#include "testutil.h"
@@ -26,6 +28,8 @@ static char *privkey = NULL;
#ifndef OPENSSL_NO_SOCK
+#define DTLS_RECORD_EPOCH_AND_SEQ_LEN 8
+
/* Just a ClientHello without a cookie */
static const unsigned char clienthello_nocookie[] = {
0x16, /* Handshake */
@@ -615,6 +619,157 @@ err:
return success;
}
+#ifndef OPENSSL_NO_DTLS1_2
+static unsigned char *create_cookie_clienthello(int *outlen,
+ const unsigned char *seq)
+{
+ SSL_CTX *ctx = NULL;
+ SSL *ssl = NULL;
+ BIO *rbio = NULL, *wbio = NULL;
+ BIO *ssl_rbio = NULL, *ssl_wbio = NULL;
+ char *data = NULL;
+ long datalen;
+ unsigned char *ret = NULL;
+ int sslret;
+
+ if (!TEST_ptr(ctx = SSL_CTX_new(DTLS_client_method()))
+ || !TEST_ptr(ssl = SSL_new(ctx))
+ || !TEST_ptr(rbio = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(wbio = BIO_new(BIO_s_mem())))
+ goto err;
+
+ ssl_rbio = rbio;
+ ssl_wbio = wbio;
+ SSL_set0_rbio(ssl, rbio);
+ SSL_set0_wbio(ssl, wbio);
+ rbio = wbio = NULL;
+ SSL_set_connect_state(ssl);
+
+ if (!TEST_int_le(sslret = SSL_connect(ssl), 0)
+ || !TEST_int_eq(SSL_get_error(ssl, sslret), SSL_ERROR_WANT_READ)
+ || !TEST_int_gt(BIO_reset(ssl_wbio), 0)
+ || !TEST_int_eq(BIO_write(ssl_rbio, verify, sizeof(verify)),
+ sizeof(verify))
+ || !TEST_int_le(sslret = SSL_connect(ssl), 0)
+ || !TEST_int_eq(SSL_get_error(ssl, sslret), SSL_ERROR_WANT_READ)
+ || !TEST_long_ge(datalen = BIO_get_mem_data(ssl_wbio, &data),
+ DTLS1_RT_HEADER_LENGTH)
+ || !TEST_long_le(datalen, INT_MAX))
+ goto err;
+
+ if (!TEST_ptr(ret = OPENSSL_memdup(data, datalen)))
+ goto err;
+
+ *outlen = (int)datalen;
+
+ /* DTLS record header bytes 3..10 are epoch and sequence number. */
+ memcpy(ret + 3, seq, DTLS_RECORD_EPOCH_AND_SEQ_LEN);
+
+err:
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+ BIO_free(rbio);
+ BIO_free(wbio);
+ return ret;
+}
+
+static int dtls_listen_write_seq_test(int tst)
+{
+ static const unsigned char initial_seq[DTLS_RECORD_EPOCH_AND_SEQ_LEN] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
+ };
+ static const unsigned char expected_record_seq[2 + DTLS_RECORD_EPOCH_AND_SEQ_LEN] = {
+ 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02
+ };
+ unsigned char *inbuf = NULL;
+ int inbuflen = 0;
+ SSL_CONNECTION *s = NULL;
+ SSL_CTX *ctx = NULL;
+ SSL *ssl = NULL;
+ BIO *outbio = NULL;
+ BIO *inbio = NULL;
+ BIO_ADDR *peer = NULL;
+ char *data;
+ long datalen;
+ int ret, success = 0;
+
+ if (!TEST_ptr(inbuf = create_cookie_clienthello(&inbuflen, initial_seq)))
+ goto err;
+
+ if (!TEST_ptr(ctx = SSL_CTX_new(DTLS_server_method()))
+ || !TEST_ptr(peer = BIO_ADDR_new()))
+ goto err;
+ SSL_CTX_set_cookie_generate_cb(ctx, cookie_gen);
+ SSL_CTX_set_cookie_verify_cb(ctx, cookie_verify);
+ if (!TEST_true(SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM))
+ || !TEST_true(SSL_CTX_use_PrivateKey_file(ctx, privkey,
+ SSL_FILETYPE_PEM)))
+ goto err;
+
+ if (!TEST_ptr(ssl = SSL_new(ctx))
+ || !TEST_ptr(outbio = BIO_new(BIO_s_mem())))
+ goto err;
+
+ SSL_set0_wbio(ssl, outbio);
+ if (!TEST_ptr(inbio = BIO_new_mem_buf(inbuf, inbuflen)))
+ goto err;
+
+ BIO_set_mem_eof_return(inbio, -1);
+ SSL_set0_rbio(ssl, inbio);
+ inbio = NULL;
+
+ if (!TEST_int_eq(ret = DTLSv1_listen(ssl, peer), 1))
+ goto err;
+
+ datalen = BIO_get_mem_data(outbio, &data);
+ if (!TEST_long_eq(datalen, 0))
+ goto err;
+
+ if (tst == 1) {
+ /* Drive the DTLS 1.2 write-side uint48 wrap path directly. */
+ s = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
+ if (!TEST_ptr(s)
+ || !TEST_true(s->rlayer.wrlmethod->set_sequence != NULL)
+ || !TEST_true(s->rlayer.wrlmethod->set_sequence(s->rlayer.wrl,
+ 0xffffffffffffULL)))
+ goto err;
+ }
+
+ ret = SSL_accept(ssl);
+ if (tst == 1) {
+ if (!TEST_int_le(ret, 0)
+ || !TEST_int_eq(SSL_get_error(ssl, ret), SSL_ERROR_SSL)
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ SSL_R_SEQUENCE_CTR_WRAPPED))
+ goto err;
+ success = 1;
+ goto err;
+ }
+
+ if (!TEST_int_le(ret, 0)
+ || !TEST_int_eq(SSL_get_error(ssl, ret), SSL_ERROR_WANT_READ))
+ goto err;
+
+ datalen = BIO_get_mem_data(outbio, &data);
+ if (!TEST_long_ge(datalen, DTLS1_RT_HEADER_LENGTH)
+ || !TEST_mem_eq(data + 1, sizeof(expected_record_seq),
+ expected_record_seq, sizeof(expected_record_seq)))
+ goto err;
+
+ SSL_set0_rbio(ssl, NULL);
+ success = 1;
+
+err:
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+ BIO_free(inbio);
+ OPENSSL_free(inbuf);
+ OPENSSL_free(peer);
+ return success;
+}
+#endif
+
#ifndef OPENSSL_NO_DTLS1_3
/*
* Test that DTLSv1_listen() clamps the max version to DTLS 1.2.
@@ -755,6 +910,9 @@ int setup_tests(void)
#ifndef OPENSSL_NO_SOCK
ADD_ALL_TESTS(dtls_listen_test,
(int)OSSL_NELEM(testpackets) + (int)OSSL_NELEM(testpackets13));
+#ifndef OPENSSL_NO_DTLS1_2
+ ADD_ALL_TESTS(dtls_listen_write_seq_test, 2);
+#endif
#ifndef OPENSSL_NO_DTLS1_3
ADD_TEST(test_dtls_listen_dtls13_negotiated_to_dtls12);
ADD_TEST(test_dtls13_listen_client_dtls13_only);