Exploit summary
OpenVPN 2.3.0 and earlier running in UDP mode are subject to chosen
ciphertext injection due to a non-constant-time HMAC comparison
function. Plaintext recovery may be possible using a padding oracle
attack on the CBC mode cipher implementation of the crypto library,
optimistically at a rate of about one character per 3 hours. PolarSSL
seems vulnerable to such an attack; the vulnerability of OpenSSL has not
been verified or tested.
Severity
OpenVPN servers are typically configured to silently drop packets with
the wrong HMAC. For this reason measuring the processing time of the
packets is not trivial without a MITM position. In practice, the attack
likely needs some target-specific information to be effective.
The severity of this vulnerability can be considered low. Only if
OpenVPN is configured to use a null-cipher, arbitrary plain-text can be
injected which can completely open up this attack vector.
Affected versions
OpenVPN 2.3.0 and earlier are vulnerable. A fix (commit
f375aa67cc) is included in OpenVPN 2.3.1 and later.
src/openvpn/buffer.h
@@ -668,6 +668,10 @@ bool openvpn_snprintf(char *str, size_t size, const char *format, ...)
}
}
+/**
+ * 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)
{
@@ -676,6 +680,10 @@ bool openvpn_snprintf(char *str, size_t size, const char *format, ...)
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)
{
src/openvpn/crypto.c
@@ -65,6 +65,24 @@
#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,
@@ -244,7 +262,7 @@
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));