From: Miroslav Lichvar Date: Wed, 2 Apr 2025 13:32:05 +0000 (+0200) Subject: util: add function for constant-time memory comparison X-Git-Tag: 4.7-pre1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dab98fa8da2ff7a4637ff2229dfeefb3accdcff4;p=thirdparty%2Fchrony.git util: add function for constant-time memory comparison Add a function to check if two buffers of the same length contain the same data, but do the comparison in a constant time with respect to the returned value to avoid creating a timing side channel, i.e. the time depends only on the buffer length, not on the content. Use the gnutls_memcmp() or nettle_memeql_sec() functions if available, otherwise use the same algorithm as nettle - bitwise ORing XORed data. --- diff --git a/configure b/configure index 91893e2c..eaa41a08 100755 --- a/configure +++ b/configure @@ -888,6 +888,7 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nettle = "1" ]; HASH_OBJ="hash_nettle.o" HASH_LINK="$test_link" MYCPPFLAGS="$MYCPPFLAGS $test_cflags" + add_def HAVE_NETTLE add_def FEAT_SECHASH if test_code 'CMAC in nettle' 'nettle/cmac.h' "$test_cflags" "$test_link" \ @@ -910,6 +911,7 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_gnutls = "1" ]; HASH_OBJ="hash_gnutls.o" HASH_LINK="$test_link" MYCPPFLAGS="$MYCPPFLAGS $test_cflags" + add_def HAVE_GNUTLS add_def FEAT_SECHASH if test_code 'CMAC in gnutls' 'gnutls/crypto.h' "$test_cflags" "$test_link" \ diff --git a/test/unit/util.c b/test/unit/util.c index d52a2684..15bc793c 100644 --- a/test/unit/util.c +++ b/test/unit/util.c @@ -32,8 +32,8 @@ handle_signal(int signal) void test_unit(void) { + char buf[16], buf2[16], *s, *s2, *words[3]; struct timespec ts, ts2, ts3, ts4; - char buf[16], *s, *s2, *words[3]; NTP_int64 ntp_ts, ntp_ts2, ntp_fuzz; NTP_int32 ntp32_ts; struct timeval tv; @@ -797,5 +797,19 @@ test_unit(void) TEST_CHECK(strcmp(words[0], "a") == 0); TEST_CHECK(strcmp(words[1], "b") == 0); + for (i = 0; i < 1000; i++) { + UTI_GetRandomBytes(buf, sizeof (buf)); + memcpy(buf2, buf, sizeof (buf)); + for (j = 0; j < sizeof (buf); j++) + TEST_CHECK(UTI_IsMemoryEqual(buf, buf2, j)); + + for (j = 0; j < 8 * sizeof (buf); j++) { + buf2[j / 8] ^= 1U << j % 8; + TEST_CHECK(!UTI_IsMemoryEqual(buf, buf2, sizeof (buf))); + buf2[j / 8] ^= 1U << j % 8; + TEST_CHECK(UTI_IsMemoryEqual(buf, buf2, sizeof (buf))); + } + } + HSH_Finalise(); } diff --git a/util.c b/util.c index a4c8288b..b278da69 100644 --- a/util.c +++ b/util.c @@ -29,6 +29,12 @@ #include "sysincl.h" +#if defined(HAVE_NETTLE) +#include +#elif defined(HAVE_GNUTLS) +#include +#endif + #include "logging.h" #include "memory.h" #include "util.h" @@ -1648,3 +1654,22 @@ UTI_SplitString(char *string, char **words, int max_saved_words) return i; } + +/* ================================================== */ + +int +UTI_IsMemoryEqual(const void *s1, const void *s2, unsigned int len) +{ +#if defined(HAVE_NETTLE) + return nettle_memeql_sec(s1, s2, len); +#elif defined(HAVE_GNUTLS) + return gnutls_memcmp(s1, s2, len) == 0; +#else + unsigned int i, x; + + for (i = 0, x = 0; i < len; i++) + x |= ((const unsigned char *)s1)[i] ^ ((const unsigned char *)s2)[i]; + + return x == 0; +#endif +} diff --git a/util.h b/util.h index fbab1df9..682c58f5 100644 --- a/util.h +++ b/util.h @@ -257,6 +257,11 @@ extern unsigned int UTI_HexToBytes(const char *hex, void *buf, unsigned int len) number of pointers to the words. */ extern int UTI_SplitString(char *string, char **words, int max_saved_words); +/* Check if two buffers of the same length contain the same data, but do the + comparison in constant time with respect to the returned value to avoid + creating a timing side channel */ +extern int UTI_IsMemoryEqual(const void *s1, const void *s2, unsigned int len); + /* Macros to get maximum and minimum of two values */ #ifdef MAX #undef MAX