DTLS fragment reassembly heap overwrite in merge_handshake_packet()
During DTLS handshake fragment reassembly, the first fragment allocates state using its declared total length, but a later fragment with the same handshake type is merged using its own independently parsed length, offset, and payload size. `merge_handshake_packet()` does not verify that the later fragment's declared total length matches the stored message length or that `start_offset + fragment_length` stays within the destination buffer before `memcpy()`. A remote unauthenticated DTLS peer can therefore send overlapping fragments that cause heap corruption before handshake authentication completes.
**Impact**: A remote unauthenticated attacker can corrupt heap memory and likely crash a DTLS client or server during handshake processing.
**Suggested fix**: Reject fragments whose declared total length differs from the stored message, enforce strict bounds checks before copying fragment data, and consider matching reassembly state on DTLS handshake sequence as well as type.
A candidate patch is included here:
```diff
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -1008,7 +1008,18 @@ static int merge_handshake_packet(gnutls_session_t session,
_gnutls_handshake_buffer_move(
&session->internals.handshake_recv_buffer[pos], hsk);
} else {
+ /* Fragments for the same message must agree on the total
+ * handshake length. */
+ if (hsk->length !=
+ session->internals.handshake_recv_buffer[pos].length) {
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+
if (hsk->start_offset <
session->internals.handshake_recv_buffer[pos]
.start_offset &&
hsk->end_offset + 1 >=
session->internals.handshake_recv_buffer[pos]
.start_offset) {
+ if (hsk->start_offset >
+ session->internals.handshake_recv_buffer[pos]
+ .data.length ||
+ hsk->data.length >
+ session->internals.handshake_recv_buffer[pos]
+ .data.length -
+ hsk->start_offset) {
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
memcpy(&session->internals.handshake_recv_buffer[pos]
.data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
@@ -1032,6 +1043,15 @@ static int merge_handshake_packet(gnutls_session_t session,
hsk->start_offset <=
session->internals.handshake_recv_buffer[pos]
.end_offset +
1) {
+ if (hsk->start_offset >
+ session->internals.handshake_recv_buffer[pos]
+ .data.length ||
+ hsk->data.length >
+ session->internals.handshake_recv_buffer[pos]
+ .data.length -
+ hsk->start_offset) {
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
memcpy(&session->internals.handshake_recv_buffer[pos]
.data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
```
Happy to provide more info as needed. Thanks.
issue