Coverage Report

Created: 2023-03-26 07:33

/src/gnutls/lib/ext/heartbeat.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2012,2013 Free Software Foundation, Inc.
3
 * Copyright (C) 2013 Nikos Mavrogiannopoulos
4
 *
5
 * Author: Nikos Mavrogiannopoulos
6
 *
7
 * This file is part of GnuTLS.
8
 *
9
 * The GnuTLS is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU Lesser General Public License
11
 * as published by the Free Software Foundation; either version 2.1 of
12
 * the License, or (at your option) any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful, but
15
 * WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with this program.  If not, see <https://www.gnu.org/licenses/>
21
 *
22
 */
23
24
/* This file implements the TLS heartbeat extension.
25
 */
26
27
#include "errors.h"
28
#include "gnutls_int.h"
29
#include <dtls.h>
30
#include <record.h>
31
#include <ext/heartbeat.h>
32
#include <hello_ext.h>
33
#include <random.h>
34
35
#ifdef ENABLE_HEARTBEAT
36
/**
37
  * gnutls_heartbeat_enable:
38
  * @session: is a #gnutls_session_t type.
39
  * @type: one of the GNUTLS_HB_* flags
40
  *
41
  * If this function is called with the %GNUTLS_HB_PEER_ALLOWED_TO_SEND
42
  * @type, GnuTLS will allow heartbeat messages to be received. Moreover it also
43
  * request the peer to accept heartbeat messages. This function
44
  * must be called prior to TLS handshake.
45
  *
46
  * If the @type used is %GNUTLS_HB_LOCAL_ALLOWED_TO_SEND, then the peer
47
  * will be asked to accept heartbeat messages but not send ones.
48
  *
49
  * The function gnutls_heartbeat_allowed() can be used to test Whether
50
  * locally generated heartbeat messages can be accepted by the peer.
51
  *
52
  * Since: 3.1.2
53
  **/
54
void gnutls_heartbeat_enable(gnutls_session_t session, unsigned int type)
55
{
56
  gnutls_ext_priv_data_t epriv;
57
58
  epriv = (void *)(intptr_t) type;
59
  _gnutls_hello_ext_set_priv(session, GNUTLS_EXTENSION_HEARTBEAT, epriv);
60
}
61
62
/**
63
  * gnutls_heartbeat_allowed:
64
  * @session: is a #gnutls_session_t type.
65
  * @type: one of %GNUTLS_HB_LOCAL_ALLOWED_TO_SEND and %GNUTLS_HB_PEER_ALLOWED_TO_SEND
66
  *
67
  * This function will check whether heartbeats are allowed
68
  * to be sent or received in this session. 
69
  *
70
  * Returns: Non zero if heartbeats are allowed.
71
  *
72
  * Since: 3.1.2
73
  **/
74
unsigned gnutls_heartbeat_allowed(gnutls_session_t session, unsigned int type)
75
{
76
  gnutls_ext_priv_data_t epriv;
77
78
  if (session->internals.handshake_in_progress != 0)
79
    return 0; /* not allowed */
80
81
  if (_gnutls_hello_ext_get_priv
82
      (session, GNUTLS_EXTENSION_HEARTBEAT, &epriv) < 0)
83
    return 0; /* Not enabled */
84
85
  if (type == GNUTLS_HB_LOCAL_ALLOWED_TO_SEND) {
86
    if (((intptr_t) epriv) & LOCAL_ALLOWED_TO_SEND)
87
      return 1;
88
  } else if (((intptr_t) epriv) & GNUTLS_HB_PEER_ALLOWED_TO_SEND)
89
    return 1;
90
91
  return 0;
92
}
93
94
# define DEFAULT_PADDING_SIZE 16
95
96
/*
97
 * Sends heartbeat data.
98
 */
99
static int
100
heartbeat_send_data(gnutls_session_t session, const void *data,
101
        size_t data_size, uint8_t type)
