Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions crates/bcrypt/RUSTSEC-0000-0000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
```toml
[advisory]
id = "RUSTSEC-0000-0000"
package = "bcrypt"
date = "2026-06-20"
url = "https://github.com/Keats/rust-bcrypt/pull/103"
references = [
"https://github.com/Keats/rust-bcrypt/issues/62",
"https://github.com/Keats/rust-bcrypt/pull/95",
"https://github.com/Keats/rust-bcrypt/commit/f5f1ee2862c1198a85afe3c2f8cd80835162b7e9",
]
categories = ["denial-of-service"]
cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
keywords = ["panic", "utf-8", "denial-of-service"]

[versions]
patched = [">= 0.19.2"]
unaffected = ["< 0.19.0"]
```

# Panic in `bcrypt::verify` on non-ASCII hash input

`bcrypt::verify(password, hash)` and `HashParts::from_str(hash)` panic in
`str::slice_error_fail` when given a 60-byte `&str` containing a multi-byte
UTF-8 character at certain byte positions.

## Impact

Any Rust code that calls `bcrypt::verify` (or `HashParts::from_str`) with an
attacker-controlled hash string will panic. The `bcrypt` crate is
`#![forbid(unsafe_code)]`, so this is limited to a denial-of-service and
cannot lead to memory corruption.

Realistic attack contexts include:

- Rust authentication services reading hashes from a database that was
previously compromised via, e.g., SQL injection. The attacker can then
crash the service on every login attempt against the tampered account.
- CLI tools accepting hashes from stdin or command-line arguments.
- Password managers or vault services loading hashes from untrusted
configuration sources.

## Root cause

`split_hash` performed five `&str` slicing operations on the input hash:
`&hash[1..3]`, `&hash[4..6]`, `&hash[7..]`, `&salt_and_hash[..22]`, and
`&salt_and_hash[22..]`. None of these were char-boundary-checked. Any input
where a multi-byte UTF-8 character spanned one of those byte positions
caused a panic.

This is a regression of the fix originally shipped in 2021 for issue #62
(commit `0833509`). The regression was introduced in the parser rewrite in
PR #95 (commit `e9a8394`, released as 0.19.0).

The pre-existing regression test `does_no_error_on_char_boundary_splitting`
was not removed, but was silently rendered ineffective by the new
`bytes[0] != b'$'` guard, which rejected its input earlier and prevented
it from reaching the buggy slices — leaving CI green through the regression.

## Fix

`split_hash` now rejects any hash string containing non-ASCII bytes up
front. A valid bcrypt hash is always exactly 60 ASCII bytes, so this
closes the entire class of byte-boundary panics rather than guarding each
slice individually.

The fix was merged in PR #103 and released as `bcrypt 0.19.2` on 2026-06-20.

## Downstream impact

`pyca/bcrypt` (which depended on `bcrypt 0.19.1`) is **not** affected. Its
Python-side wrapper performs its own byte-level salt parsing before
invoking `bcrypt::hash_with_salt`, and never reaches the buggy code path
in `split_hash` or `verify`.