Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bench: s2n_constant_time_equals #4717

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bindings/rust/bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ crabgrind = { version = "0.1", optional = true }
structopt = { version = "0.3", optional = true }
serde_json = { version = "1.0", optional = true }
semver = { version = "1.0", optional = true }
rand = "0.8.5"

[dependencies.plotters]
version = "0.3"
Expand Down Expand Up @@ -64,3 +65,7 @@ harness = false
[[bench]]
name = "connection_creation"
harness = false

[[bench]]
name = "constant_time_equals"
harness = false
107 changes: 107 additions & 0 deletions bindings/rust/bench/benches/constant_time_equals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//! These benchmarks compare the performance s2n_constant_time_equals with memcmp.
//! The benchmarks can be run with "cargo bench low-level-comparison"
//!
//! The results show that s2n_constant_time_equals is significantly slower than
//! memcmp, but that for small amounts of data the comparisons remain very cheap,
//! on the order of hundreds of nano-seconds.
//!
//! We also see that for pathological inputs, the overhead of the constant time
//! equals grows more noticeable, e.g. ~30 μs. This pathological case is roughly
//! modelled after what might be seen if a client totally fills an extension with
//! data. For this reason we generally avoid using s2n_constant_time_equals to
//! compare large amounts of data.

use criterion::{criterion_group, criterion_main, Criterion};
use rand::RngCore;
// we don't need any symbols from s2n-tls, but we do need to link against it to
// make s2n_constant_time_equals available.
use s2n_tls as _;
use std::ffi::c_void;

const SMALL_DATA_LENGTH: usize = u8::MAX as usize;
const MAX_EXTENSION_SIZE: usize = u16::MAX as usize;

// wrapper around the libc memcmp
fn memcmp(a: &[u8], b: &[u8]) -> i32 {
unsafe {
libc::memcmp(
a.as_ptr() as *const c_void,
b.as_ptr() as *const c_void,
a.len(),
)
}
}

extern "C" {
// s2n_constant_time_equals is not exposed publicly, so we manually write the
// bindings for it.
//
// bool s2n_constant_time_equals(const uint8_t *a, const uint8_t *b, const uint32_t len)
fn s2n_constant_time_equals(a: *const u8, b: *const u8, len: u32) -> bool;
}

fn rust_s2n_constant_time_equals(a: &[u8], b: &[u8]) -> bool {
unsafe { s2n_constant_time_equals(a.as_ptr(), b.as_ptr(), a.len() as u32) }
}

fn comparison(criterion: &mut Criterion) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we trying to do with this PR? Like, it is relevant for #4709, but once that is merged we're not really going to have a use for this comparison. If we're actually trying to benchmark s2n_constant_time_equals(), I would expect to see a test that checks that the comparison of [1, 2, 3, 4, 5] to [1, 0, 0, 0, 0] takes exactly as much time as the comparison of [1, 2, 3, 4, 5] to [1, 2, 3, 4, 5]. But maybe we trust that that case is covered with cbmc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, I see this benchmark function as being strictly interested in the performance of s2n_constant_time_equals, and not at all concerned with the correctness of it. We formally assert its correctness with the ctverif proofs.

let mut a = [0; SMALL_DATA_LENGTH];
let mut b = [0; SMALL_DATA_LENGTH];
let mut a_copy = [0; SMALL_DATA_LENGTH];

let mut rng = rand::thread_rng();
rng.fill_bytes(&mut a);
rng.fill_bytes(&mut b);

// use a separate copy to avoid the timing difference caused by loads
a_copy.copy_from_slice(&a);

// compare memcmp vs s2n_constant_time_equals, small data && data not equal
let mut group = criterion.benchmark_group("low-level-comparison - small data, not equal");
group.bench_function("memcmp", |bencher| bencher.iter(|| memcmp(&a, &b)));

group.bench_function("s2n_constant_time_equals", |bencher| {
bencher.iter(|| rust_s2n_constant_time_equals(&a, &b))
});
group.finish();

// compare memcmp vs s2n_constant_time_equals, small data && data is equal
let mut group = criterion.benchmark_group("low-level-comparison - small data, equal");
group.bench_function("memcmp", |bencher| bencher.iter(|| memcmp(&a, &a_copy)));
group.bench_function("s2n_constant_time_equals_c", |bencher| {
bencher.iter(|| rust_s2n_constant_time_equals(&a, &a_copy))
});
group.finish();

let mut pathological_data = [[0; SMALL_DATA_LENGTH]; MAX_EXTENSION_SIZE / SMALL_DATA_LENGTH];
for chunk in pathological_data.iter_mut() {
rng.fill_bytes(chunk);
}

// compare memcmp vs s2n_constant_time_equals, lots of small data && data is not equal
let mut group = criterion.benchmark_group("low-level-comparison - many small blobs, not equal");
group.bench_function("memcmp", |bencher| {
bencher.iter(|| {
for e in pathological_data.iter() {
// use "black box" to prevent the compiler from optimizing out the
// memcmp operation
std::hint::black_box(memcmp(e, &a));
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
}
})
});

group.bench_function("s2n_constant_time_equals", |bencher| {
bencher.iter(|| {
for e in pathological_data.iter() {
rust_s2n_constant_time_equals(e, &a);
}
})
});
group.finish();
}

criterion_group!(benches, comparison);
criterion_main!(benches);
Loading