102
{
103
  int ret, pos;
104
  uint8_t *response;
105
106
  response = gnutls_malloc(1 + 2 + data_size + DEFAULT_PADDING_SIZE);
107
  if (response == NULL)
108
    return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
109
110
  pos = 0;
111
  response[pos++] = type;
112
113
  _gnutls_write_uint16(data_size, &response[pos]);
114
  pos += 2;
115
116
  memcpy(&response[pos], data, data_size);
117
  pos += data_size;
118
119
  ret =
120
      gnutls_rnd(GNUTLS_RND_NONCE, &response[pos], DEFAULT_PADDING_SIZE);
121
  if (ret < 0) {
122
    gnutls_assert();
123
    goto cleanup;
124
  }
125
  pos += DEFAULT_PADDING_SIZE;
126
127
  ret =
128
      _gnutls_send_int(session, GNUTLS_HEARTBEAT, -1,
129
           EPOCH_WRITE_CURRENT, response, pos, MBUFFER_FLUSH);
130
131
 cleanup:
132
  gnutls_free(response);
133
  return ret;
134
}
135
136
/**
137
 * gnutls_heartbeat_ping:
138
 * @session: is a #gnutls_session_t type.
139
 * @data_size: is the length of the ping payload.
140
 * @max_tries: if flags is %GNUTLS_HEARTBEAT_WAIT then this sets the number of retransmissions. Use zero for indefinite (until timeout).
141
 * @flags: if %GNUTLS_HEARTBEAT_WAIT then wait for pong or timeout instead of returning immediately.
142
 *
143
 * This function sends a ping to the peer. If the @flags is set
144
 * to %GNUTLS_HEARTBEAT_WAIT then it waits for a reply from the peer.
145
 * 
146
 * Note that it is highly recommended to use this function with the
147
 * flag %GNUTLS_HEARTBEAT_WAIT, or you need to handle retransmissions
148
 * and timeouts manually.
149
 *
150
 * The total TLS data transmitted as part of the ping message are given by
151
 * the following formula: MAX(16, @data_size)+gnutls_record_overhead_size()+3.
152
 *
153
 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
154
 *
155
 * Since: 3.1.2
156
 **/
157
int
158
gnutls_heartbeat_ping(gnutls_session_t session, size_t data_size,
159
          unsigned int max_tries, unsigned int flags)
