From a65d634651a3369fb7f5f60c06c3af2666332db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 23 Oct 2024 07:49:25 +0300 Subject: [PATCH] Split fuzz targets --- fuzz/Cargo.toml | 21 +++- fuzz/LICENSE-MIT | 18 +--- fuzz/fuzz_targets/scrypt_fuzzer.rs | 141 ------------------------- fuzz/fuzz_targets/scrypt_hash.rs | 12 +++ fuzz/fuzz_targets/scrypt_phc_hash.rs | 22 ++++ fuzz/fuzz_targets/scrypt_phc_verify.rs | 13 +++ fuzz/src/lib.rs | 16 +++ 7 files changed, 82 insertions(+), 161 deletions(-) delete mode 100644 fuzz/fuzz_targets/scrypt_fuzzer.rs create mode 100644 fuzz/fuzz_targets/scrypt_hash.rs create mode 100644 fuzz/fuzz_targets/scrypt_phc_hash.rs create mode 100644 fuzz/fuzz_targets/scrypt_phc_verify.rs create mode 100644 fuzz/src/lib.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d852794b..c5b805db 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "scrypt-fuzz" +name = "fuzz" version = "0.0.0" publish = false edition = "2018" @@ -9,12 +9,25 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -base16ct = { version = "0.2", features = ["alloc"] } scrypt = { path = "../scrypt", features = ["simple"]} [[bin]] -name = "scrypt_fuzzer" -path = "fuzz_targets/scrypt_fuzzer.rs" +name = "scrypt_hash" +path = "fuzz_targets/scrypt_hash.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "scrypt_phc_verify" +path = "fuzz_targets/scrypt_phc_verify.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "scrypt_phc_hash" +path = "fuzz_targets/scrypt_phc_hash.rs" test = false doc = false bench = false diff --git a/fuzz/LICENSE-MIT b/fuzz/LICENSE-MIT index ca6363d7..86fd6862 100644 --- a/fuzz/LICENSE-MIT +++ b/fuzz/LICENSE-MIT @@ -1,4 +1,5 @@ -Copyright (c) 2021-2024 The RustCrypto Project Developers +Copyright (c) 2024 The RustCrypto Project Developers +Copyright (c) 2024 Google LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -23,18 +24,3 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - -Copyright (c) 2024 Google LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/fuzz/fuzz_targets/scrypt_fuzzer.rs b/fuzz/fuzz_targets/scrypt_fuzzer.rs deleted file mode 100644 index 3be88800..00000000 --- a/fuzz/fuzz_targets/scrypt_fuzzer.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![no_main] - -use libfuzzer_sys::fuzz_target; -use scrypt::{scrypt, Params, Scrypt}; -use scrypt::password_hash::{Ident, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; - -const SAMPLE_HASH: &str = - "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E"; - -// Generate random params -fn fuzzed_params(data: &[u8]) -> Option { - if data.len() >= 8 { - let log_n = data[0] % 16; // Cap log_n to 16 - let r = u32::from_le_bytes([data[1], data[2], data[3], 0]) % 32; // Cap r to a reasonable value like 32 - let p = if data.len() > 4 { - u32::from_le_bytes([data[4], data[5], data[6], 0]) % 16 - } else { - 1 - }; - let len = if data.len() > 7 { - data[7] as usize % 65 - } else { - 32 - }; - - Params::new(log_n, r, p, len).ok() - } else { - None - } -} - -// Generate random salt value -fn fuzzed_salt(data: &[u8]) -> Option { - let salt_data = if data.len() >= 16 { &data[..16] } else { data }; - SaltString::encode_b64(salt_data).ok() -} - -// Validate the salt string -fn validate_salt(salt_str: &str) -> bool { - // Check length - let length = salt_str.len(); - if !(4..=64).contains(&length) { - return false; - } - - for char in salt_str.chars() { - if !matches!(char, 'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '+' | '.' | '-') { - return false; - } - } - - true -} - -// Prepare random data by splitting random data -fn split_fuzz_data<'a>(data: &'a [u8], splits: &[usize]) -> Vec<&'a [u8]> { - let mut result = Vec::new(); - let mut start = 0; - - for &split in splits { - if start + split <= data.len() { - result.push(&data[start..start + split]); - start += split; - } else { - result.push(&data[start..]); - break; - } - } - - result -} - -fuzz_target!(|data: &[u8]| { - let params = fuzzed_params(data).unwrap_or_else(|| Params::new(16, 8, 1, 64).unwrap()); - let splits = split_fuzz_data(data, &[32, 32, 32]); - let password = splits.first().unwrap_or(&data); - let salt = splits.get(1).unwrap_or(&data); - let mut result = vec![0u8; 256]; - - if let Some(salt_string) = fuzzed_salt(salt) { - if !validate_salt(salt_string.as_str()) { - return; - } - - let salt_value = salt_string.as_salt(); // Safe to use now - - let formatted_hash = format!("$scrypt$ln=16,r=8,p=1${}$invalid$", base16ct::lower::encode_string(password)); - - if let Ok(hash) = - PasswordHash::new(SAMPLE_HASH).or_else(|_| PasswordHash::new(formatted_hash.as_str())) - { - // Randomly choose the fuzz target function - let target_selector = if !data.is_empty() { data[0] % 5 } else { 0 }; - match target_selector { - 0 => { - let _ = scrypt(password, salt, ¶ms, &mut result); - } - 1 => { - let _ = Scrypt.verify_password(password, &hash).is_err(); - } - 2 => { - let _ = Scrypt.hash_password_customized( - password, - Some(Ident::new_unwrap("scrypt")), - None, - params, - salt_value, - ); - } - 3 => { - if let Some(random_params) = fuzzed_params(password) { - let _ = scrypt(password, salt, &random_params, &mut result); - } - } - 4 => { - let _ = PasswordHash::new(SAMPLE_HASH).is_ok(); - } - _ => { - let _ = scrypt(password, salt, ¶ms, &mut result); - } - } - } - } else { - // Skip this iteration if the salt is invalid - return; - } -}); diff --git a/fuzz/fuzz_targets/scrypt_hash.rs b/fuzz/fuzz_targets/scrypt_hash.rs new file mode 100644 index 00000000..03ec1cea --- /dev/null +++ b/fuzz/fuzz_targets/scrypt_hash.rs @@ -0,0 +1,12 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use scrypt::scrypt; + +use fuzz::ScryptRandParams; + +fuzz_target!(|data: (&[u8], &[u8], ScryptRandParams)| { + let (password, salt, ScryptRandParams(params)) = data; + let mut result = [0u8; 64]; + let res = scrypt(password, salt, ¶ms, &mut result); + assert!(res.is_ok()); +}); diff --git a/fuzz/fuzz_targets/scrypt_phc_hash.rs b/fuzz/fuzz_targets/scrypt_phc_hash.rs new file mode 100644 index 00000000..39c30408 --- /dev/null +++ b/fuzz/fuzz_targets/scrypt_phc_hash.rs @@ -0,0 +1,22 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use scrypt::password_hash::{Ident, PasswordHasher, Salt, SaltString}; +use scrypt::Scrypt; + +use fuzz::ScryptRandParams; + +fuzz_target!(|data: (&[u8], &[u8], ScryptRandParams)| { + let (password, salt, ScryptRandParams(params)) = data; + if salt.len() < Salt::MIN_LENGTH { + return; + } + let salt_string = SaltString::encode_b64(salt).unwrap(); + let res = Scrypt.hash_password_customized( + password, + Some(Ident::new_unwrap("scrypt")), + None, + params, + &salt_string, + ); + assert!(res.is_ok()); +}); diff --git a/fuzz/fuzz_targets/scrypt_phc_verify.rs b/fuzz/fuzz_targets/scrypt_phc_verify.rs new file mode 100644 index 00000000..4052d9f6 --- /dev/null +++ b/fuzz/fuzz_targets/scrypt_phc_verify.rs @@ -0,0 +1,13 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use scrypt::password_hash::{PasswordHash, PasswordVerifier}; +use scrypt::Scrypt; + +const SAMPLE_HASH: &str = "$scrypt$ln=16,r=8,p=1$\ + aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E"; + +fuzz_target!(|password: &[u8]| { + let hash = PasswordHash::new(SAMPLE_HASH).expect("SAMPLE_HASH is valid"); + let res = Scrypt.verify_password(password, &hash); + assert!(res.is_err()); +}); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 00000000..1ba2c8a4 --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,16 @@ +use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; + +#[derive(Debug)] +pub struct ScryptRandParams(pub scrypt::Params); + +impl<'a> Arbitrary<'a> for ScryptRandParams { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let log_n = u.int_in_range(0..=15)?; + let r = u.int_in_range(1..=32)?; + let p = u.int_in_range(1..=16)?; + let len = u.int_in_range(10..=64)?; + + let params = scrypt::Params::new(log_n, r, p, len).unwrap(); + Ok(Self(params)) + } +}