rustsec-2026-0199
Vulnerability from osv_rustsec
Published
2026-06-20 12:00
Modified
2026-07-04 08:15
Summary
Panic in `bcrypt::verify` on non-ASCII hash input
Details

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.


{
  "affected": [
    {
      "database_specific": {
        "categories": [
          "denial-of-service"
        ],
        "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
        "informational": null
      },
      "ecosystem_specific": {
        "affected_functions": null,
        "affects": {
          "arch": [],
          "functions": [],
          "os": []
        }
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "bcrypt",
        "purl": "pkg:cargo/bcrypt"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.19.0"
            },
            {
              "fixed": "0.19.2"
            }
          ],
          "type": "SEMVER"
        }
      ],
      "versions": []
    }
  ],
  "aliases": [],
  "database_specific": {
    "license": "CC0-1.0"
  },
  "details": "`bcrypt::verify(password, hash)` and `HashParts::from_str(hash)` panic in\n`str::slice_error_fail` when given a 60-byte `\u0026str` containing a multi-byte\nUTF-8 character at certain byte positions.\n\n## Impact\n\nAny Rust code that calls `bcrypt::verify` (or `HashParts::from_str`) with an\nattacker-controlled hash string will panic. The `bcrypt` crate is\n`#![forbid(unsafe_code)]`, so this is limited to a denial-of-service and\ncannot lead to memory corruption.\n\nRealistic attack contexts include:\n\n- Rust authentication services reading hashes from a database that was\n  previously compromised via, e.g., SQL injection. The attacker can then\n  crash the service on every login attempt against the tampered account.\n- CLI tools accepting hashes from stdin or command-line arguments.\n- Password managers or vault services loading hashes from untrusted\n  configuration sources.\n\n## Root cause\n\n`split_hash` performed five `\u0026str` slicing operations on the input hash:\n`\u0026hash[1..3]`, `\u0026hash[4..6]`, `\u0026hash[7..]`, `\u0026salt_and_hash[..22]`, and\n`\u0026salt_and_hash[22..]`. None of these were char-boundary-checked. Any input\nwhere a multi-byte UTF-8 character spanned one of those byte positions\ncaused a panic.\n\nThis is a regression of the fix originally shipped in 2021 for issue #62\n(commit `0833509`). The regression was introduced in the parser rewrite in\nPR #95 (commit `e9a8394`, released as 0.19.0).\n\nThe pre-existing regression test `does_no_error_on_char_boundary_splitting`\nwas not removed, but was silently rendered ineffective by the new\n`bytes[0] != b\u0027$\u0027` guard, which rejected its input earlier and prevented\nit from reaching the buggy slices \u2014 leaving CI green through the regression.\n\n## Fix\n\n`split_hash` now rejects any hash string containing non-ASCII bytes up\nfront. A valid bcrypt hash is always exactly 60 ASCII bytes, so this\ncloses the entire class of byte-boundary panics rather than guarding each\nslice individually.\n\nThe fix was merged in PR #103 and released as `bcrypt 0.19.2` on 2026-06-20.\n\n## Downstream impact\n\n`pyca/bcrypt` (which depended on `bcrypt 0.19.1`) is **not** affected. Its\nPython-side wrapper performs its own byte-level salt parsing before\ninvoking `bcrypt::hash_with_salt`, and never reaches the buggy code path\nin `split_hash` or `verify`.",
  "id": "RUSTSEC-2026-0199",
  "modified": "2026-07-04T08:15:24Z",
  "published": "2026-06-20T12:00:00Z",
  "references": [
    {
      "type": "PACKAGE",
      "url": "https://crates.io/crates/bcrypt"
    },
    {
      "type": "ADVISORY",
      "url": "https://rustsec.org/advisories/RUSTSEC-2026-0199.html"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Keats/rust-bcrypt/pull/103"
    },
    {
      "type": "REPORT",
      "url": "https://github.com/Keats/rust-bcrypt/issues/62"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Keats/rust-bcrypt/pull/95"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Keats/rust-bcrypt/commit/f5f1ee2862c1198a85afe3c2f8cd80835162b7e9"
    }
  ],
  "related": [],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Panic in `bcrypt::verify` on non-ASCII hash input"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…