Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🦀 crates.io
Not in CISA KEV

GHSA-h37v-hp6w-2pp8 ml-dsa

Fix: RustCrypto/signatures@10f4ff0

GHSA-h37v-hp6w-2pp8 is a security vulnerability in ml-dsa. A fix is available for ml-dsa — see the affected versions and patch details below.

ml-dsa's UseHint function has off by two error when r0 equals zero

Published
Feb 2, 2026
Updated
Feb 3, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Feb 3, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🦀ml-dsa

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects crates.io packages — download data is not available via public APIs for these ecosystems.

Description

Summary

There's a bug in the use_hint function where it adds 1 instead of subtracting 1 when the decomposed low bits r0 equal exactly zero. FIPS 204 Algorithm 40 is pretty clear that r0 > 0 means strictly positive, but the current code treats zero as positive. This causes valid signatures to potentially fail verification when this edge case gets hit.

Details

The issue is in ml-dsa/src/hint.rs in the use_hint function. Here's what FIPS 204 Algorithm 40 says:

3: if h = 1 and r0 > 0  return (r1 + 1) mod m
4: if h = 1 and r0 <= 0  return (r1 − 1) mod m

Line 3 uses r0 > 0 (strictly greater than zero), and line 4 uses r0 <= 0 (less than or equal, which includes zero). So when r0 = 0, the spec says to subtract 1.

But the current implementation does this:

if h && r0.0 <= gamma2 {
    Elem::new((r1.0 + 1) % m)
} else if h && r0.0 >= BaseField::Q - gamma2 {
    Elem::new((r1.0 + m - 1) % m)
}

The problem is r0.0 <= gamma2 includes zero. When r0 = 0, this condition is true (since 0 <= gamma2), so it adds 1. But according to the spec, r0 = 0 should fall into the r0 <= 0 case and subtract 1 instead.

The result is +1 when it should be -1, which is an off by two error mod m.

PoC

Take MLDSA 44 where γ2 = 95,232 and m = 44.

If use_hint(true, 0) is called:

  • Decompose(0) gives (r1=0, r0=0)
  • The condition r0.0 <= gamma2 is 0 <= 95232 which is true
  • So it returns (0 + 1) % 44 = 1

But FIPS 204 says:

  • r0 > 0 is 0 > 0 which is false
  • r0 ≤ 0 is 0 ≤ 0 which is true
  • So it should return (0 - 1) mod 44 = 43

The function returns 1 when it should return 43.

This can happen in real signatures whenever any coefficient of the w' vector happens to be a multiple of 2γ2, which makes its decomposed r0 equal zero. It's not super common but it's definitely possible, and when it hits, verification will fail for a completely valid signature.

Impact

This is a FIPS 204 compliance bug that affects signature verification. When the edge case triggers, valid signatures get rejected. Since MLDSA is supposed to be used for high security post quantum cryptography, having verification randomly fail isn't great. It's also theoretically possible that the mismatch between what signing expects and what verification does could be exploited somehow, though that would need more looking into.

The fix is straightforward, just change the condition to explicitly check for positive values:

if h && r0.0 > 0 && r0.0 <= gamma2 {
    Elem::new((r1.0 + 1) % m)
} else if h {
    Elem::new((r1.0 + m - 1) % m)
}

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🦀crates.ioml-dsaall versions0.1.0-rc.5cargo update -p ml-dsa --precise 0.1.0-rc.5

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ml-dsa, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update ml-dsa to 0.1.0-rc.5 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-h37v-hp6w-2pp8 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-h37v-hp6w-2pp8 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-h37v-hp6w-2pp8. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary There's a bug in the `use_hint` function where it adds 1 instead of subtracting 1 when the decomposed low bits `r0` equal exactly zero. FIPS 204 Algorithm 40 is pretty clear that `r0 > 0` means strictly positive, but the current code treats zero as positive. This causes valid signatures to potentially fail verification when this edge case gets hit. ### Details The issue is in `ml-dsa/src/hint.rs` in the `use_hint` function. Here's what FIPS 204 Algorithm 40 says: ``` 3: if h = 1 and r0 > 0 return (r1 + 1) mod m 4: if h = 1 and r0 <= 0 return (r1 − 1) mod m ``` Line 3 uses `r
O3 Security · Impact-Aware SCA

Is GHSA-h37v-hp6w-2pp8 in your dependencies?

O3 Security finds GHSA-h37v-hp6w-2pp8 across crates.io dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-h37v-hp6w-2pp8: ml-dsa | O3 Security