Skip to content

Reject non-ASCII bytes in hash string to prevent panic (regression of #62)#103

Open
MuhammedHussein17 wants to merge 2 commits into
Keats:masterfrom
MuhammedHussein17:fix/utf8-panic-in-split-hash
Open

Reject non-ASCII bytes in hash string to prevent panic (regression of #62)#103
MuhammedHussein17 wants to merge 2 commits into
Keats:masterfrom
MuhammedHussein17:fix/utf8-panic-in-split-hash

Conversation

@MuhammedHussein17

Copy link
Copy Markdown

Per private email discussion with @Keats — opening this as a regular bug fix.

Summary

bcrypt::verify / HashParts::from_str panic in str::slice_error_fail
when given a 60-byte &str containing a multi-byte UTF-8 character at
certain positions. This is a regression of the fix shipped in 2021 for
issue #62 (commit 0833509), which was lost during the parser rewrite
in PR #95 (e9a8394, released as 0.19.0).

Root cause

split_hash performs five &str slicing operations on the input:
&hash[1..3], &hash[4..6], &hash[7..], &salt_and_hash[..22], and
&salt_and_hash[22..]. None of these are boundary-checked. Any input
where a multi-byte UTF-8 character spans one of those boundaries panics.

The previous parser had an is_char_boundary(22) guard. After the rewrite,
the existing regression test does_no_error_on_char_boundary_splitting
still exists but is rejected earlier by the new bytes[0] != b'$' check
and never actually reaches the buggy slices — which is why CI was green
on the regression.

Fix

Reject non-ASCII bytes in the hash string up front in split_hash. A
valid bcrypt hash is always 60 ASCII bytes, so this closes the entire
class rather than guarding each slice individually.

Tests

Two new tests added (both fail on master, pass with this change):

  • verify_rejects_multibyte_utf8_in_hash — end-to-end via verify()
  • split_hash_rejects_non_ascii — direct parser-level assertion

The pre-existing does_no_error_on_char_boundary_splitting test is
kept as-is.

Bonus: fuzz target was misnamed

fuzz/fuzz_targets/verify.rs was calling bcrypt::hash instead of
bcrypt::verify, so the hash-string parser surface was never actually
fuzzed. Fixed in the second commit. With the fix, future regressions
of this class should be caught by coverage-guided fuzzing.

Affects

  • 0.19.0 — yes
  • 0.19.1 — yes (current)
  • 0.18.0 and earlier — no

The target was calling bcrypt::hash() instead of bcrypt::verify(),
which meant the hash-string parser surface was never fuzzed. This is
why the Keats#62 regression in Keats#95 wasn't caught by CI.
…eats#62)

A valid bcrypt hash is always 60 ASCII bytes. The parser rewrite in Keats#95
   removed the is_char_boundary check that Keats#62's fix introduced. Rejecting
   non-ASCII up front in split_hash closes the entire class of byte-boundary
   panics across all five &str slices in the parser.

   Adds two regression tests:
   - verify_rejects_multibyte_utf8_in_hash: end-to-end via verify()
   - split_hash_rejects_non_ascii: direct parser-level test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant