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