Source:
Info:
before 3.1.7 does not properly consider timing side-channel attacks on a
noncompliant MAC check operation during the processing of malformed CBC
padding, which allows remote attackers to conduct distinguishing attacks and
plaintext-recovery attacks via statistical analysis of timing data for crafted
packets, a related issue to CVE-2013-0169.
Status:
Need to determine if this patch has been sent upstream.
--- gnutls-2.8.6/lib/gnutls_cipher.c.orig 2013-05-21 14:38:08.865598248 +0530
+++ gnutls-2.8.6/lib/gnutls_cipher.c 2013-05-21 15:51:24.878786918 +0530
@@ -418,6 +418,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)
+ _gnutls_hmac (&td, plaintext->data, len);
+ else
+ _gnutls_hmac (&td, plaintext->data, plaintext->size);
+ }
+ }
+ }
+
+ 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.
*/
@@ -429,11 +472,12 @@ _gnutls_ciphertext2compressed (gnutls_se
{
uint8_t MAC[MAX_HASH_SIZE];
uint16_t c_length;
- uint8_t pad;
+ unsigned int pad = 0;
int length;
digest_hd_st td;
uint16_t blocksize;
int ret, i, pad_failed = 0;
+ int preamble_size = 0;
uint8_t major, minor;
gnutls_protocol_t ver;
int hash_size =
@@ -509,31 +553,23 @@ _gnutls_ciphertext2compressed (gnutls_se
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 (ver >= GNUTLS_TLS1 && pad_failed == 0)
- for (i = 2; i < pad; i++)
+ 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 ();
@@ -552,14 +588,19 @@ _gnutls_ciphertext2compressed (gnutls_se
_gnutls_hmac (&td,
UINT64DATA (session->connection_state.
read_sequence_number), 8);
+ preamble_size += 8;
_gnutls_hmac (&td, &type, 1);
+ preamble_size++;
if (ver >= GNUTLS_TLS1)
{ /* TLS 1.x */
_gnutls_hmac (&td, &major, 1);
+ preamble_size++;
_gnutls_hmac (&td, &minor, 1);
+ preamble_size++;
}
_gnutls_hmac (&td, &c_length, 2);
+ preamble_size += 2;
if (length > 0)
_gnutls_hmac (&td, ciphertext.data, length);
@@ -567,21 +608,19 @@ _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)
- 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;
}
- /* copy the decrypted stuff to compress_data.
+ /* copy the decrypted stuff to compressed_data.
*/
if (compress_size < length)
{
--- gnutls-2.8.6/lib/gnutls_hash_int.h.orig 2013-05-21 15:51:50.195114457 +0530
+++ gnutls-2.8.6/lib/gnutls_hash_int.h 2013-05-21 15:53:44.212046617 +0530
@@ -92,4 +92,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 */