mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-12-29 10:04:41 +01:00
5e3e2f4e17
This will never be reliable under high load, without making it unable to detect real errors. But the test is useful because if we don't have this test we'll never notice if we break the const-timedness of our implementation. So, move the calloc out of the test loop (which seems to make it more reliable), and then after we've run it, check the 1-minute load average. Too high, we don't complain about results. It's not perfect, but it's better. Running 100 times (-O3) serially gave 100 successes with the following results: Constant: Within 5% 562-926(832.89+/-73)/1000 times Non-constant: More than 5% slower 860-990(956.35+/-26)/1000 times More importantly, if we swap the const and non-const tests, we get the expected 100 failures: Non-constant: Within 5% 14-79(41.17+/-14)/1000 times Constant: More than 5% slower 44-231(111.89+/-33)/1000 times Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
152 lines
3.8 KiB
C
152 lines
3.8 KiB
C
#include <assert.h>
|
|
#include <bitcoin/privkey.c>
|
|
#include <ccan/err/err.h>
|
|
#include <ccan/time/time.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
/* AUTOGENERATED MOCKS START */
|
|
/* AUTOGENERATED MOCKS END */
|
|
static bool verbose = false;
|
|
|
|
#define RUNS (16 * 10000)
|
|
static struct timerel const_time_test(struct secret *s1,
|
|
struct secret *s2,
|
|
size_t off)
|
|
{
|
|
struct timeabs start, end;
|
|
int result = 0;
|
|
|
|
memset(s1, 0, RUNS * sizeof(*s1));
|
|
memset(s2, 0, RUNS * sizeof(*s2));
|
|
|
|
for (size_t i = 0; i < RUNS; i++)
|
|
s2[i].data[off] = i;
|
|
|
|
start = time_now();
|
|
for (size_t i = 0; i < RUNS; i++)
|
|
result += secret_eq_consttime(&s1[i], &s2[i]);
|
|
end = time_now();
|
|
|
|
if (result != RUNS / 256)
|
|
errx(1, "Expected %u successes at offset %zu, not %u!",
|
|
RUNS / 256, off, result);
|
|
|
|
return time_between(end, start);
|
|
}
|
|
|
|
static inline bool secret_eq_nonconst(const struct secret *a,
|
|
const struct secret *b)
|
|
{
|
|
return memcmp(a, b, sizeof(*a)) == 0;
|
|
}
|
|
|
|
static struct timerel nonconst_time_test(struct secret *s1,
|
|
struct secret *s2,
|
|
size_t off)
|
|
{
|
|
struct timeabs start, end;
|
|
int result = 0;
|
|
|
|
memset(s1, 0, RUNS * sizeof(*s1));
|
|
memset(s2, 0, RUNS * sizeof(*s2));
|
|
|
|
for (size_t i = 0; i < RUNS; i++)
|
|
s2[i].data[off] = i;
|
|
|
|
start = time_now();
|
|
for (size_t i = 0; i < RUNS; i++)
|
|
result += secret_eq_nonconst(&s1[i], &s2[i]);
|
|
end = time_now();
|
|
|
|
if (result != RUNS / 256)
|
|
errx(1, "Expected %u successes at offset %zu, not %u!",
|
|
RUNS / 256, off, result);
|
|
|
|
return time_between(end, start);
|
|
}
|
|
|
|
static struct secret *s1, *s2;
|
|
|
|
/* Returns true if test result is expected: we consider 5% "same". */
|
|
static bool secret_time_test(struct timerel (*test)(struct secret *s1,
|
|
struct secret *s2,
|
|
size_t off),
|
|
bool should_be_const)
|
|
{
|
|
struct timerel firstbyte_time, lastbyte_time, diff;
|
|
|
|
firstbyte_time = test(s1, s2, 0);
|
|
lastbyte_time = test(s1, s2, sizeof(s1->data)-1);
|
|
|
|
if (verbose)
|
|
printf("First byte %u psec vs last byte %u psec\n",
|
|
(int)time_to_nsec(time_divide(firstbyte_time, RUNS/1000)),
|
|
(int)time_to_nsec(time_divide(lastbyte_time, RUNS/1000)));
|
|
|
|
/* If they differ by more than 5%, get upset. */
|
|
if (time_less(firstbyte_time, lastbyte_time))
|
|
diff = time_sub(lastbyte_time, firstbyte_time);
|
|
else {
|
|
/* If the lastbyte test was faster, that's a fail it we expected
|
|
* it to be slower... */
|
|
if (!should_be_const)
|
|
return false;
|
|
diff = time_sub(firstbyte_time, lastbyte_time);
|
|
}
|
|
|
|
return time_less(time_multiply(diff, 20), firstbyte_time) == should_be_const;
|
|
}
|
|
|
|
#define ITERATIONS 1000
|
|
|
|
int main(void)
|
|
{
|
|
const char *v;
|
|
int const_success, nonconst_success = ITERATIONS, i;
|
|
double load;
|
|
setup_locale();
|
|
|
|
/* no point running this under valgrind. */
|
|
v = getenv("VALGRIND");
|
|
if (v && atoi(v) == 1)
|
|
exit(0);
|
|
|
|
s1 = calloc(RUNS, sizeof(*s1));
|
|
s2 = calloc(RUNS, sizeof(*s2));
|
|
|
|
/* When not loaded, this should pass over 50% of the time. */
|
|
const_success = 0;
|
|
for (i = 0; i < ITERATIONS; i++)
|
|
const_success += secret_time_test(const_time_test, true);
|
|
|
|
printf("=> Within 5%% %u/%u times\n", const_success, i);
|
|
|
|
/* This fails without -O2 or above, at least here (x86 Ubuntu gcc 7.3) */
|
|
if (strstr(COPTFLAGS, "-O2") || strstr(COPTFLAGS, "-O3")) {
|
|
/* Should show measurable differences at least 1/2 the time. */
|
|
nonconst_success = 0;
|
|
for (i = 0; i < 1000; i++)
|
|
nonconst_success
|
|
+= secret_time_test(nonconst_time_test, false);
|
|
|
|
printf("=> More than 5%% slower %u/%u times\n",
|
|
nonconst_success, i);
|
|
}
|
|
|
|
/* Now, check loadavg: if we weren't alone, that could explain results */
|
|
getloadavg(&load, 1);
|
|
if (load > 1.0)
|
|
errx(0, "Load %.2f: too high, ignoring", load);
|
|
|
|
if (const_success < ITERATIONS / 2)
|
|
errx(1, "Only const time %u/%u?", const_success, i);
|
|
|
|
if (nonconst_success < ITERATIONS / 2)
|
|
errx(1, "memcmp seemed const time %u/%u?", nonconst_success, i);
|
|
free(s1);
|
|
free(s2);
|
|
|
|
return 0;
|
|
}
|