160
{
161
  int ret;
162
  unsigned int retries = 1, diff;
163
  struct timespec now;
164
165
  if (data_size > MAX_HEARTBEAT_LENGTH)
166
    return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
167
168
  if (gnutls_heartbeat_allowed
169
      (session, GNUTLS_HB_LOCAL_ALLOWED_TO_SEND) == 0)
170
    return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
171
172
  /* resume previous call if interrupted */
173
  if (session->internals.record_send_buffer.byte_length > 0 &&
174
      session->internals.record_send_buffer.head != NULL &&
175
      session->internals.record_send_buffer.head->type ==
176
      GNUTLS_HEARTBEAT)
177
    return _gnutls_io_write_flush(session);
178
179
  switch (session->internals.hb_state) {
180
  case SHB_SEND1:
181
    if (data_size > DEFAULT_PADDING_SIZE)
182
      data_size -= DEFAULT_PADDING_SIZE;
183
    else
184
      data_size = 0;
185
186
    _gnutls_buffer_reset(&session->internals.hb_local_data);
187
188
    ret =
189
        _gnutls_buffer_resize(&session->internals.hb_local_data,
190
            data_size);
191
    if (ret < 0)
192
      return gnutls_assert_val(ret);
193
194
    ret =
195
        gnutls_rnd(GNUTLS_RND_NONCE,
196
             session->internals.hb_local_data.data,
197
             data_size);
198
    if (ret < 0)
199
      return gnutls_assert_val(ret);
200
201
    gnutls_gettime(&session->internals.hb_ping_start);
202
    session->internals.hb_local_data.length = data_size;
203
    session->internals.hb_state = SHB_SEND2;
204
205
    FALLTHROUGH;
206
  case SHB_SEND2:
207
    session->internals.hb_actual_retrans_timeout_ms =
208
        session->internals.hb_retrans_timeout_ms;
209
 retry:
210
    ret =
211
        heartbeat_send_data(session,
212
          session->internals.hb_local_data.data,
213
          session->internals.hb_local_data.length,
214
          HEARTBEAT_REQUEST);
215
    if (ret < 0)
216
      return gnutls_assert_val(ret);
217
218
    gnutls_gettime(&session->internals.hb_ping_sent);
219
220
    if (!(flags & GNUTLS_HEARTBEAT_WAIT)) {
221
      session->internals.hb_state = SHB_SEND1;
222
      break;
223
    }
224
225
    session->internals.hb_state = SHB_RECV;
226
    FALLTHROUGH;
227
228
  case SHB_RECV:
229
    ret =
230
        _gnutls_recv_int(session, GNUTLS_HEARTBEAT,
231
             NULL, 0, NULL,
232
             session->internals.
233
             hb_actual_retrans_timeout_ms);
234
    if (ret == GNUTLS_E_HEARTBEAT_PONG_RECEIVED) {
235
      session->internals.hb_state = SHB_SEND1;
236
      break;
237
    } else if (ret == GNUTLS_E_TIMEDOUT) {
238
      retries++;
239
      if (max_tries > 0 && retries > max_tries) {
240
        session->internals.hb_state = SHB_SEND1;
241
        return gnutls_assert_val(ret);
242
      }
243
244
      gnutls_gettime(&now);
245
      diff =
246
          timespec_sub_ms(&now,
247
              &session->internals.hb_ping_start);
248
      if (diff > session->internals.hb_total_timeout_ms) {
249
        session->internals.hb_state = SHB_SEND1;
250
        return gnutls_assert_val(GNUTLS_E_TIMEDOUT);
251
      }
252
253
      session->internals.hb_actual_retrans_timeout_ms *= 2;
254
      session->internals.hb_actual_retrans_timeout_ms %=
255
          MAX_DTLS_TIMEOUT;
256
257
      session->internals.hb_state = SHB_SEND2;
258
      goto retry;
259
    } else if (ret < 0) {
260
      session->internals.hb_state = SHB_SEND1;
261
      return gnutls_assert_val(ret);
262
    }
263
  }
264
265
  return 0;
266
}
267
268
/**
269
 * gnutls_heartbeat_pong:
270
 * @session: is a #gnutls_session_t type.
271
 * @flags: should be zero
272
 *
273
 * This function replies to a ping by sending a pong to the peer.
274
 *
275
 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
276
 *
277
 * Since: 3.1.2
278
 **/
279
int gnutls_heartbeat_pong(gnutls_session_t session, unsigned int flags)
280
{
281
  int ret;
282
283
  if (session->internals.record_send_buffer.byte_length > 0 &&
284
      session->internals.record_send_buffer.head != NULL &&
285
      session->internals.record_send_buffer.head->type ==
286
      GNUTLS_HEARTBEAT)
287
    return _gnutls_io_write_flush(session);
288
289
  if (session->internals.hb_remote_data.length == 0)
290
    return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
291
292
  ret =
293
      heartbeat_send_data(session,
294
        session->internals.hb_remote_data.data,
295
        session->internals.hb_remote_data.length,
296
        HEARTBEAT_RESPONSE);
297
298
  _gnutls_buffer_reset(&session->internals.hb_remote_data);
299
300
  if (ret < 0)
301
    return gnutls_assert_val(ret);
302
303
  return 0;
304
}
305
306
/*
307
 * Processes a heartbeat message. 
308
 */
