diff --git a/Cargo.toml b/Cargo.toml index fc6f989..6035f18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,14 @@ keywords = ["argon2", "argon2d", "argon2i", "hash", "password"] [lib] name = "argon2" +[features] +default = ["crossbeam-utils"] + [dependencies] base64 = "0.21" blake2b_simd = "1.0" constant_time_eq = "0.3.0" +crossbeam-utils = { version = "0.8", optional = true } serde = { version = "1.0", optional = true, features=["derive"] } [dev-dependencies] diff --git a/README.md b/README.md index 3c06db4..a6e6e76 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ assert!(matches); Create a password hash with custom settings and verify it: ```rust -use argon2::{self, Config, Variant, Version}; +use argon2::{self, Config, ThreadMode, Variant, Version}; let password = b"password"; let salt = b"othersalt"; @@ -49,6 +49,7 @@ let config = Config { mem_cost: 65536, time_cost: 10, lanes: 4, + thread_mode: ThreadMode::Parallel, secret: &[], ad: &[], hash_length: 32 diff --git a/src/argon2.rs b/src/argon2.rs index 15e5c01..b93ea0d 100644 --- a/src/argon2.rs +++ b/src/argon2.rs @@ -12,6 +12,7 @@ use crate::core; use crate::encoding; use crate::memory::Memory; use crate::result::Result; +use crate::thread_mode::ThreadMode; use crate::variant::Variant; use crate::version::Version; @@ -73,16 +74,25 @@ pub fn encoded_len( /// ``` /// /// -/// Create an Argon2d encoded hash with 4 lanes: +/// Create an Argon2d encoded hash with 4 lanes and parallel execution: /// /// ``` -/// use argon2::{self, Config, Variant}; +/// use argon2::{self, Config, ThreadMode, Variant}; /// /// let pwd = b"password"; /// let salt = b"somesalt"; /// let mut config = Config::default(); /// config.variant = Variant::Argon2d; -/// config.lanes = 4; +#[cfg_attr(feature = "crossbeam-utils", doc = "config.lanes = 4;")] +#[cfg_attr( + feature = "crossbeam-utils", + doc = "config.thread_mode = ThreadMode::Parallel;" +)] +#[cfg_attr(not(feature = "crossbeam-utils"), doc = "config.lanes = 1;")] +#[cfg_attr( + not(feature = "crossbeam-utils"), + doc = "config.thread_mode = ThreadMode::Sequential;" +)] /// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap(); /// ``` pub fn hash_encoded(pwd: &[u8], salt: &[u8], config: &Config) -> Result { @@ -108,16 +118,25 @@ pub fn hash_encoded(pwd: &[u8], salt: &[u8], config: &Config) -> Result /// ``` /// /// -/// Create an Argon2d hash with 4 lanes: +/// Create an Argon2d hash with 4 lanes and parallel execution: /// /// ``` -/// use argon2::{self, Config, Variant}; +/// use argon2::{self, Config, ThreadMode, Variant}; /// /// let pwd = b"password"; /// let salt = b"somesalt"; /// let mut config = Config::default(); /// config.variant = Variant::Argon2d; -/// config.lanes = 4; +#[cfg_attr(feature = "crossbeam-utils", doc = "config.lanes = 4;")] +#[cfg_attr( + feature = "crossbeam-utils", + doc = "config.thread_mode = ThreadMode::Parallel;" +)] +#[cfg_attr(not(feature = "crossbeam-utils"), doc = "config.lanes = 1;")] +#[cfg_attr( + not(feature = "crossbeam-utils"), + doc = "config.thread_mode = ThreadMode::Sequential;" +)] /// let vec = argon2::hash_raw(pwd, salt, &config).unwrap(); /// ``` pub fn hash_raw(pwd: &[u8], salt: &[u8], config: &Config) -> Result> { @@ -160,12 +179,18 @@ pub fn verify_encoded(encoded: &str, pwd: &[u8]) -> Result { /// ``` pub fn verify_encoded_ext(encoded: &str, pwd: &[u8], secret: &[u8], ad: &[u8]) -> Result { let decoded = encoding::decode_string(encoded)?; + let threads = if cfg!(feature = "crossbeam-utils") { + decoded.parallelism + } else { + 1 + }; let config = Config { variant: decoded.variant, version: decoded.version, mem_cost: decoded.mem_cost, time_cost: decoded.time_cost, lanes: decoded.parallelism, + thread_mode: ThreadMode::from_threads(threads), secret, ad, hash_length: decoded.hash.len() as u32, @@ -177,7 +202,6 @@ pub fn verify_encoded_ext(encoded: &str, pwd: &[u8], secret: &[u8], ad: &[u8]) - /// /// # Examples /// -/// /// ``` /// use argon2::{self, Config}; /// @@ -203,7 +227,8 @@ pub fn verify_raw(pwd: &[u8], salt: &[u8], hash: &[u8], config: &Config) -> Resu fn run(context: &Context) -> Vec { let mut memory = Memory::new(context.config.lanes, context.lane_length); core::initialize(context, &mut memory); - core::fill_memory_blocks(context, &mut memory); + // SAFETY: `memory` is constructed from `context`. + unsafe { core::fill_memory_blocks(context, &mut memory) }; core::finalize(context, &memory) } @@ -213,7 +238,41 @@ mod tests { #[test] fn single_thread_verification_multi_lane_hash() { - let hash = "$argon2i$v=19$m=4096,t=3,p=4$YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo$BvBk2OaSofBHfbrUW61nHrWB/43xgfs/QJJ5DkMAd8I"; - verify_encoded(hash, b"foo").unwrap(); + let hash = "$argon2i$v=19$m=4096,t=3,p=4$YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo$\ + BvBk2OaSofBHfbrUW61nHrWB/43xgfs/QJJ5DkMAd8I"; + let res = verify_encoded(hash, b"foo").unwrap(); + assert!(res); + } + + #[test] + fn test_argon2id_for_miri() { + let hash = "$argon2id$v=19$m=256,t=2,p=16$c29tZXNhbHQ$\ + 0gasyPnKXiBHQ5bft/bd4jrmy2DdtrLTX3JR9co7fRY"; + let res = verify_encoded(hash, b"password").unwrap(); + assert!(res); + } + + #[test] + fn test_argon2id_for_miri_2() { + let hash = "$argon2id$v=19$m=512,t=2,p=8$c29tZXNhbHQ$\ + qgW4yz2jO7oklapDpVwzUYgfDLzfwkppGTvhRDDBjkY"; + let res = verify_encoded(hash, b"password").unwrap(); + assert!(res); + } + + #[test] + fn test_argon2d_for_miri() { + let hash = "$argon2d$v=19$m=256,t=2,p=16$c29tZXNhbHQ$\ + doW5kZ/0cTwqwbYTwr9JD0wNwy3tMyJMMk9ojGsC8bk"; + let res = verify_encoded(hash, b"password").unwrap(); + assert!(res); + } + + #[test] + fn test_argon2i_for_miri() { + let hash = "$argon2i$v=19$m=256,t=2,p=16$c29tZXNhbHQ$\ + c1suSp12ZBNLSuyhD8pJriM2r5jP2kgZ5QdDAk3+HaY"; + let res = verify_encoded(hash, b"password").unwrap(); + assert!(res); } } diff --git a/src/block.rs b/src/block.rs index b0573be..2c72cda 100644 --- a/src/block.rs +++ b/src/block.rs @@ -11,7 +11,13 @@ use std::fmt; use std::fmt::Debug; use std::ops::{BitXorAssign, Index, IndexMut}; -/// Structure for the (1KB) memory block implemented as 128 64-bit words. +/// Structure for the (1 KiB) memory block implemented as 128 64-bit words. +// Blocks are 128-byte aligned to prevent false sharing. This specific alignment value replicates +// what `crossbeam-utils::CachePadded` does on modern architectures, which either use 128-byte +// cache lines (aarch64) or pull 64-byte cache lines in pairs (x86-64), without the need for +// stubbing that type in when `crossbeam-utils` isn't available. As blocks are fairly large (1 +// KiB), this simplification shouldn't severy affect the (rarer) targets with smaller cache lines. +#[repr(align(128))] pub struct Block([u64; common::QWORDS_IN_BLOCK]); impl Block { diff --git a/src/config.rs b/src/config.rs index 9f8269f..f43acca 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::thread_mode::ThreadMode; use crate::variant::Variant; use crate::version::Version; @@ -14,7 +15,7 @@ use crate::version::Version; /// # Examples /// /// ``` -/// use argon2::{Config, Variant, Version}; +/// use argon2::{Config, ThreadMode, Variant, Version}; /// /// let config = Config::default(); /// assert_eq!(config.ad, &[]); @@ -24,6 +25,7 @@ use crate::version::Version; /// assert_eq!(config.secret, &[]); /// assert_eq!(config.time_cost, 2); /// assert_eq!(config.variant, Variant::Argon2id); +/// assert_eq!(config.thread_mode, ThreadMode::Sequential); /// assert_eq!(config.version, Version::Version13); /// ``` #[derive(Clone, Debug, PartialEq)] @@ -43,6 +45,9 @@ pub struct Config<'a> { /// The key. pub secret: &'a [u8], + /// The thread mode. + pub thread_mode: ThreadMode, + /// The number of passes. pub time_cost: u32, @@ -62,6 +67,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 4096, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 3, variant: Variant::Argon2i, version: Version::Version13, @@ -76,6 +82,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 47104, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 1, variant: Variant::Argon2id, version: Version::Version13, @@ -90,6 +97,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 19456, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 2, variant: Variant::Argon2id, version: Version::Version13, @@ -104,6 +112,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 12288, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 3, variant: Variant::Argon2id, version: Version::Version13, @@ -118,6 +127,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 9216, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 4, variant: Variant::Argon2id, version: Version::Version13, @@ -132,6 +142,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 7168, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 5, variant: Variant::Argon2id, version: Version::Version13, @@ -146,6 +157,7 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 2097152, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 1, variant: Variant::Argon2id, version: Version::Version13, @@ -160,11 +172,16 @@ impl<'a> Config<'a> { lanes: 1, mem_cost: 65536, secret: &[], + thread_mode: ThreadMode::default(), time_cost: 3, variant: Variant::Argon2id, version: Version::Version13, } } + + pub fn uses_sequential(&self) -> bool { + self.thread_mode == ThreadMode::Sequential || self.lanes == 1 + } } impl<'a> Default for Config<'a> { @@ -178,6 +195,7 @@ impl<'a> Default for Config<'a> { mod tests { use crate::config::Config; + use crate::thread_mode::ThreadMode; use crate::variant::Variant; use crate::version::Version; @@ -189,6 +207,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 19 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 2); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -202,6 +221,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 4096); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 3); assert_eq!(config.variant, Variant::Argon2i); assert_eq!(config.version, Version::Version13); @@ -215,6 +235,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 46 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 1); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -228,6 +249,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 19 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 2); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -241,6 +263,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 12 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 3); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -254,6 +277,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 9 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 4); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -267,6 +291,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 7 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 5); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -280,6 +305,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 2 * 1024 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 1); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); @@ -293,6 +319,7 @@ mod tests { assert_eq!(config.lanes, 1); assert_eq!(config.mem_cost, 64 * 1024); assert_eq!(config.secret, &[]); + assert_eq!(config.thread_mode, ThreadMode::Sequential); assert_eq!(config.time_cost, 3); assert_eq!(config.variant, Variant::Argon2id); assert_eq!(config.version, Version::Version13); diff --git a/src/context.rs b/src/context.rs index 390db1f..c777aa2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -118,6 +118,7 @@ mod tests { use crate::config::Config; use crate::context::Context; use crate::error::Error; + use crate::thread_mode::ThreadMode; use crate::variant::Variant; use crate::version::Version; @@ -129,6 +130,7 @@ mod tests { lanes: 4, mem_cost: 4096, secret: b"secret", + thread_mode: ThreadMode::Sequential, time_cost: 3, variant: Variant::Argon2i, version: Version::Version13, diff --git a/src/core.rs b/src/core.rs index 92aa4ba..1c99830 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,13 +6,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![warn(unsafe_op_in_unsafe_fn)] + use crate::block::Block; use crate::common; use crate::context::Context; -use crate::memory::Memory; +use crate::memory::{Memory, UnsafeBlocks}; use crate::variant::Variant; use crate::version::Version; use blake2b_simd::Params; +#[cfg(feature = "crossbeam-utils")] +use crossbeam_utils::thread::scope; /// Position of the block currently being operated on. #[derive(Clone, Debug)] @@ -29,8 +33,18 @@ pub fn initialize(context: &Context, memory: &mut Memory) { } /// Fills all the memory blocks. -pub fn fill_memory_blocks(context: &Context, memory: &mut Memory) { - fill_memory_blocks_st(context, memory); +/// +/// # Safety +/// +/// The provided `context` and `memory` arguments must be consistent. +pub unsafe fn fill_memory_blocks(context: &Context, memory: &mut Memory) { + if context.config.uses_sequential() { + // SAFETY: adhering to the safety contract is delegated to the caller. + unsafe { fill_memory_blocks_st(context, memory) }; + } else { + // SAFETY: adhering to the safety contract is delegated to the caller. + unsafe { fill_memory_blocks_mt(context, memory) }; + } } /// Calculates the final hash and returns it. @@ -177,7 +191,45 @@ fn fill_first_blocks(context: &Context, memory: &mut Memory, h0: &mut [u8]) { } } -fn fill_memory_blocks_st(context: &Context, memory: &mut Memory) { +/// # Safety +/// +/// The provided `context` and `memory` arguments must be consistent. +#[cfg(feature = "crossbeam-utils")] +unsafe fn fill_memory_blocks_mt(context: &Context, memory: &mut Memory) { + let mem = &memory.as_unsafe_blocks(); + for p in 0..context.config.time_cost { + for s in 0..common::SYNC_POINTS { + let _ = scope(|scoped| { + for l in 0..context.config.lanes { + let position = Position { + pass: p, + lane: l, + slice: s, + index: 0, + }; + scoped.spawn(move |_| { + // SAFETY: segments are processed slicewise and then each is handed + // (exactly once) to a worker thread. The threads synchronize when the + // `scope` is dropped, before the next slice is processed. The caller + // promises that `context` and `memory` are consistent, and `position` + // reflects the current lane and slice. + unsafe { fill_segment(context, &position, mem) }; + }); + } + }); + } + } +} + +#[cfg(not(feature = "crossbeam-utils"))] +unsafe fn fill_memory_blocks_mt(_: &Context, _: &mut Memory) { + unimplemented!() +} + +/// # Safety +/// +/// The provided `context` and `memory` arguments must be consistent. +unsafe fn fill_memory_blocks_st(context: &Context, memory: &mut Memory) { for p in 0..context.config.time_cost { for s in 0..common::SYNC_POINTS { for l in 0..context.config.lanes { @@ -187,13 +239,26 @@ fn fill_memory_blocks_st(context: &Context, memory: &mut Memory) { slice: s, index: 0, }; - fill_segment(context, &position, memory); + // SAFETY: segments are processed slicewise and then sequentially, the caller + // promises that `context` and `memory` are consistent, and `position` reflects the + // current lane and slice. + unsafe { fill_segment(context, &position, &memory.as_unsafe_blocks()) }; } } } } -fn fill_segment(context: &Context, position: &Position, memory: &mut Memory) { +/// # Safety +/// +/// The memory must be processed slicewise, and this function must be called exactly once per +/// segment, where a segment is the intersection between the slice being processed and a lane. +/// That is, within a slice, this function is called exactly once per lane. +/// +/// If segments are filled in parallel, synchronization points are required between slices. +/// +/// Finally, the provided `context` and `memory` arguments must be consistent, and `position` must +/// reflect the correct current lane and slice. +unsafe fn fill_segment(context: &Context, position: &Position, memory: &UnsafeBlocks) { let mut position = position.clone(); let data_independent_addressing = (context.config.variant == Variant::Argon2i) || (context.config.variant == Variant::Argon2id && position.pass == 0) @@ -248,7 +313,11 @@ fn fill_segment(context: &Context, position: &Position, memory: &mut Memory) { } pseudo_rand = address_block[(i % common::ADDRESSES_IN_BLOCK) as usize]; } else { - pseudo_rand = memory[prev_offset][0]; + assert!(prev_offset < context.memory_blocks); + assert!(prev_offset / context.lane_length == position.lane); + // SAFETY: `prev_offset` is in bounds and on this lane, so the block isn't mutably + // aliased or mutated from another thread (in the current slice). + pseudo_rand = unsafe { memory.get_unchecked(prev_offset as usize) }[0]; } // 1.2.2 Computing the lane of the reference block @@ -267,18 +336,33 @@ fn fill_segment(context: &Context, position: &Position, memory: &mut Memory) { // 2 Creating a new block let index = context.lane_length as u64 * ref_lane + ref_index as u64; - let mut curr_block = memory[curr_offset].clone(); - { - let prev_block = &memory[prev_offset]; - let ref_block = &memory[index]; - if context.config.version == Version::Version10 || position.pass == 0 { - fill_block(prev_block, ref_block, &mut curr_block, false); - } else { - fill_block(prev_block, ref_block, &mut curr_block, true); - } + assert!(curr_offset < context.memory_blocks); + assert!(prev_offset < context.memory_blocks); + assert!(index < context.memory_blocks as u64); + assert!(curr_offset != prev_offset && (curr_offset as u64) != index); + assert!(prev_offset / context.lane_length == position.lane); + assert!( + index / (context.lane_length as u64) == position.lane as u64 + || index % (context.lane_length as u64) / (context.segment_length as u64) + != position.slice as u64 + ); + // SAFETY: `curr_offset`, `prev_offset` and `index` are in bounds and refer to different + // blocks; and during the processing of the current slice: + // - `curr_offset` is only accessed by this thread, and there are no other references to + // the corresponding block; + // - `prev_offset` is on the same lane as `curr_offset`, and therefore the corresponding + // block isn't mutably aliased or mutated from another thread; + // - `index` is either on the same lane or on a different slice, and in both cases it will + // not be mutably aliased or mutated from another thread. + let curr_block = unsafe { memory.get_mut_unchecked(curr_offset as usize) }; + let prev_block = unsafe { memory.get_unchecked(prev_offset as usize) }; + let ref_block = unsafe { memory.get_unchecked(index as usize) }; + if context.config.version == Version::Version10 || position.pass == 0 { + fill_block(prev_block, ref_block, curr_block, false); + } else { + fill_block(prev_block, ref_block, curr_block, true); } - memory[curr_offset] = curr_block; curr_offset += 1; prev_offset += 1; } diff --git a/src/encoding.rs b/src/encoding.rs index 3843c99..683b799 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -158,11 +158,17 @@ pub fn num_len(number: u32) -> u32 { #[cfg(test)] mod tests { + #[cfg(feature = "crossbeam-utils")] use crate::config::Config; + #[cfg(feature = "crossbeam-utils")] use crate::context::Context; use crate::decoded::Decoded; - use crate::encoding::{base64_len, decode_string, encode_string, num_len}; + #[cfg(feature = "crossbeam-utils")] + use crate::encoding::encode_string; + use crate::encoding::{base64_len, decode_string, num_len}; use crate::error::Error; + #[cfg(feature = "crossbeam-utils")] + use crate::thread_mode::ThreadMode; use crate::variant::Variant; use crate::version::Version; @@ -357,6 +363,7 @@ mod tests { assert_eq!(result, Err(Error::DecodingFail)); } + #[cfg(feature = "crossbeam-utils")] #[test] fn encode_string_returns_correct_string() { let hash = b"12345678901234567890123456789012".to_vec(); @@ -366,6 +373,7 @@ mod tests { lanes: 1, mem_cost: 4096, secret: &[], + thread_mode: ThreadMode::Parallel, time_cost: 3, variant: Variant::Argon2i, version: Version::Version13, diff --git a/src/lib.rs b/src/lib.rs index 43753b7..10bed0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ //! Create a password hash with custom settings and verify it: //! //! ```rust -//! use argon2::{self, Config, Variant, Version}; +//! use argon2::{self, Config, ThreadMode, Variant, Version}; //! //! let password = b"password"; //! let salt = b"othersalt"; @@ -53,7 +53,16 @@ //! version: Version::Version13, //! mem_cost: 65536, //! time_cost: 10, -//! lanes: 4, +#![cfg_attr(feature = "crossbeam-utils", doc = " lanes: 4,")] +#![cfg_attr( + feature = "crossbeam-utils", + doc = " thread_mode: ThreadMode::Parallel," +)] +#![cfg_attr(not(feature = "crossbeam-utils"), doc = " lanes: 1,")] +#![cfg_attr( + not(feature = "crossbeam-utils"), + doc = " thread_mode: ThreadMode::Sequential," +)] //! secret: &[], //! ad: &[], //! hash_length: 32 @@ -85,6 +94,7 @@ mod encoding; mod error; mod memory; mod result; +mod thread_mode; mod variant; mod version; @@ -92,5 +102,6 @@ pub use crate::argon2::*; pub use crate::config::Config; pub use crate::error::Error; pub use crate::result::Result; +pub use crate::thread_mode::ThreadMode; pub use crate::variant::Variant; pub use crate::version::Version; diff --git a/src/memory.rs b/src/memory.rs index a7b1756..fdfb132 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -6,10 +6,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![warn(unsafe_op_in_unsafe_fn)] + use crate::block::Block; use std::fmt; use std::fmt::Debug; +use std::marker::PhantomData; use std::ops::{Index, IndexMut}; +use std::ptr::NonNull; /// Structure representing the memory matrix. pub struct Memory { @@ -32,8 +36,72 @@ impl Memory { let blocks = vec![Block::zero(); total].into_boxed_slice(); Memory { rows, cols, blocks } } + + /// Returns a pointer to the flat array of blocks useful for parallel disjoint access. + pub fn as_unsafe_blocks(&mut self) -> UnsafeBlocks<'_> { + UnsafeBlocks { + blocks: NonNull::new(self.blocks.as_mut_ptr()).unwrap(), + phantom: PhantomData, + } + } } +/// A wrapped raw pointer to the flat array of blocks, useful for parallel disjoint access. +/// +/// All operations on this type are unchecked and require `unsafe`. Callers are responsible for +/// accessing the blocks without violating the aliasing rules or resulting in data races. +pub struct UnsafeBlocks<'a> { + blocks: NonNull, + phantom: PhantomData<&'a [Block]>, +} + +impl UnsafeBlocks<'_> { + /// Get a shared reference to the `Block` at `index`. + /// + /// # Safety + /// + /// The caller must ensure that `index` is in bounds, no mutable references exist or are + /// created to the corresponding block, and no data races happen while the returned reference + /// lives. + pub unsafe fn get_unchecked(&self, index: usize) -> &Block { + // SAFETY: the caller promises that the `index` is in bounds; therefore, we're within the + // bounds of the allocated object, and the offset in bytes fits in an `isize`. + let ptr = unsafe { self.blocks.add(index) }; + // SAFETY: the caller promises that there are no mutable references to the requested + // `Block` or data races; and `ptr` points to a valid and aligned `Block`. + unsafe { ptr.as_ref() } + } + + /// Get a mutable reference to the `Block` at `index`. + /// + /// # Safety + /// + /// The caller must ensure that `index` is in bounds, no other references exist or are created + /// to the corresponding block, and no data races happen while the returned reference lives. + #[allow(clippy::mut_from_ref)] + pub unsafe fn get_mut_unchecked(&self, index: usize) -> &mut Block { + // SAFETY: the caller promises that the `index` is in bounds; therefore, we're within + // the bounds of the allocated object, and the offset in bytes fits in an `isize`. + let mut ptr = unsafe { self.blocks.add(index) }; + // SAFETY: the caller promises that there are no other references, accesses, or data races + // affecting the target `Block`; and `ptr` points to a valid and aligned `Block`. + // + // Also note that this isn't interior mutability, as we're going through a `NonNull` + // pointer derived from a mutable reference (and interior mutability is not transitive + // through raw pointers[1][2][3]). + // + // [1]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#interior-mutability + // [2]: https://doc.rust-lang.org/stable/reference/behavior-considered-undefined.html?highlight=undefin#r-undefined.immutable + // [3]: https://github.com/rust-lang/reference/issues/1227 + unsafe { ptr.as_mut() } + } +} + +// SAFETY: passing or sharing an `UnsafeBlocks` accross threads is, in itself, safe (calling any +// methods on it isn't, but they already require `unsafe`). +unsafe impl Send for UnsafeBlocks<'_> {} +unsafe impl Sync for UnsafeBlocks<'_> {} + impl Debug for Memory { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Memory {{ rows: {}, cols: {} }}", self.rows, self.cols) diff --git a/src/thread_mode.rs b/src/thread_mode.rs new file mode 100644 index 0000000..6259eb6 --- /dev/null +++ b/src/thread_mode.rs @@ -0,0 +1,64 @@ +// Copyright (c) 2017 Xidorn Quan +// Copyright (c) 2017 Martijn Rijkeboer +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// The thread mode used to perform the hashing. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ThreadMode { + /// Run in one thread. + Sequential, + + #[cfg(feature = "crossbeam-utils")] + /// Run in the same number of threads as the number of lanes. + Parallel, +} + +impl ThreadMode { + #[cfg(feature = "crossbeam-utils")] + /// Create a thread mode from the threads count. + pub fn from_threads(threads: u32) -> ThreadMode { + if threads > 1 { + ThreadMode::Parallel + } else { + ThreadMode::Sequential + } + } + + #[cfg(not(feature = "crossbeam-utils"))] + pub fn from_threads(threads: u32) -> ThreadMode { + assert_eq!(threads, 1); + Self::default() + } +} + +impl Default for ThreadMode { + fn default() -> ThreadMode { + ThreadMode::Sequential + } +} + +#[cfg(test)] +mod tests { + + use crate::thread_mode::ThreadMode; + + #[test] + fn default_returns_correct_thread_mode() { + assert_eq!(ThreadMode::default(), ThreadMode::Sequential); + } + + #[cfg(feature = "crossbeam-utils")] + #[test] + fn from_threads_returns_correct_thread_mode() { + assert_eq!(ThreadMode::from_threads(0), ThreadMode::Sequential); + assert_eq!(ThreadMode::from_threads(1), ThreadMode::Sequential); + assert_eq!(ThreadMode::from_threads(2), ThreadMode::Parallel); + assert_eq!(ThreadMode::from_threads(10), ThreadMode::Parallel); + assert_eq!(ThreadMode::from_threads(100), ThreadMode::Parallel); + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 80792be..0d6a3ce 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -8,7 +8,7 @@ // These tests are based on Argon's test.c test suite. -use argon2::{Config, Error, Variant, Version}; +use argon2::{Config, Error, ThreadMode, Variant, Version}; use hex::ToHex; #[cfg(not(debug_assertions))] @@ -1014,6 +1014,7 @@ fn test_hash_raw_with_not_enough_memory() { mem_cost: 2, time_cost: 2, lanes: 1, + thread_mode: ThreadMode::Sequential, secret: &[], ad: &[], hash_length: 32, @@ -1032,6 +1033,7 @@ fn test_hash_raw_with_too_short_salt() { mem_cost: 2048, time_cost: 2, lanes: 1, + thread_mode: ThreadMode::Sequential, secret: &[], ad: &[], hash_length: 32, @@ -1051,12 +1053,14 @@ fn hash_test( hex: &str, enc: &str, ) { + let threads = if cfg!(feature = "crossbeam-utils") { p } else { 1 }; let config = Config { variant: var, version: ver, mem_cost: m, time_cost: t, lanes: p, + thread_mode: ThreadMode::from_threads(threads), secret: &[], ad: &[], hash_length: 32,