Add a data-independent variant of memcmp and a d-i memeq function.

The tor_memcmp code is by Robert Ransom, and the tor_memeq code is
by me.  Both incorporate some ideas from DJB's stuff.
This commit is contained in:
Nick Mathewson 2011-05-09 18:39:23 -04:00
parent 8a36f21251
commit 4b19730c82
6 changed files with 243 additions and 2 deletions

7
changes/bug3122_memcmp Normal file
View File

@ -0,0 +1,7 @@
o Security fixes
- Replace all potentially sensitive memory comparison operations
with versions whose runtime does not depend on the data being
compared. This will help resist a class of attacks where an
adversary can use variations in timing information to learn
sensitive data. Fix for one case of bug 3122. (Safe memcmp
implementation by Robert Ransom based partially on code by DJB.)

View File

@ -626,6 +626,24 @@ if test "$tor_cv_twos_complement" != no ; then
[Define to 1 iff we represent negative integers with two's complement])
fi
# What does shifting a negative value do?
AC_CACHE_CHECK([whether right-shift on negative values does sign-extension], tor_cv_sign_extend,
[AC_RUN_IFELSE([AC_LANG_SOURCE(
[[int main () { int okay = (-60 >> 8) == -1; return okay ? 0 : 1; }]])],
[tor_cv_sign_extend=yes],
[tor_cv_sign_extend=no],
[tor_cv_sign_extend=cross])])
if test "$tor_cv_sign_extend" = cross ; then
# Cross-compiling; let's hope that the target isn't raving mad.
AC_MSG_NOTICE([Cross-compiling: we'll assume that right-shifting negative integers causes sign-extension])
fi
if test "$tor_cv_sign_extend" != no ; then
AC_DEFINE([RSHIFT_DOES_SIGN_EXTEND], 1,
[Define to 1 iff right-shifting a negative value performs sign-extension])
fi
# Whether we should use the dmalloc memory allocation debugging library.
AC_MSG_CHECKING(whether to use dmalloc (debug memory allocation library))
AC_ARG_WITH(dmalloc,

View File

@ -10,7 +10,7 @@ libor_extra_source=
endif
libor_a_SOURCES = address.c log.c util.c compat.c container.c mempool.c \
memarea.c $(libor_extra_source)
memarea.c di_ops.c $(libor_extra_source)
libor_crypto_a_SOURCES = crypto.c aes.c tortls.c torgzip.c
noinst_HEADERS = address.h log.h crypto.h test.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc
noinst_HEADERS = address.h log.h crypto.h test.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h di_ops.h ciphers.inc

133
src/common/di_ops.c Normal file
View File

@ -0,0 +1,133 @@
/* Copyright (c) 2011, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file di_ops.c
* \brief Functions for data-independent operations
**/
#include "orconfig.h"
#include "di_ops.h"
/**
* Timing-safe version of memcmp. As memcmp, compare the <b>sz</b> bytes
* at <b>a</b> with the <b>sz</b> bytes at <b>, and returns less than 0 if the
* bytes at <b>a</b> lexically precede those at <b>b</b>, 0 if the byte ranges
* are equal, and greater than zero if the bytes at <b>a</b> lexically follow
* those at <b>.
*
* This implementation differs from memcmp in that its timing behavior is not
* data-dependent: it should return in the same amount of time regardless of
* the contents of <b>a</b> and <b>b</b>.
*/
int
tor_memcmp(const void *a, const void *b, size_t len)
{
const uint8_t *x = a;
const uint8_t *y = b;
size_t i = len;
int retval = 0;
/* This loop goes from the end of the arrays to the start. At the
* start of every iteration, before we decrement i, we have set
* "retval" equal to the result of memcmp(a+i,b+i,len-i). During the
* loop, we update retval by leaving it unchanged if x[i]==y[i] and
* setting it to x[i]-y[i] if x[i]!= y[i].
*
* The following assumes we are on a system with two's-complement
* arithmetic. We check for this at configure-time with the check
* that sets USING_TWOS_COMPLEMENT. If we aren't two's complement, then
* torint.h will stop compilation with an error.
*/
while (i--) {
int v1 = x[i];
int v2 = y[i];
int equal_p = v1 ^ v2;
/* The following sets bits 8 and above of equal_p to 'equal_p ==
* 0', and thus to v1 == v2. (To see this, note that if v1 ==
* v2, then v1^v2 == equal_p == 0, so equal_p-1 == -1, which is the
* same as ~0 on a two's-complement machine. Then note that if
* v1 != v2, then 0 < v1 ^ v2 < 256, so 0 <= equal_p - 1 < 255.)
*/
--equal_p;
equal_p >>= 8;
/* Thanks to (sign-preserving) arithmetic shift, equal_p is now
* equal to -(v1 == v2), which is exactly what we need below.
* (Since we're assuming two's-complement arithmetic, -1 is the
* same as ~0 (all bits set).)
*
* (The result of an arithmetic shift on a negative value is
* actually implementation-defined in standard C. So how do we
* get away with assuming it? Easy. We check.) */
#if ((-60 >> 8) != -1)
#error "According to cpp, right-shift doesn't perform sign-extension."
#endif
#ifndef RSHIFT_DOES_SIGN_EXTEND
#error "According to configure, right-shift doesn't perform sign-extension."
#endif
/* If v1 == v2, equal_p is ~0, so this will leave retval
* unchanged; otherwise, equal_p is 0, so this will zero it. */
retval &= equal_p;
/* If v1 == v2, then this adds 0, and leaves retval unchanged.
* Otherwise, we just zeroed retval, so this sets it to v1 - v2. */
retval += (v1 - v2);
/* There. Now retval is equal to its previous value if v1 == v2, and
* equal to v1 - v2 if v1 != v2. */
}
return retval;
}
/**
* Timing-safe memory comparison. Return true if the <b>sz</b> bytes at
* <b>a</b> are the same as the <b>sz</b> bytes at <b>, and 0 otherwise.
*
* This implementation differs from !memcmp(a,b,sz) in that its timing
* behavior is not data-dependent: it should return in the same amount of time
* regardless of the contents of <b>a</b> and <b>b</b>. It differs from
* !tor_memcmp(a,b,sz) by being faster.
*/
int
tor_memeq(const void *a, const void *b, size_t sz)
{
/* Treat a and b as byte ranges. */
const uint8_t *ba = a, *bb = b;
uint32_t any_difference = 0;
while (sz--) {
/* Set byte_diff to all of those bits that are different in *ba and *bb,
* and advance both ba and bb. */
const uint8_t byte_diff = *ba++ ^ *bb++;
/* Set bits in any_difference if they are set in byte_diff. */
any_difference |= byte_diff;
}
/* Now any_difference is 0 if there are no bits different between
* a and b, and is nonzero if there are bits different between a
* and b. Now for paranoia's sake, let's convert it to 0 or 1.
*
* (If we say "!any_difference", the compiler might get smart enough
* to optimize-out our data-independence stuff above.)
*
* To unpack:
*
* If any_difference == 0:
* any_difference - 1 == ~0
* (any_difference - 1) >> 8 == 0x00ffffff
* 1 & ((any_difference - 1) >> 8) == 1
*
* If any_difference != 0:
* 0 < any_difference < 256, so
* 0 < any_difference - 1 < 255
* (any_difference - 1) >> 8 == 0
* 1 & ((any_difference - 1) >> 8) == 0
*/
return 1 & ((any_difference - 1) >> 8);
}

28
src/common/di_ops.h Normal file
View File

@ -0,0 +1,28 @@
/* Copyright (c) 2003-2004, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2011, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file di_ops.h
* \brief Headers for di_ops.c
**/
#ifndef TOR_DI_OPS_H
#define TOR_DI_OPS_H
#include "orconfig.h"
#include "torint.h"
int tor_memcmp(const void *a, const void *b, size_t sz);
int tor_memeq(const void *a, const void *b, size_t sz);
#define tor_memneq(a,b,sz) (!tor_memeq((a),(b),(sz)))
/** Alias for the platform's memcmp() function. This function is
* <em>not</em> data-independent: we define this alias so that we can
* mark cases where we are deliberately using a data-dependent memcmp()
* implementation.
*/
#define fast_memcmp(a,b,c) (memcmp((a),(b),(c)))
#endif

View File

@ -43,6 +43,7 @@ const char tor_svn_revision[] = "";
#include "torgzip.h"
#include "mempool.h"
#include "memarea.h"
#include "di_ops.h"
#ifdef USE_DMALLOC
#include <dmalloc.h>
@ -1331,6 +1332,59 @@ test_util(void)
;
}
static void
test_util_di_ops(void)
{
#define LT -1
#define GT 1
#define EQ 0
const struct {
const char *a; int want_sign; const char *b;
} examples[] = {
{ "Foo", EQ, "Foo" },
{ "foo", GT, "bar", },
{ "foobar", EQ ,"foobar" },
{ "foobar", LT, "foobaw" },
{ "foobar", GT, "f00bar" },
{ "foobar", GT, "boobar" },
{ "", EQ, "" },
{ NULL, 0, NULL },
};
int i;
for (i = 0; examples[i].a; ++i) {
size_t len = strlen(examples[i].a);
int eq1, eq2, neq1, neq2, cmp1, cmp2;
test_eq(len, strlen(examples[i].b));
/* We do all of the operations, with operands in both orders. */
eq1 = tor_memeq(examples[i].a, examples[i].b, len);
eq2 = tor_memeq(examples[i].b, examples[i].a, len);
neq1 = tor_memneq(examples[i].a, examples[i].b, len);
neq2 = tor_memneq(examples[i].b, examples[i].a, len);
cmp1 = tor_memcmp(examples[i].a, examples[i].b, len);
cmp2 = tor_memcmp(examples[i].b, examples[i].a, len);
/* Check for correctness of cmp1 */
if (cmp1 < 0 && examples[i].want_sign != LT)
test_fail();
else if (cmp1 > 0 && examples[i].want_sign != GT)
test_fail();
else if (cmp1 == 0 && examples[i].want_sign != EQ)
test_fail();
/* Check for consistency of everything else with cmp1 */
test_eq(eq1, eq2);
test_eq(neq1, neq2);
test_eq(cmp1, -cmp2);
test_eq(eq1, cmp1 == 0);
test_eq(neq1, !eq1);
}
done:
;
}
/** Helper: assert that IPv6 addresses <b>a</b> and <b>b</b> are the same. On
* failure, reports an error, describing the addresses as <b>e1</b> and
* <b>e2</b>, and reporting the line number as <b>line</b>. */
@ -4753,6 +4807,7 @@ static struct {
SUBENT(crypto, aes_iv),
SUBENT(crypto, base32_decode),
ENT(util),
SUBENT(util, di_ops),
SUBENT(util, ip6_helpers),
SUBENT(util, gzip),
SUBENT(util, datadir),