Skip to content

Commit 497a800

Browse files
committed
arithmetic: Clarify memory safety of elem_exp_consttime.
`elem_exp_consttime` was written weirdly in that it had a bunch of wrappers functions around assembly functions, `bn_gather5`, etc., where the wrapper functions were not marked `unsafe` but also they didn't themselves enforce safety properties. It kind of made sense but technically they should have been `unsafe`. And, there were just a lot of confusing details in how they were written. Start over with the wrapper functions, and put the new versions of them in the `x86_64_mont` submodule. We intentionally avoid adding any masking to `power` arguments, which would make them completely safe. To review this properly, one needs to compare how these functions are documented and used in `crypto/fipsmodule/bn/internal.h` and `crypto/fipsmodule/bn/exponentiation.c` (note this file was renamed in recent versions of BoringSSL; use the version that corresponds to the most recently-merged BoringSSL in *ring*.) ``` git difftool HEAD^1:src/arithmetic/bigint.rs \ src/arithmetic/x86_64_mont.rs ```
1 parent 1cab914 commit 497a800

File tree

5 files changed

+197
-110
lines changed

5 files changed

+197
-110
lines changed

src/arithmetic/bigint.rs

Lines changed: 20 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ pub fn elem_exp_consttime<M>(
415415
base: Elem<M, R>,
416416
exponent: &PrivateExponent,
417417
m: &Modulus<M>,
418-
) -> Result<Elem<M, Unencoded>, error::Unspecified> {
418+
) -> Result<Elem<M, Unencoded>, LimbSliceError> {
419419
use crate::{bssl, limb::Window};
420420

421421
const WINDOW_BITS: usize = 5;
@@ -476,8 +476,7 @@ pub fn elem_exp_consttime<M>(
476476
let src1 = entry(previous, src1, num_limbs);
477477
let src2 = entry(previous, src2, num_limbs);
478478
let dst = entry_mut(rest, 0, num_limbs);
479-
limbs_mul_mont((dst, src1, src2), m.limbs(), m.n0(), m.cpu_features())
480-
.map_err(error::erase::<LimbSliceError>)?;
479+
limbs_mul_mont((dst, src1, src2), m.limbs(), m.n0(), m.cpu_features())?;
481480
}
482481

483482
let tmp = m.zero();
@@ -502,12 +501,10 @@ pub fn elem_exp_consttime<M>(
502501
base: Elem<M, R>,
503502
exponent: &PrivateExponent,
504503
m: &Modulus<M>,
505-
) -> Result<Elem<M, Unencoded>, error::Unspecified> {
504+
) -> Result<Elem<M, Unencoded>, LimbSliceError> {
505+
use super::x86_64_mont::{gather5, mul_mont_gather5_amm, power5_amm, scatter5};
506506
use crate::{cpu, limb::LIMB_BYTES};
507507

508-
// Pretty much all the math here requires CPU feature detection to have
509-
// been done. `cpu_features` isn't threaded through all the internal
510-
// functions, so just make it clear that it has been done at this point.
511508
let cpu_features = m.cpu_features();
512509

513510
// The x86_64 assembly was written under the assumption that the input data
@@ -534,85 +531,6 @@ pub fn elem_exp_consttime<M>(
534531
table.split_at_mut(TABLE_ENTRIES * num_limbs)
535532
};
536533

537-
fn scatter(table: &mut [Limb], acc: &[Limb], i: LeakyWindow, num_limbs: usize) {
538-
prefixed_extern! {
539-
fn bn_scatter5(a: *const Limb, a_len: c::size_t, table: *mut Limb, i: LeakyWindow);
540-
}
541-
unsafe { bn_scatter5(acc.as_ptr(), num_limbs, table.as_mut_ptr(), i) }
542-
}
543-
544-
fn gather(table: &[Limb], acc: &mut [Limb], i: Window, num_limbs: usize) {
545-
prefixed_extern! {
546-
fn bn_gather5(r: *mut Limb, a_len: c::size_t, table: *const Limb, i: Window);
547-
}
548-
unsafe { bn_gather5(acc.as_mut_ptr(), num_limbs, table.as_ptr(), i) }
549-
}
550-
551-
fn limbs_mul_mont_gather5_amm(
552-
table: &[Limb],
553-
acc: &mut [Limb],
554-
base: &[Limb],
555-
m: &[Limb],
556-
n0: &N0,
557-
i: Window,
558-
num_limbs: usize,
559-
) {
560-
prefixed_extern! {
561-
fn bn_mul_mont_gather5(
562-
rp: *mut Limb,
563-
ap: *const Limb,
564-
table: *const Limb,
565-
np: *const Limb,
566-
n0: &N0,
567-
num: c::size_t,
568-
power: Window,
569-
);
570-
}
571-
unsafe {
572-
bn_mul_mont_gather5(
573-
acc.as_mut_ptr(),
574-
base.as_ptr(),
575-
table.as_ptr(),
576-
m.as_ptr(),
577-
n0,
578-
num_limbs,
579-
i,
580-
);
581-
}
582-
}
583-
584-
fn power_amm(
585-
table: &[Limb],
586-
acc: &mut [Limb],
587-
m_cached: &[Limb],
588-
n0: &N0,
589-
i: Window,
590-
num_limbs: usize,
591-
) {
592-
prefixed_extern! {
593-
fn bn_power5(
594-
r: *mut Limb,
595-
a: *const Limb,
596-
table: *const Limb,
597-
n: *const Limb,
598-
n0: &N0,
599-
num: c::size_t,
600-
i: Window,
601-
);
602-
}
603-
unsafe {
604-
bn_power5(
605-
acc.as_mut_ptr(),
606-
acc.as_ptr(),
607-
table.as_ptr(),
608-
m_cached.as_ptr(),
609-
n0,
610-
num_limbs,
611-
i,
612-
);
613-
}
614-
}
615-
616534
// These are named `(tmp, am, np)` in BoringSSL.
617535
let (acc, base_cached, m_cached): (&mut [Limb], &[Limb], &[Limb]) = {
618536
let (acc, rest) = state.split_at_mut(num_limbs);
@@ -639,11 +557,10 @@ pub fn elem_exp_consttime<M>(
639557
m_cached: &[Limb],
640558
n0: &N0,
641559
mut i: LeakyWindow,
642-
num_limbs: usize,
643560
cpu_features: cpu::Features,
644561
) -> Result<(), LimbSliceError> {
645562
loop {
646-
scatter(table, acc, i, num_limbs);
563+
scatter5(acc, table, i)?;
647564
i *= 2;
648565
if i >= TABLE_ENTRIES as LeakyWindow {
649566
break;
@@ -657,38 +574,34 @@ pub fn elem_exp_consttime<M>(
657574

658575
// acc = table[0] = base**0 (i.e. 1).
659576
m.oneR(acc);
660-
scatter(table, acc, 0, num_limbs);
577+
scatter5(acc, table, 0)?;
661578

662579
// acc = base**1 (i.e. base).
663580
acc.copy_from_slice(base_cached);
664581

665582
// Fill in entries 1, 2, 4, 8, 16.
666-
scatter_powers_of_2(table, acc, m_cached, n0, 1, num_limbs, cpu_features)
667-
.map_err(error::erase::<LimbSliceError>)?;
583+
scatter_powers_of_2(table, acc, m_cached, n0, 1, cpu_features)?;
668584
// Fill in entries 3, 6, 12, 24; 5, 10, 20, 30; 7, 14, 28; 9, 18; 11, 22; 13, 26; 15, 30;
669585
// 17; 19; 21; 23; 25; 27; 29; 31.
670586
for i in (3..(TABLE_ENTRIES as LeakyWindow)).step_by(2) {
671-
limbs_mul_mont_gather5_amm(
672-
table,
673-
acc,
674-
base_cached,
675-
m_cached,
676-
n0,
677-
Window::from(i - 1), // Not secret
678-
num_limbs,
679-
);
680-
scatter_powers_of_2(table, acc, m_cached, n0, i, num_limbs, cpu_features)
681-
.map_err(error::erase::<LimbSliceError>)?;
587+
let power = Window::from(i - 1);
588+
assert!(power < 32); // Not secret,
589+
unsafe {
590+
mul_mont_gather5_amm(acc, base_cached, table, m_cached, n0, power, cpu_features)
591+
}?;
592+
scatter_powers_of_2(table, acc, m_cached, n0, i, cpu_features)?;
682593
}
683594

684595
let acc = limb::fold_5_bit_windows(
685596
exponent.limbs(),
686597
|initial_window| {
687-
gather(table, acc, initial_window, num_limbs);
598+
unsafe { gather5(acc, table, initial_window) }
599+
.unwrap_or_else(unwrap_impossible_limb_slice_error);
688600
acc
689601
},
690602
|acc, window| {
691-
power_amm(table, acc, m_cached, n0, window, num_limbs);
603+
unsafe { power5_amm(acc, table, m_cached, n0, window, cpu_features) }
604+
.unwrap_or_else(unwrap_impossible_limb_slice_error);
692605
acc
693606
},
694607
);
@@ -766,7 +679,9 @@ mod tests {
766679
.expect("valid exponent")
767680
};
768681
let base = into_encoded(base, &m);
769-
let actual_result = elem_exp_consttime(base, &e, &m).unwrap();
682+
let actual_result = elem_exp_consttime(base, &e, &m)
683+
.map_err(error::erase::<LimbSliceError>)
684+
.unwrap();
770685
assert_elem_eq(&actual_result, &expected_result);
771686

772687
Ok(())

src/arithmetic/x86_64_mont.rs

Lines changed: 172 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024-2025 Brian Smith.
1+
// Copyright 2015-2025 Brian Smith.
22
//
33
// Permission to use, copy, modify, and/or distribute this software for any
44
// purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
1414

1515
#![cfg(target_arch = "x86_64")]
1616

17-
use super::{inout::AliasingSlices2, n0::N0, MAX_LIMBS};
17+
use super::{inout::AliasingSlices2, n0::N0, LimbSliceError, MAX_LIMBS};
1818
use crate::{
1919
c,
2020
cpu::{
@@ -23,9 +23,9 @@ use crate::{
2323
GetFeature as _,
2424
},
2525
error::LenMismatchError,
26-
limb::Limb,
26+
limb::{LeakyWindow, Limb, Window},
2727
};
28-
use core::ops::ControlFlow;
28+
use core::{num::NonZeroUsize, ops::ControlFlow};
2929

3030
#[inline]
3131
pub(super) fn bn_sqr8x_mont(
@@ -75,3 +75,171 @@ pub(super) fn bn_sqr8x_mont(
7575
});
7676
ControlFlow::Break(r)
7777
}
78+
79+
#[inline(always)]
80+
pub(super) fn scatter5(
81+
a: &[Limb],
82+
table: &mut [Limb],
83+
power: LeakyWindow,
84+
) -> Result<(), LimbSliceError> {
85+
prefixed_extern! {
86+
// Upstream uses `num: c::size_t` too, and `power: c::size_t`; see
87+
// `_MAX_LIMBS_ADDRESSES_MEMORY_SAFETY_ISSUES`.
88+
fn bn_scatter5(
89+
inp: *const Limb,
90+
num: c::NonZero_size_t,
91+
table: *mut Limb,
92+
power: LeakyWindow,
93+
);
94+
}
95+
let num_limbs = check_common(a, table)?;
96+
assert!(power < 32);
97+
unsafe { bn_scatter5(a.as_ptr(), num_limbs, table.as_mut_ptr(), power) };
98+
Ok(())
99+
}
100+
101+
// SAFETY: `power` must be less than 32.
102+
#[inline(always)]
103+
pub(super) unsafe fn gather5(
104+
r: &mut [Limb],
105+
table: &[Limb],
106+
power: Window,
107+
) -> Result<(), LimbSliceError> {
108+
prefixed_extern! {
109+
// Upstream uses `num: c::size_t` too, and `power: c::size_t`; see
110+
// `_MAX_LIMBS_ADDRESSES_MEMORY_SAFETY_ISSUES`.
111+
pub(super) fn bn_gather5(
112+
out: *mut Limb,
113+
num: c::NonZero_size_t,
114+
table: *const Limb,
115+
power: Window);
116+
}
117+
let num_limbs = check_common(r, table)?;
118+
// SAFETY: We cannot assert that `power` is in range because it is secret.
119+
// TODO: Create a `Window5` type that is guaranteed to be in range.
120+
unsafe { bn_gather5(r.as_mut_ptr(), num_limbs, table.as_ptr(), power) };
121+
Ok(())
122+
}
123+
124+
// SAFETY: `power` must be less than 32.
125+
#[inline(always)]
126+
pub(super) unsafe fn mul_mont_gather5_amm(
127+
r: &mut [Limb],
128+
a: &[Limb],
129+
table: &[Limb],
130+
n: &[Limb],
131+
n0: &N0,
132+
power: Window,
133+
_maybe_adx_bmi1_bmi2: cpu::Features, // TODO: Option<(Adx, Bmi1, Bmi2)>,
134+
) -> Result<(), LimbSliceError> {
135+
prefixed_extern! {
136+
// Upstream has `num: c::int` and `power: c::int`; see
137+
// `_MAX_LIMBS_ADDRESSES_MEMORY_SAFETY_ISSUES`.
138+
pub(super) fn bn_mul_mont_gather5(
139+
rp: *mut Limb,
140+
ap: *const Limb,
141+
table: *const Limb,
142+
np: *const Limb,
143+
n0: &N0,
144+
num: c::NonZero_size_t,
145+
power: Window,
146+
);
147+
}
148+
let num_limbs = check_common_with_n(r, table, n)?;
149+
if a.len() != num_limbs.get() {
150+
return Err(LimbSliceError::len_mismatch(LenMismatchError::new(a.len())));
151+
}
152+
// SAFETY: We cannot assert that `power` is in range because it is secret.
153+
// TODO: Create a `Window5` type that is guaranteed to be in range.
154+
unsafe {
155+
bn_mul_mont_gather5(
156+
r.as_mut_ptr(),
157+
a.as_ptr(),
158+
table.as_ptr(),
159+
n.as_ptr(),
160+
n0,
161+
num_limbs,
162+
power,
163+
)
164+
};
165+
Ok(())
166+
}
167+
168+
// SAFETY: `power` must be less than 32.
169+
#[inline(always)]
170+
pub(super) unsafe fn power5_amm(
171+
in_out: &mut [Limb],
172+
table: &[Limb],
173+
n: &[Limb],
174+
n0: &N0,
175+
power: Window,
176+
_maybe_adx_bmi1_bmi2: cpu::Features, // TODO: Option<(Adx, Bmi1, Bmi2)>,
177+
) -> Result<(), LimbSliceError> {
178+
prefixed_extern! {
179+
// Upstream has `num: c::int` and `power: c::int`; see
180+
// `_MAX_LIMBS_ADDRESSES_MEMORY_SAFETY_ISSUES`.
181+
fn bn_power5(
182+
rp: *mut Limb,
183+
ap: *const Limb,
184+
table: *const Limb,
185+
np: *const Limb,
186+
n0: &N0,
187+
num: c::NonZero_size_t,
188+
power: Window,
189+
);
190+
}
191+
let num_limbs = check_common_with_n(in_out, table, n)?;
192+
// SAFETY: We cannot assert that `power` is in range because it is secret.
193+
// TODO: Create a `Window5` type that is guaranteed to be in range.
194+
unsafe {
195+
bn_power5(
196+
in_out.as_mut_ptr(),
197+
in_out.as_ptr(),
198+
table.as_ptr(),
199+
n.as_ptr(),
200+
n0,
201+
num_limbs,
202+
power,
203+
);
204+
}
205+
Ok(())
206+
}
207+
208+
// Helps the compiler will be able to hoist all of these checks out of the
209+
// loops in the caller. Try to help the compiler by doing the checks
210+
// consistently in each function and also by inlining this function and all the
211+
// callers.
212+
#[inline(always)]
213+
fn check_common(a: &[Limb], table: &[Limb]) -> Result<NonZeroUsize, LimbSliceError> {
214+
assert_eq!((table.as_ptr() as usize) % 16, 0); // According to BoringSSL.
215+
let num_limbs = NonZeroUsize::new(a.len()).ok_or_else(|| LimbSliceError::too_short(a.len()))?;
216+
if num_limbs.get() % 8 != 0 {
217+
// TODO: Use a different error.
218+
return Err(LimbSliceError::len_mismatch(LenMismatchError::new(a.len())));
219+
}
220+
if num_limbs.get() > MAX_LIMBS {
221+
return Err(LimbSliceError::too_long(a.len()));
222+
}
223+
if num_limbs.get() * 32 != table.len() {
224+
return Err(LimbSliceError::len_mismatch(LenMismatchError::new(
225+
table.len(),
226+
)));
227+
};
228+
Ok(num_limbs)
229+
}
230+
231+
#[inline(always)]
232+
fn check_common_with_n(
233+
a: &[Limb],
234+
table: &[Limb],
235+
n: &[Limb],
236+
) -> Result<NonZeroUsize, LimbSliceError> {
237+
// Choose `a` instead of `n` so that every function starts with
238+
// `check_common` passing the exact same arguments, so that the compiler
239+
// can easily de-dupe the checks.
240+
let num_limbs = check_common(a, table)?;
241+
if n.len() != num_limbs.get() {
242+
return Err(LimbSliceError::len_mismatch(LenMismatchError::new(n.len())));
243+
}
244+
Ok(num_limbs)
245+
}

0 commit comments

Comments
 (0)