CVE-2013-2061: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: The openvpn_decrypt function in crypto.c in OpenVPN 2.3.0 and earlier, when running in UDP mode, allows remote attackers to obtain sensitive information via a timing attack involving an HMAC comparison function that does not run in constant time and a padding oracle attack on the CBC mode cipher.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Published: 2013-11-18T02:55Z
Repository: https://github.com/OpenVPN/openvpn
Language: C
Files changed: src/openvpn/buffer.h, src/openvpn/crypto.c
Commit date: 2013-03-22 17:06:04 +0100
Commit message: Use constant time memcmp when comparing HMACs in openvpn_decrypt.

Signed-off-by: Steffan Karger <steffan.karger@fox-it.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Diff: diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
index 7cae733..93efb09 100644
--- a/src/openvpn/buffer.h
+++ b/src/openvpn/buffer.h
@@ -666,18 +666,26 @@ buf_read_u32 (struct buffer *buf, bool *good)
 	*good = true;
       return ntohl (ret);
     }
 }
 
+/**
+ * Compare src buffer contents with match.
+ * *NOT* constant time. Do not use when comparing HMACs.
+ */
 static inline bool
 buf_string_match (const struct buffer *src, const void *match, int size)
 {
   if (size != src->len)
     return false;
   return memcmp (BPTR (src), match, size) == 0;
 }
 
+/**
+ * Compare first size bytes of src buffer contents with match.
+ * *NOT* constant time. Do not use when comparing HMACs.
+ */
 static inline bool
 buf_string_match_head (const struct buffer *src, const void *match, int size)
 {
   if (size < 0 || size > src->len)
     return false;
diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index 405c0aa..d9adf5b 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -63,10 +63,28 @@
  */
 
 #define CRYPT_ERROR(format) \
   do { msg (D_CRYPT_ERRORS, "%s: " format, error_prefix); goto error_exit; } while (false)
 
+/**
+ * As memcmp(), but constant-time.
+ * Returns 0 when data is equal, non-zero otherwise.
+ */
+static int
+memcmp_constant_time (const void *a, const void *b, size_t size) {
+  const uint8_t * a1 = a;
+  const uint8_t * b1 = b;
+  int ret = 0;
+  size_t i;
+
+  for (i = 0; i < size; i++) {
+      ret |= *a1++ ^ *b1++;
+  }
+
+  return ret;
+}
+
 void
 openvpn_encrypt (struct buffer *buf, struct buffer work,
 		 const struct crypto_options *opt,
 		 const struct frame* frame)
 {
@@ -242,11 +260,11 @@ openvpn_decrypt (struct buffer *buf, struct buffer work,
 
 	  hmac_ctx_update (ctx->hmac, BPTR (buf) + hmac_len, BLEN (buf) - hmac_len);
 	  hmac_ctx_final (ctx->hmac, local_hmac);
 
 	  /* Compare locally computed HMAC with packet HMAC */
-	  if (memcmp (local_hmac, BPTR (buf), hmac_len))
+	  if (memcmp_constant_time (local_hmac, BPTR (buf), hmac_len))
 	    CRYPT_ERROR ("packet HMAC authentication failed");
 
 	  ASSERT (buf_advance (buf, hmac_len));
 	}
 

Vulnerable code: 	  if (memcmp (local_hmac, BPTR (buf), hmac_len))
Fixed code: /**
 * Compare src buffer contents with match.
 * *NOT* constant time. Do not use when comparing HMACs.
 */
/**
 * Compare first size bytes of src buffer contents with match.
 * *NOT* constant time. Do not use when comparing HMACs.
 */
/**
 * As memcmp(), but constant-time.
 * Returns 0 when data is equal, non-zero otherwise.
 */
static int
memcmp_constant_time (const void *a, const void *b, size_t size) {
  const uint8_t * a1 = a;
  const uint8_t * b1 = b;
  int ret = 0;
  size_t i;

  for (i = 0; i < size; i++) {
      ret |= *a1++ ^ *b1++;
  }

  return ret;
}

	  if (memcmp_constant_time (local_hmac, BPTR (buf), hmac_len))