309
int _gnutls_heartbeat_handle(gnutls_session_t session, mbuffer_st * bufel)
310
{
311
  int ret;
312
  unsigned type;
313
  unsigned pos;
314
  uint8_t *msg = _mbuffer_get_udata_ptr(bufel);
315
  size_t hb_len, len = _mbuffer_get_udata_size(bufel);
316
317
  if (gnutls_heartbeat_allowed
318
      (session, GNUTLS_HB_PEER_ALLOWED_TO_SEND) == 0)
319
    return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
320
321
  if (len < 3 + DEFAULT_PADDING_SIZE)
322
    return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
323
324
  pos = 0;
325
  type = msg[pos++];
326
327
  hb_len = _gnutls_read_uint16(&msg[pos]);
328
  if (hb_len > len - 3 - DEFAULT_PADDING_SIZE)
329
    return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
330
331
  pos += 2;
332
333
  switch (type) {
334
  case HEARTBEAT_REQUEST:
335
    _gnutls_buffer_reset(&session->internals.hb_remote_data);
336
337
    ret =
338
        _gnutls_buffer_resize(&session->internals.hb_remote_data,
339
            hb_len);
340
    if (ret < 0)
341
      return gnutls_assert_val(ret);
342
343
    if (hb_len > 0)
344
      memcpy(session->internals.hb_remote_data.data,
345
             &msg[pos], hb_len);
346
    session->internals.hb_remote_data.length = hb_len;
347
348
    return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PING_RECEIVED);
349
350
  case HEARTBEAT_RESPONSE:
351
352
    if (hb_len != session->internals.hb_local_data.length)
353
      return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
354
355
    if (hb_len > 0 &&
356
        memcmp(&msg[pos],
357
         session->internals.hb_local_data.data,
358
         hb_len) != 0) {
359
      if (IS_DTLS(session))
360
        return gnutls_assert_val(GNUTLS_E_AGAIN); /* ignore it */
361
      else
362
        return
363
            gnutls_assert_val
364
            (GNUTLS_E_UNEXPECTED_PACKET);
365
    }
366
367
    _gnutls_buffer_reset(&session->internals.hb_local_data);
368
369
    return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PONG_RECEIVED);
370
  default:
371
    _gnutls_record_log
372
        ("REC[%p]: HB: received unknown type %u\n", session, type);
373
    return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
374
  }
375
}
376
377
/**
378
 * gnutls_heartbeat_get_timeout:
379
 * @session: is a #gnutls_session_t type.
380
 *
381
 * This function will return the milliseconds remaining
382
 * for a retransmission of the previously sent ping
383
 * message. This function is useful when ping is used in
384
 * non-blocking mode, to estimate when to call gnutls_heartbeat_ping()
385
 * if no packets have been received.
386
 *
387
 * Returns: the remaining time in milliseconds.
388
 *
389
 * Since: 3.1.2
390
 **/
391
unsigned int gnutls_heartbeat_get_timeout(gnutls_session_t session)
392
{
393
  struct timespec now;
394
  unsigned int diff;
395
396
  gnutls_gettime(&now);
397
  diff = timespec_sub_ms(&now, &session->internals.hb_ping_sent);
398
  if (diff >= session->internals.hb_actual_retrans_timeout_ms)
399
    return 0;
400
  else
401
    return session->internals.hb_actual_retrans_timeout_ms - diff;
402
}
403
404
/**
405
 * gnutls_heartbeat_set_timeouts:
406
 * @session: is a #gnutls_session_t type.
407
 * @retrans_timeout: The time at which a retransmission will occur in milliseconds
408
 * @total_timeout: The time at which the connection will be aborted, in milliseconds.
409
 *
410
 * This function will override the timeouts for the DTLS heartbeat
411
 * protocol. The retransmission timeout is the time after which a
412
 * message from the peer is not received, the previous request will
413
 * be retransmitted. The total timeout is the time after which the
414
 * handshake will be aborted with %GNUTLS_E_TIMEDOUT.
415
 *
416
 * Since: 3.1.2
417
 **/
418
void gnutls_heartbeat_set_timeouts(gnutls_session_t session,
419
           unsigned int retrans_timeout,
420
           unsigned int total_timeout)
421
{
422
  session->internals.hb_retrans_timeout_ms = retrans_timeout;
423
  session->internals.hb_total_timeout_ms = total_timeout;
424
}
425
426
static int
427
_gnutls_heartbeat_recv_params(gnutls_session_t session,
428
            const uint8_t * data, size_t _data_size)
