1 #include "crypto_core.h"
2 
3 #include <gtest/gtest.h>
4 
5 #include <algorithm>
6 #include <vector>
7 
8 namespace {
9 
10 enum {
11   /**
12    * The size of the arrays to compare. This was chosen to take around 2000
13    * CPU clocks on x86_64.
14    *
15    * This is 1MiB.
16    */
17   CRYPTO_TEST_MEMCMP_SIZE = 1024 * 1024,
18   /**
19    * The number of times we run memcmp in the test.
20    *
21    * We compute the median time taken to reduce error margins.
22    */
23   CRYPTO_TEST_MEMCMP_ITERATIONS = 500,
24   /**
25    * The margin of error (in clocks) we allow for this test.
26    *
27    * Should be within 0.5% of ~2000 CPU clocks. In reality, the code is much
28    * more precise and is usually within 1 CPU clock.
29    */
30   CRYPTO_TEST_MEMCMP_EPS = 10,
31 };
32 
memcmp_time(uint8_t const * a,uint8_t const * b,size_t len)33 clock_t memcmp_time(uint8_t const *a, uint8_t const *b, size_t len) {
34   clock_t start = clock();
35   volatile int result = crypto_memcmp(a, b, len);
36   (void)result;
37   return clock() - start;
38 }
39 
40 /**
41  * This function performs the actual timing. It interleaves comparison of
42  * equal and non-equal arrays to reduce the influence of external effects
43  * such as the machine being a little more busy 1 second later.
44  */
memcmp_median(uint8_t const * src,uint8_t const * same,uint8_t const * not_same,size_t len)45 std::pair<clock_t, clock_t> memcmp_median(uint8_t const *src, uint8_t const *same,
46                                           uint8_t const *not_same, size_t len) {
47   clock_t same_results[CRYPTO_TEST_MEMCMP_ITERATIONS];
48   clock_t not_same_results[CRYPTO_TEST_MEMCMP_ITERATIONS];
49 
50   for (size_t i = 0; i < CRYPTO_TEST_MEMCMP_ITERATIONS; i++) {
51     same_results[i] = memcmp_time(src, same, len);
52     not_same_results[i] = memcmp_time(src, not_same, len);
53   }
54 
55   std::sort(same_results, same_results + CRYPTO_TEST_MEMCMP_ITERATIONS);
56   clock_t const same_median = same_results[CRYPTO_TEST_MEMCMP_ITERATIONS / 2];
57   std::sort(not_same_results, not_same_results + CRYPTO_TEST_MEMCMP_ITERATIONS);
58   clock_t const not_same_median = not_same_results[CRYPTO_TEST_MEMCMP_ITERATIONS / 2];
59   return {same_median, not_same_median};
60 }
61 
62 /**
63  * This test checks whether crypto_memcmp takes the same time for equal and
64  * non-equal chunks of memory.
65  */
TEST(CryptoCore,MemcmpTimingIsDataIndependent)66 TEST(CryptoCore, MemcmpTimingIsDataIndependent) {
67   // A random piece of memory.
68   std::vector<uint8_t> src(CRYPTO_TEST_MEMCMP_SIZE);
69   random_bytes(src.data(), CRYPTO_TEST_MEMCMP_SIZE);
70 
71   // A separate piece of memory containing the same data.
72   std::vector<uint8_t> same = src;
73 
74   // Another piece of memory containing different data.
75   std::vector<uint8_t> not_same(CRYPTO_TEST_MEMCMP_SIZE);
76   random_bytes(not_same.data(), CRYPTO_TEST_MEMCMP_SIZE);
77 
78   // Once we have C++17:
79   // auto const [same_median, not_same_median] =
80   auto const result =
81       memcmp_median(src.data(), same.data(), not_same.data(), CRYPTO_TEST_MEMCMP_SIZE);
82 
83   clock_t const delta =
84       std::max(result.first, result.second) - std::min(result.first, result.second);
85 
86   EXPECT_LT(delta, CRYPTO_TEST_MEMCMP_EPS)
87       << "Delta time is too long (" << delta << " >= " << CRYPTO_TEST_MEMCMP_EPS << ")\n"
88       << "Time of the same data comparison: " << result.first << " clocks\n"
89       << "Time of the different data comparison: " << result.second << " clocks";
90 }
91 
92 }  // namespace
93