]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
buffers: switch from end_offset over to frag_length
authorAlexander Sosedkin <asosedkin@redhat.com>
Mon, 23 Mar 2026 14:09:43 +0000 (15:09 +0100)
committerAlexander Sosedkin <asosedkin@redhat.com>
Wed, 29 Apr 2026 13:35:02 +0000 (15:35 +0200)
Instead of maintaining an inclusive [start_offset, end_offset] range
when reassembling DTLS handshake,
track start_offset and a relative frag_length instead.

You'd think it'd be a no-op, but it fixes:

* 0-length fragments triggering completion if message was 1 byte long
* a remotely triggerable underflow and an ensuing heap overrun

Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1811
Fixes: CVE-2026-33845
Fixes: GNUTLS-SA-2026-04-29-3
CVSS: 7.5 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
lib/buffers.c
lib/gnutls_int.h

index e9ddf0403b35812855e092e2f133f11f7fb8557b..c3df8a37bc584428d72daaa812aee15727ef0387 100644 (file)
@@ -919,10 +919,7 @@ static int parse_handshake_header(gnutls_session_t session, mbuffer_st *bufel,
        }
        data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size;
 
-       if (frag_length > 0)
-               hsk->end_offset = hsk->start_offset + frag_length - 1;
-       else
-               hsk->end_offset = 0;
+       hsk->frag_length = frag_length;
 
        _gnutls_handshake_log(
                "HSK[%p]: %s (%u) was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n",
@@ -936,9 +933,11 @@ static int parse_handshake_header(gnutls_session_t session, mbuffer_st *bufel,
 
        if (hsk->length > 0 &&
            (frag_length > data_size ||
-            (frag_length > 0 && hsk->end_offset >= hsk->length))) {
+            (frag_length > 0 &&
+             hsk->start_offset + frag_length > hsk->length))) {
                return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
-       } else if (hsk->length == 0 && hsk->end_offset != 0 &&
+       } else if (hsk->length == 0 &&
+                  hsk->start_offset + frag_length != hsk->start_offset &&
                   hsk->start_offset != 0)
                return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
 
@@ -1002,11 +1001,10 @@ static int merge_handshake_packet(gnutls_session_t session,
                        hsk->data.length = hsk->length;
                }
 
-               if (hsk->length > 0 && hsk->end_offset > 0 &&
-                   hsk->end_offset - hsk->start_offset + 1 != hsk->length) {
+               if (hsk->length > 0 && hsk->frag_length > 0 &&
+                   hsk->frag_length != hsk->length) {
                        memmove(&hsk->data.data[hsk->start_offset],
-                               hsk->data.data,
-                               hsk->end_offset - hsk->start_offset + 1);
+                               hsk->data.data, hsk->frag_length);
                }
 
                session->internals.handshake_recv_buffer_size++;
@@ -1040,20 +1038,27 @@ static int merge_handshake_packet(gnutls_session_t session,
                }
 
                if (hsk->start_offset < recv_buf[pos].start_offset &&
-                   hsk->end_offset + 1 >= recv_buf[pos].start_offset) {
+                   hsk->start_offset + hsk->frag_length >=
+                           recv_buf[pos].start_offset) {
                        memcpy(&recv_buf[pos].data.data[hsk->start_offset],
                               hsk->data.data, hsk->data.length);
                        recv_buf[pos].start_offset = hsk->start_offset;
-                       recv_buf[pos].end_offset =
-                               MIN(hsk->end_offset, recv_buf[pos].end_offset);
-               } else if (hsk->end_offset > recv_buf[pos].end_offset &&
-                          hsk->start_offset <= recv_buf[pos].end_offset + 1) {
+                       recv_buf[pos].frag_length = MIN(
+                               hsk->frag_length, recv_buf[pos].frag_length);
+               } else if (hsk->start_offset + hsk->frag_length >
+                                  recv_buf[pos].start_offset +
+                                          recv_buf[pos].frag_length &&
+                          hsk->start_offset <=
+                                  recv_buf[pos].start_offset +
+                                          recv_buf[pos].frag_length) {
                        memcpy(&recv_buf[pos].data.data[hsk->start_offset],
                               hsk->data.data, hsk->data.length);
 
-                       recv_buf[pos].end_offset = hsk->end_offset;
                        recv_buf[pos].start_offset = MIN(
                                hsk->start_offset, recv_buf[pos].start_offset);
+                       recv_buf[pos].frag_length = hsk->start_offset +
+                                                   hsk->frag_length -
+                                                   recv_buf[pos].start_offset;
                }
                _gnutls_handshake_buffer_clear(hsk);
        }
@@ -1113,8 +1118,8 @@ static int get_last_packet(gnutls_session_t session,
                }
 
                else if ((recv_buf[LAST_ELEMENT].start_offset == 0 &&
-                         recv_buf[LAST_ELEMENT].end_offset ==
-                                 recv_buf[LAST_ELEMENT].length - 1) ||
+                         recv_buf[LAST_ELEMENT].frag_length ==
+                                 recv_buf[LAST_ELEMENT].length) ||
                         recv_buf[LAST_ELEMENT].length == 0) {
                        session->internals.dtls.hsk_read_seq++;
                        _gnutls_handshake_buffer_move(hsk,
@@ -1125,8 +1130,9 @@ static int get_last_packet(gnutls_session_t session,
                        /* if we don't have a complete handshake message, but we
                         * have queued data waiting, try again to reconstruct the
                         * handshake packet, using the queued */
-                       if (recv_buf[LAST_ELEMENT].end_offset !=
-                                   recv_buf[LAST_ELEMENT].length - 1 &&
+                       if ((recv_buf[LAST_ELEMENT].start_offset +
+                            recv_buf[LAST_ELEMENT].frag_length) !=
+                                   recv_buf[LAST_ELEMENT].length &&
                            record_check_unprocessed(session) > 0)
                                return gnutls_assert_val(
                                        GNUTLS_E_INT_CHECK_AGAIN);
@@ -1313,9 +1319,7 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
                                        &session->internals.record_buffer,
                                        bufel, ret);
 
-                               data_size = MIN(tmp.length,
-                                               tmp.end_offset -
-                                                       tmp.start_offset + 1);
+                               data_size = MIN(tmp.length, tmp.frag_length);
 
                                ret = _gnutls_buffer_append_data(
                                        &tmp.data,
@@ -1331,7 +1335,6 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
                                ret = merge_handshake_packet(session, &tmp);
                                if (ret < 0)
                                        return gnutls_assert_val(ret);
-
                        } while (_mbuffer_get_udata_size(bufel) > 0);
 
                        prev = bufel;
index d1643be9d4a193bfcf597ba7903a2e70385a0648..3e5a8f36149357ca232d1ff7143690bbf3fa910d 100644 (file)
@@ -460,10 +460,10 @@ typedef struct {
        uint16_t sequence;
 
        /* indicate whether that message is complete.
-        * complete means start_offset == 0 and end_offset == length
+        * complete means start_offset == 0 and frag_length == length
         */
        uint32_t start_offset;
-       uint32_t end_offset;
+       uint32_t frag_length; /* used exclusively in DTLS reassembly */
 
        uint8_t header[MAX_HANDSHAKE_HEADER_SIZE];
        int header_size;