429
{
430
  unsigned policy;
431
  gnutls_ext_priv_data_t epriv;
432
433
  if (_gnutls_hello_ext_get_priv
434
      (session, GNUTLS_EXTENSION_HEARTBEAT, &epriv) < 0) {
435
    if (session->security_parameters.entity == GNUTLS_CLIENT)
436
      return
437
          gnutls_assert_val
438
          (GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
439
    return 0; /* Not enabled */
440
  }
441
442
  if (_data_size == 0)
443
    return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
444
445
  policy = (intptr_t) epriv;
446
447
  if (data[0] == 1)
448
    policy |= LOCAL_ALLOWED_TO_SEND;
449
  else if (data[0] == 2)
450
    policy |= LOCAL_NOT_ALLOWED_TO_SEND;
451
  else
452
    return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
453
454
  epriv = (void *)(intptr_t) policy;
455
  _gnutls_hello_ext_set_priv(session, GNUTLS_EXTENSION_HEARTBEAT, epriv);
456
457
  return 0;
458
}
459
460
static int
461
_gnutls_heartbeat_send_params(gnutls_session_t session,
462
            gnutls_buffer_st * extdata)
463
{
464
  gnutls_ext_priv_data_t epriv;
465
  uint8_t p;
466
467
  if (_gnutls_hello_ext_get_priv
468
      (session, GNUTLS_EXTENSION_HEARTBEAT, &epriv) < 0)
469
    return 0; /* nothing to send - not enabled */
470
471
  if (((intptr_t) epriv) & GNUTLS_HB_PEER_ALLOWED_TO_SEND)
472
    p = 1;
473
  else      /*if (epriv.num & GNUTLS_HB_PEER_NOT_ALLOWED_TO_SEND) */
474
    p = 2;
475
476
  if (_gnutls_buffer_append_data(extdata, &p, 1) < 0)
477
    return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
478
479
  return 1;
480
}
481
482
static int
483
_gnutls_heartbeat_pack(gnutls_ext_priv_data_t epriv, gnutls_buffer_st * ps)
484
{
485
  int ret;
486
487
  BUFFER_APPEND_NUM(ps, (intptr_t) epriv);
488
489
  return 0;
490
491
}
492
493
static int
494
_gnutls_heartbeat_unpack(gnutls_buffer_st * ps, gnutls_ext_priv_data_t * _priv)
495
{
496
  gnutls_ext_priv_data_t epriv;
497
  int ret;
498
499
  BUFFER_POP_CAST_NUM(ps, epriv);
500
501
  *_priv = epriv;
502
503
  ret = 0;
504
 error:
505
  return ret;
506
}
507
508
const hello_ext_entry_st ext_mod_heartbeat = {
509
  .name = "Heartbeat",
510
  .tls_id = 15,
511
  .gid = GNUTLS_EXTENSION_HEARTBEAT,
512
  .client_parse_point = GNUTLS_EXT_TLS,
513
  .server_parse_point = GNUTLS_EXT_TLS,
514
  .validity =
515
      GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS |
516
      GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE |
517
      GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO,
518
  .recv_func = _gnutls_heartbeat_recv_params,
519
  .send_func = _gnutls_heartbeat_send_params,
520
  .pack_func = _gnutls_heartbeat_pack,
521
  .unpack_func = _gnutls_heartbeat_unpack,
522
  .deinit_func = NULL,
523
  .cannot_be_overriden = 1
524
};
525
526
#else
527
void gnutls_heartbeat_enable(gnutls_session_t session, unsigned int type)
528
0
{
529
0
}
530
531
unsigned gnutls_heartbeat_allowed(gnutls_session_t session, unsigned int type)
532
0
{
533
0
  return 0;
534
0
}
535
536
int
537
gnutls_heartbeat_ping(gnutls_session_t session, size_t data_size,
538
          unsigned int max_tries, unsigned int flags)
539
0
{
540
0
  return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
541
0
}
542
543
int gnutls_heartbeat_pong(gnutls_session_t session, unsigned int flags)
544
0
{
545
0
  return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
546
0
}
547
548
unsigned int gnutls_heartbeat_get_timeout(gnutls_session_t session)
549
0
{
550
0
  return 0;
551
0
}
552
553
void gnutls_heartbeat_set_timeouts(gnutls_session_t session,
554
           unsigned int retrans_timeout,
555
           unsigned int total_timeout)
556
0
{
557
0
  return;
558
0
}
559
#endif