From c5912d0155c70f2ba7ac35beee6cb283ba6ac8ba Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 20 Aug 2024 01:12:04 +0000 Subject: [PATCH 1/5] bench: s2n_constant_time_equals --- bindings/rust/bench/Cargo.toml | 5 + .../bench/benches/constant_time_equals.rs | 105 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 bindings/rust/bench/benches/constant_time_equals.rs diff --git a/bindings/rust/bench/Cargo.toml b/bindings/rust/bench/Cargo.toml index e05dbe0b47c..9db92808a0c 100644 --- a/bindings/rust/bench/Cargo.toml +++ b/bindings/rust/bench/Cargo.toml @@ -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" @@ -60,3 +61,7 @@ harness = false [[bench]] name = "resumption" harness = false + +[[bench]] +name = "constant_time_equals" +harness = false diff --git a/bindings/rust/bench/benches/constant_time_equals.rs b/bindings/rust/bench/benches/constant_time_equals.rs new file mode 100644 index 00000000000..b434b6df79d --- /dev/null +++ b/bindings/rust/bench/benches/constant_time_equals.rs @@ -0,0 +1,105 @@ +// 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 no 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) { + 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 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 not 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() { + std::hint::black_box(memcmp(e, &a)); + } + }) + }); + + 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); From 8eb19a92d7dbec68c36bba38804c4f33908b570e Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Thu, 22 Aug 2024 11:31:05 -0700 Subject: [PATCH 2/5] Update bindings/rust/bench/benches/constant_time_equals.rs Co-authored-by: maddeleine <59030281+maddeleine@users.noreply.github.com> --- bindings/rust/bench/benches/constant_time_equals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/bench/benches/constant_time_equals.rs b/bindings/rust/bench/benches/constant_time_equals.rs index b434b6df79d..44fb89be67e 100644 --- a/bindings/rust/bench/benches/constant_time_equals.rs +++ b/bindings/rust/bench/benches/constant_time_equals.rs @@ -36,7 +36,7 @@ fn memcmp(a: &[u8], b: &[u8]) -> i32 { } extern "C" { - // s2n_constant_time_equals is no exposed publicly, so we manually write the + // 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) From 72085155b64d282ce4a96961e41062c5ff7ebdb4 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Thu, 22 Aug 2024 11:31:15 -0700 Subject: [PATCH 3/5] Update bindings/rust/bench/benches/constant_time_equals.rs Co-authored-by: maddeleine <59030281+maddeleine@users.noreply.github.com> --- bindings/rust/bench/benches/constant_time_equals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/bench/benches/constant_time_equals.rs b/bindings/rust/bench/benches/constant_time_equals.rs index 44fb89be67e..d475cf4b576 100644 --- a/bindings/rust/bench/benches/constant_time_equals.rs +++ b/bindings/rust/bench/benches/constant_time_equals.rs @@ -59,7 +59,7 @@ fn comparison(criterion: &mut Criterion) { // 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 equal + // 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))); From e430d447bcda5626d1f52f3249d6c1a15e641e59 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Thu, 22 Aug 2024 11:31:29 -0700 Subject: [PATCH 4/5] Update bindings/rust/bench/benches/constant_time_equals.rs Co-authored-by: maddeleine <59030281+maddeleine@users.noreply.github.com> --- bindings/rust/bench/benches/constant_time_equals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/bench/benches/constant_time_equals.rs b/bindings/rust/bench/benches/constant_time_equals.rs index d475cf4b576..62656caf675 100644 --- a/bindings/rust/bench/benches/constant_time_equals.rs +++ b/bindings/rust/bench/benches/constant_time_equals.rs @@ -68,7 +68,7 @@ fn comparison(criterion: &mut Criterion) { }); group.finish(); - // compare memcmp vs s2n_constant_time_equals, small data && data is not equal + // 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| { From c349ee041fb0e5f37fc0ffbe451f83b983002e2a Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Thu, 22 Aug 2024 18:33:46 +0000 Subject: [PATCH 5/5] address pr feedback - add comment for black_box --- bindings/rust/bench/benches/constant_time_equals.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/rust/bench/benches/constant_time_equals.rs b/bindings/rust/bench/benches/constant_time_equals.rs index 62656caf675..bf92bf2ddd2 100644 --- a/bindings/rust/bench/benches/constant_time_equals.rs +++ b/bindings/rust/bench/benches/constant_time_equals.rs @@ -86,6 +86,8 @@ fn comparison(criterion: &mut Criterion) { 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)); } })