Skip to content

Commit 5b7082b

Browse files
authoredJun 24, 2024··
curve: use subtle::BlackBox optimization barrier (#662)
Replaces the security mitigation added in #659 and #661 for masking-related timing variability which used an inline `black_box` using the recently added `subtle::BlackBox` newtype (see dalek-cryptography/subtle#123) Internally `BlackBox` uses a volatile read by default (i.e. same strategy which was used before) or when the `core_hint_black_box` feature of `subtle` is enabled, it uses `core::hint::black_box` (whose documentation was recently updated to reflect the nuances of potential cryptographic use, see rust-lang/rust#126703) This PR goes ahead and uses `BlackBox` for both `mask` and `underflow_mask` where previously it was only used on `underflow_mask`. The general pattern of bitwise masking inside a loop seems worrisome for the optimizer potentially inserting branches in the future. Below are godbolt inspections of the generated assembly, which are free of the `jns` instructions originally spotted in #659/#661: - 32-bit (read_volatile): https://godbolt.org/z/TKo9fqza4 - 32-bit (hint::black_box): https://godbolt.org/z/caoMxYbET - 64-bit (read_volatile): https://godbolt.org/z/PM6zKjj1f - 64-bit (hint::black_box): https://godbolt.org/z/nseaPvdWv
1 parent 5312a03 commit 5b7082b

File tree

3 files changed

+16
-28
lines changed

3 files changed

+16
-28
lines changed
 

‎curve25519-dalek/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ff = { version = "0.13", default-features = false, optional = true }
5151
group = { version = "0.13", default-features = false, optional = true }
5252
rand_core = { version = "0.6.4", default-features = false, optional = true }
5353
digest = { version = "0.10", default-features = false, optional = true }
54-
subtle = { version = "2.3.0", default-features = false }
54+
subtle = { version = "2.6.0", default-features = false }
5555
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
5656
zeroize = { version = "1", default-features = false, optional = true }
5757

‎curve25519-dalek/src/backend/serial/u32/scalar.rs

+8-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
use core::fmt::Debug;
1414
use core::ops::{Index, IndexMut};
15+
use subtle::BlackBox;
1516

1617
#[cfg(feature = "zeroize")]
1718
use zeroize::Zeroize;
@@ -185,30 +186,24 @@ impl Scalar29 {
185186

186187
/// Compute `a - b` (mod l).
187188
pub fn sub(a: &Scalar29, b: &Scalar29) -> Scalar29 {
188-
// Optimization barrier to prevent compiler from inserting branch instructions
189-
// TODO(tarcieri): find a better home (or abstraction) for this
190-
fn black_box(value: u32) -> u32 {
191-
// SAFETY: `u32` is a simple integer `Copy` type and `value` lives on the stack so
192-
// a pointer to it will be valid.
193-
unsafe { core::ptr::read_volatile(&value) }
194-
}
195-
196189
let mut difference = Scalar29::ZERO;
197-
let mask = (1u32 << 29) - 1;
190+
let mask = BlackBox::new((1u32 << 29) - 1);
198191

199192
// a - b
200193
let mut borrow: u32 = 0;
201194
for i in 0..9 {
202195
borrow = a[i].wrapping_sub(b[i] + (borrow >> 31));
203-
difference[i] = borrow & mask;
196+
difference[i] = borrow & mask.get();
204197
}
205198

206199
// conditionally add l if the difference is negative
207-
let underflow_mask = ((borrow >> 31) ^ 1).wrapping_sub(1);
200+
let underflow_mask = BlackBox::new(((borrow >> 31) ^ 1).wrapping_sub(1));
208201
let mut carry: u32 = 0;
209202
for i in 0..9 {
210-
carry = (carry >> 29) + difference[i] + (constants::L[i] & black_box(underflow_mask));
211-
difference[i] = carry & mask;
203+
// SECURITY: `BlackBox` prevents LLVM from inserting a `jns` conditional on x86(_64)
204+
// which can be used to bypass this section when `underflow_mask` is zero.
205+
carry = (carry >> 29) + difference[i] + (constants::L[i] & underflow_mask.get());
206+
difference[i] = carry & mask.get();
212207
}
213208

214209
difference

‎curve25519-dalek/src/backend/serial/u64/scalar.rs

+7-14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
use core::fmt::Debug;
1515
use core::ops::{Index, IndexMut};
16+
use subtle::BlackBox;
1617

1718
#[cfg(feature = "zeroize")]
1819
use zeroize::Zeroize;
@@ -174,32 +175,24 @@ impl Scalar52 {
174175

175176
/// Compute `a - b` (mod l)
176177
pub fn sub(a: &Scalar52, b: &Scalar52) -> Scalar52 {
177-
// Optimization barrier to prevent compiler from inserting branch instructions
178-
// TODO(tarcieri): find a better home (or abstraction) for this
179-
fn black_box(value: u64) -> u64 {
180-
// SAFETY: `u64` is a simple integer `Copy` type and `value` lives on the stack so
181-
// a pointer to it will be valid.
182-
unsafe { core::ptr::read_volatile(&value) }
183-
}
184-
185178
let mut difference = Scalar52::ZERO;
186-
let mask = (1u64 << 52) - 1;
179+
let mask = BlackBox::new((1u64 << 52) - 1);
187180

188181
// a - b
189182
let mut borrow: u64 = 0;
190183
for i in 0..5 {
191184
borrow = a[i].wrapping_sub(b[i] + (borrow >> 63));
192-
difference[i] = borrow & mask;
185+
difference[i] = borrow & mask.get();
193186
}
194187

195188
// conditionally add l if the difference is negative
196-
let underflow_mask = ((borrow >> 63) ^ 1).wrapping_sub(1);
189+
let underflow_mask = BlackBox::new(((borrow >> 63) ^ 1).wrapping_sub(1));
197190
let mut carry: u64 = 0;
198191
for i in 0..5 {
199-
// SECURITY: `black_box` prevents LLVM from inserting a `jns` conditional on x86(_64)
192+
// SECURITY: `BlackBox` prevents LLVM from inserting a `jns` conditional on x86(_64)
200193
// which can be used to bypass this section when `underflow_mask` is zero.
201-
carry = (carry >> 52) + difference[i] + (constants::L[i] & black_box(underflow_mask));
202-
difference[i] = carry & mask;
194+
carry = (carry >> 52) + difference[i] + (constants::L[i] & underflow_mask.get());
195+
difference[i] = carry & mask.get();
203196
}
204197

205198
difference

0 commit comments

Comments
 (0)