Commit 071d697199 for openssl.org
commit 071d69719904764269d40f3cef7bcb308c87c193
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Thu Sep 17 18:00:07 2026 +0900
dtls: read the rest of a DTLS 1.3 ACK body from the current record
An ACK never spans records (RFC 9147 section 4), so once the first
DTLS1_HM_HEADER_LENGTH bytes are read, the remainder of the body is
exactly what is left in the current record. Take it directly instead
of calling ssl_read_bytes() a second time: that call re-enters the
timeout handling and can overwrite the ACK bytes already copied into
init_buf.
Assisted-by: pi:kimi-k3
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep 22 15:47:56 2026
Merged-from: https://github.com/openssl/openssl/pull/32871
diff --git a/ssl/statem/statem_dtls.c b/ssl/statem/statem_dtls.c
index bff336e3ec..db1023e669 100644
--- a/ssl/statem/statem_dtls.c
+++ b/ssl/statem/statem_dtls.c
@@ -1046,28 +1046,33 @@ redo:
}
if (recvd_type == SSL3_RT_ACK) {
/*
- * An ACK has no message header: the bytes already read are body, and
- * an ACK never spans records, so read the rest of this one. If that
- * first read already exhausted the record there is no more body to
- * read, and asking would fetch an unrelated record.
+ * An ACK has no handshake message header: the bytes already read
+ * are body, and an ACK never spans records (RFC 9147 section 4),
+ * so the rest of the body - if any - is whatever is left in the
+ * current record. Take it directly to avoid re-entering the
+ * timeout path while assembling the ACK.
*/
if (readbytes == DTLS1_HM_HEADER_LENGTH
&& s->rlayer.curr_rec < s->rlayer.num_recs) {
- const size_t first_readbytes = readbytes;
+ TLS_RECORD *rr = &s->rlayer.tlsrecs[s->rlayer.curr_rec];
- p += DTLS1_HM_HEADER_LENGTH;
-
- i = ssl->method->ssl_read_bytes(ssl, SSL3_RT_HANDSHAKE, NULL, p,
- s->rlayer.tlsrecs[s->rlayer.curr_rec].length, 0, &readbytes);
- readbytes += first_readbytes;
/*
- * This shouldn't ever fail due to NBIO because we already checked
- * that we have enough data in the record
+ * DTLS currently processes one record at a time, so an exhausted
+ * record is excluded above. Keep these checks to avoid consuming
+ * a following record if pipelining is added.
*/
- if (i <= 0) {
- s->rwstate = SSL_READING;
- *len = 0;
- return 0;
+ if (rr->off > 0 && rr->length > 0) {
+ /*
+ * init_buf has capacity for a full plaintext record, whose size
+ * has already been checked by the record layer.
+ */
+ memcpy(p + DTLS1_HM_HEADER_LENGTH, rr->data + rr->off,
+ rr->length);
+ readbytes += rr->length;
+ if (!ssl_release_record(s, rr, rr->length)) {
+ /* SSLfatal() already called */
+ goto f_err;
+ }
}
}
s->init_num = readbytes;