From 54c2a733fe3f1cbdaa4d124a1d8b58e373255b45 Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:02:36 +0000 Subject: [PATCH] =?UTF-8?q?password=5Fhash:=20reject=20a=20PBKDF2=20creden?= =?UTF-8?q?tial=20with=20an=20absurd=20iteration=20count=20(>10M)=20instea?= =?UTF-8?q?d=20of=20running=20it=20=E2=80=94=20the=20count=20is=20read=20s?= =?UTF-8?q?traight=20from=20the=20stored=20string,=20so=20a=20corrupt/host?= =?UTF-8?q?ile=20credential=20(e.g.=20via=20a=20compromised=20accounts=20b?= =?UTF-8?q?ackend)=20could=20pin=20a=20worker=20thread=20for=20a=20very=20?= =?UTF-8?q?long=20time;=20legitimate=20work=20factors=20are=20far=20below?= =?UTF-8?q?=20the=20cap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/password_hash.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/password_hash.rs b/src/modules/password_hash.rs index d1d46e9..bff9deb 100644 --- a/src/modules/password_hash.rs +++ b/src/modules/password_hash.rs @@ -58,6 +58,11 @@ pub fn ct_eq(a: &[u8], b: &[u8]) -> bool { a.len() == b.len() && openssl::memcmp::eq(a, b) } +/// Upper bound on PBKDF2 iterations honoured from a stored credential. Legitimate +/// work factors are well under this; a larger value (from a corrupt or hostile +/// credential string) would pin a worker thread, so we reject it instead. +const MAX_PBKDF2_ITERS: usize = 10_000_000; + /// PBKDF2-HMAC-SHA256 of `pass` with `salt` and `iters`, `len` bytes out. fn pbkdf2(pass: &str, salt: &[u8], iters: usize, len: usize) -> Option> { let mut out = vec![0u8; len]; @@ -86,6 +91,9 @@ pub fn verify(stored: &str, plaintext: &str) -> bool { if let (Ok(iters), Some(salt), Some(want)) = (parts[0].parse::(), unhex(parts[1]), unhex(parts[2])) { + if !(1..=MAX_PBKDF2_ITERS).contains(&iters) { + return false; // absurd/zero work factor — refuse, don't compute + } if let Some(got) = pbkdf2(plaintext, &salt, iters, want.len()) { return ct_eq(&got, &want); }