[tor-dev] memcmp() & co. timing info disclosures?
Nick Mathewson
nickm at freehaven.net
Sat May 7 03:14:58 UTC 2011
On Fri, May 6, 2011 at 10:40 PM, Marsh Ray <marsh at extendedsubset.com> wrote:
[...]
>> but the problem in general is worrisome and we
>> should indeed replace (nearly) all of our memcmps with
>> data-independent variants.
>
> Maybe some of the str*cmps too? I grep 681 of them.
Yeah; most of these are not parsing anything secret, but we should
audit all of them to look for worrisome cases. It's even less clear
what data-independence means for strcmp: possibly it means that the
run-time should depend only on the length of the shorter string, or
the longer string, or on one of the arguments arbitrarily designated
as the "non-secret" string, or such. All of these could be reasonable
under some circumstances, but we should figure out what we actually
mean.
> We should also look for other cases where any data or padding might be
> checked, decompressed, or otherwise operated on without being as obvious as
> calling memcmp. Lots of error conditions can disclose timing information.
Yeah. This is going to be a reasonably big job.
>> (Pedantic nit-pick: we should be saying "data-independent," not
>> "constant-time." We want a memcmp(a,b,c) that takes the same number
>> of cycles for a given value of c no matter what a and b are. That's
>> data-independence. A constant-time version would be one that took the
>> same number of cycles no matter what c is.)
>
> That's a good point. In most of the code I glanced at, the length was fixed
> at compile-time. I suppose a proper "constant-time" function would have to
> take as much time as a 2GB comparison (on 32) :-).
>
>> int mem_neq(const void *m1, const void *m2, size_t n)
>> {
>> const uint8_t *b1 = m1, *b2 = m2;
>> uint8_t diff = 0;
>> while (n--)
>> diff |= *b1++ ^ *b2++;
>> return diff != 0;
>> }
>> #define mem_eq(m1, m2, n) (!mem_neq((m1), (m2),(n)))
>
> Looks good to me.
>
> What if n is 0? Is 'equals' or 'neq' a more conservative default ?
If n is 0, then "equals" is the answer: all empty strings are equal, right? :)
> Would it make sense to die in a well-defined way if m1 or m2 is NULL?
Adding a tor_assert(m1 && m2) would be fine.
> Also, if the MSB of n is set it's an invalid condition, the kind that could
> result from a conversion from a signed value.
Adding a tor_assert(n < SIZE_T_CEILING) is our usual way of handling this.
Also, as I said on the bug, doing a memcmp in constant time is harder
than doing eq/neq. I *think* that all of the cases where we care
about memcmp returning a tristate -1/0/1 result, we don't need
data-independence... but in case we *do* need one, we'll have to do
some malarkey like
int memcmp(const void *m1, const void *m2, size_t n)
{
/*XXX I don't know if this is even right; I haven't tested it at all */
const uint8_t *b1 = m1, *b2 = m2;
int retval = 0;
while (n--) {
const uint8_t v1 = b1[n], v2 = b2[n];
int diff = (int)v1 - (int)v2;
retval = (v1 == v2) * retval + diff;
}
return retval;
}
which frankly makes me sad. I bet there's a better way to go.
--
Nick
More information about the tor-dev
mailing list