Merge rust-bitcoin/rust-bitcoin#4289: [hashes] Disable fixed-time equality cmp when building for fuzzers, use fixed-time equality for `Hmac`

2d9e240fb6 [hashes] Use `fixed_time_eq` for `Hmac::eq` (Matt Corallo)
7ac7273013 [hashes] Disable fixed-time equality cmp when building for fuzzers (Matt Corallo)

Pull request description:

  Fuzzers want to break memcmp calls into separate comparisons for coverage monitoring, allowing them to not-quite-brute-force find inputs that fully match. Thus, we disable our fancy fixed-time comparison when built with the `hashes_fuzz` cfg.

ACKs for top commit:
  Kixunil:
    ACK 2d9e240fb6
  apoelstra:
    ACK 2d9e240fb6ae13e6139713f9bb8ccb51e5dc0bff; successfully ran local tests

Tree-SHA512: 372e344ae5497a10ca03a50baadb9d2e8a6dac914bbc4ca91fd5c9b839fb036f42c8b47c252ca3466c15286e889a6f5b51390cc0d938ba24dc50b13e8b863463
This commit is contained in:
merge-script 2025-03-28 23:31:40 +00:00
commit 3748bfcd66
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 48 additions and 30 deletions

View File

@ -15,41 +15,50 @@
/// As of rust 1.31.0 disassembly looks completely within reason for this, see
/// <https://godbolt.org/z/mMbGQv>.
pub fn fixed_time_eq(a: &[u8], b: &[u8]) -> bool {
assert!(a.len() == b.len());
let count = a.len();
let lhs = &a[..count];
let rhs = &b[..count];
#[cfg(hashes_fuzz)]
{
// Fuzzers want to break memcmp calls into separate comparisons for coverage monitoring,
// so we avoid our fancy fixed-time comparison below for fuzzers.
a == b
}
#[cfg(not(hashes_fuzz))]
{
assert!(a.len() == b.len());
let count = a.len();
let lhs = &a[..count];
let rhs = &b[..count];
let mut r: u8 = 0;
for i in 0..count {
let mut rs = unsafe { core::ptr::read_volatile(&r) };
rs |= lhs[i] ^ rhs[i];
unsafe {
core::ptr::write_volatile(&mut r, rs);
let mut r: u8 = 0;
for i in 0..count {
let mut rs = unsafe { core::ptr::read_volatile(&r) };
rs |= lhs[i] ^ rhs[i];
unsafe {
core::ptr::write_volatile(&mut r, rs);
}
}
}
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 4;
unsafe {
core::ptr::write_volatile(&mut r, t);
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 4;
unsafe {
core::ptr::write_volatile(&mut r, t);
}
}
}
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 2;
unsafe {
core::ptr::write_volatile(&mut r, t);
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 2;
unsafe {
core::ptr::write_volatile(&mut r, t);
}
}
}
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 1;
unsafe {
core::ptr::write_volatile(&mut r, t);
{
let mut t = unsafe { core::ptr::read_volatile(&r) };
t |= t >> 1;
unsafe {
core::ptr::write_volatile(&mut r, t);
}
}
unsafe { (::core::ptr::read_volatile(&r) & 1) == 0 }
}
unsafe { (::core::ptr::read_volatile(&r) & 1) == 0 }
}
#[cfg(test)]

View File

@ -15,8 +15,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{Hash, HashEngine};
/// A hash computed from a RFC 2104 HMAC. Parameterized by the underlying hash function.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Copy, Clone, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct Hmac<T: Hash>(T);
impl<T: Hash + str::FromStr> str::FromStr for Hmac<T> {
@ -24,6 +25,14 @@ impl<T: Hash + str::FromStr> str::FromStr for Hmac<T> {
fn from_str(s: &str) -> Result<Self, Self::Err> { Ok(Hmac(str::FromStr::from_str(s)?)) }
}
impl<T: Hash> PartialEq for Hmac<T> {
fn eq(&self, other: &Self) -> bool {
crate::cmp::fixed_time_eq(self.as_ref(), other.as_ref())
}
}
impl<T: Hash> Eq for Hmac<T> {}
/// Pair of underlying hash engines, used for the inner and outer hash of HMAC.
#[derive(Debug, Clone)]
pub struct HmacEngine<T: HashEngine> {