From 6c4c4baca8e4d3311501b2c8c2d32a0ccbe881ad Mon Sep 17 00:00:00 2001
From: mancha <mancha1@hush.com>
Date: Sun, 29 Sep 2013
Subject: CVE-2013-1619 and CVE-2013-2116 [GNUTLS-SA-2013-1,GNUTLS-SA-2013-2]
Fix to avoid a timing attack in TLS CBC record parsing (aka Lucky 13).
For background, see http://www.isg.rhul.ac.uk/tls/Lucky13.html
The fix for CVE-2013-2116 is folded into this patch since it addresses
a problem introduced by the fix for CVE-2013-1619.
This is a backport adaptation for use with GnuTLS 2.10.5.
Relevant upstream commits:
--------------------------
https://gitorious.org/gnutls/gnutls/commit/458c67cf98740e
https://gitorious.org/gnutls/gnutls/commit/93b7fcfa3297a9
---
lib/gnutls_hash_int.h | 21 +++++++++++
lib/gnutls_cipher.c | 90 ++++++++++++++++++++++++++++++++----------------
2 files changed, 81 insertions(+), 30 deletions(-)
--- a/lib/gnutls_cipher.c
+++ b/lib/gnutls_cipher.c
@@ -447,6 +447,49 @@ _gnutls_compressed2ciphertext (gnutls_se
return length;
}
+static void dummy_wait(gnutls_session_t session, gnutls_datum_t* plaintext,
+ unsigned pad_failed, unsigned int pad, unsigned total, int ver)
+{
+ /* this hack is only needed on CBC ciphers */
+ if (_gnutls_cipher_is_block (session->security_parameters.read_bulk_cipher_algorithm) == CIPHER_BLOCK)
+ {
+ uint8_t MAC[MAX_HASH_SIZE];
+ unsigned len;
+ digest_hd_st td;
+ int ret;
+
+ ret = mac_init (&td, session->security_parameters.read_mac_algorithm,
+ session->connection_state.read_mac_secret.data,
+ session->connection_state.read_mac_secret.size, ver);
+
+ if (ret < 0)
+ return;
+
+ /* force an additional hash compression function evaluation to prevent timing
+ * attacks that distinguish between wrong-mac + correct pad, from wrong-mac + incorrect pad.
+ */
+ if (pad_failed == 0 && pad > 0)
+ {
+ len = _gnutls_get_hash_block_len(session->security_parameters.read_mac_algorithm);
+ if (len > 0)
+ {
+ /* This is really specific to the current hash functions.
+ * It should be removed once a protocol fix is in place.
+ */
+ if ((pad+total) % len > len-9 && total % len <= len-9)
+ {
+ if (len < plaintext->size)
+ mac_hash (&td, plaintext->data, len, ver);
+ else
+ mac_hash (&td, plaintext->data, plaintext->size, ver);
+ }
+ }
+ }
+
+ mac_deinit (&td, MAC, ver);
+ }
+}
+
/* Deciphers the ciphertext packet, and puts the result to compress_data, of compress_size.
* Returns the actual compressed packet size.
*/
@@ -458,12 +501,12 @@ _gnutls_ciphertext2compressed (gnutls_se
{
uint8_t MAC[MAX_HASH_SIZE];
uint16_t c_length;
- uint8_t pad;
+ unsigned int pad = 0;
int length;
uint16_t blocksize;
int ret, i, pad_failed = 0;
opaque preamble[PREAMBLE_SIZE];
- int preamble_size;
+ int preamble_size = 0;
int ver = gnutls_protocol_get_version (session);
int hash_size =
_gnutls_hash_get_algo_len (session->
@@ -522,31 +565,23 @@ _gnutls_ciphertext2compressed (gnutls_se
gnutls_assert ();
return GNUTLS_E_DECRYPTION_FAILED;
}
- pad = ciphertext.data[ciphertext.size - 1] + 1; /* pad */
+ pad = ciphertext.data[ciphertext.size - 1]; /* pad */
- if ((int) pad > (int) ciphertext.size - hash_size)
- {
- gnutls_assert ();
- _gnutls_record_log
- ("REC[%p]: Short record length %d > %d - %d (under attack?)\n",
- session, pad, ciphertext.size, hash_size);
- /* We do not fail here. We check below for the
- * the pad_failed. If zero means success.
- */
- pad_failed = GNUTLS_E_DECRYPTION_FAILED;
- }
-
- length = ciphertext.size - hash_size - pad;
-
- /* Check the pading bytes (TLS 1.x)
+ /* Check the pading bytes (TLS 1.x).
+ * Note that we access all 256 bytes of ciphertext for padding check
+ * because there is a timing channel in that memory access (in certain CPUs).
*/
if (_gnutls_version_has_variable_padding (ver) && pad_failed == 0)
for (i = 2; i < pad; i++)
{
- if (ciphertext.data[ciphertext.size - i] !=
- ciphertext.data[ciphertext.size - 1])
+ if (ciphertext.data[ciphertext.size - i] != pad)
pad_failed = GNUTLS_E_DECRYPTION_FAILED;
}
+
+ if (pad_failed)
+ pad = 0;
+ length = ciphertext.size - hash_size - pad - 1;
+
break;
default:
gnutls_assert ();
@@ -585,19 +620,14 @@ _gnutls_ciphertext2compressed (gnutls_se
mac_deinit (&td, MAC, ver);
}
- /* This one was introduced to avoid a timing attack against the TLS
- * 1.0 protocol.
- */
- if (pad_failed != 0)
- {
- gnutls_assert ();
- return pad_failed;
- }
-
/* HMAC was not the same.
*/
- if (memcmp (MAC, &ciphertext.data[length], hash_size) != 0)
+ if (memcmp (MAC, &ciphertext.data[length], hash_size) != 0 || pad_failed != 0)
{
+ gnutls_datum_t compressed = {compress_data, compress_size};
+ /* HMAC was not the same. */
+ dummy_wait(session, &compressed, pad_failed, pad, length+preamble_size, ver);
+
gnutls_assert ();
return GNUTLS_E_DECRYPTION_FAILED;
}
--- a/lib/gnutls_hash_int.h
+++ b/lib/gnutls_hash_int.h
@@ -98,4 +98,25 @@ void _gnutls_mac_deinit_ssl3_handshake (
int _gnutls_hash_copy (digest_hd_st * dst_handle, digest_hd_st * src_handle);
+/* We shouldn't need to know that, but a work-around in decoding
+ * TLS record padding requires that.
+ */
+inline static size_t
+_gnutls_get_hash_block_len (gnutls_digest_algorithm_t algo)
+{
+ switch (algo)
+ {
+ case GNUTLS_DIG_MD5:
+ case GNUTLS_DIG_SHA1:
+ case GNUTLS_DIG_RMD160:
+ case GNUTLS_DIG_SHA256:
+ case GNUTLS_DIG_SHA384:
+ case GNUTLS_DIG_SHA512:
+ case GNUTLS_DIG_SHA224:
+ return 64;
+ default:
+ return 0;
+ }
+}
+
#endif /* GNUTLS_HASH_INT_H */