]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
util: add function for constant-time memory comparison
authorMiroslav Lichvar <mlichvar@redhat.com>
Wed, 2 Apr 2025 13:32:05 +0000 (15:32 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 3 Apr 2025 14:05:04 +0000 (16:05 +0200)
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.

configure
test/unit/util.c
util.c
util.h

index 91893e2ce19a68a64a414ad195c557c963a329ff..eaa41a08f1953ca5fff93226125da888f79699e6 100755 (executable)
--- 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" \
index d52a2684c365b50b236fa709c2e270c359a61b1d..15bc793c5db0f3dac9203567435e6473f0a7e4fc 100644 (file)
@@ -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 a4c8288b38ca11b6e5a81076afcfbbd02d7540cb..b278da697083092af2d213afde83f8d7f205705a 100644 (file)
--- a/util.c
+++ b/util.c
 
 #include "sysincl.h"
 
+#if defined(HAVE_NETTLE)
+#include <nettle/memops.h>
+#elif defined(HAVE_GNUTLS)
+#include <gnutls/gnutls.h>
+#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 fbab1df9acfd5e874c487a81bd30405595f01e2f..682c58f54dd02b2947dd68923a1440d12993ad1e 100644 (file)
--- 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