CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-24850
5.30.01%

Mall-DSA: Breaking Post-Quantum Promises with a Single Typo

Alon Barad
Alon Barad
Software Engineer

Jan 28, 2026·6 min read·6 visits

PoC Available

Executive Summary (TL;DR)

The RustCrypto ML-DSA implementation allowed invalid signatures with duplicate hint indices due to a coding error (`<=` vs `<`). This creates signature malleability, allowing attackers to tweak a signature without invalidating it, effectively breaking applications that rely on signature hash uniqueness (like blockchains).

A subtle logic error in the Rust `ml-dsa` crate implementation of the Module-Lattice-Based Digital Signature Standard (ML-DSA) allowed for signature malleability. By relaxing a strict inequality check (`<`) to a non-strict one (`<=`) during hint verification, the implementation inadvertently accepted non-canonical signatures containing duplicate hint indices. While this does not compromise the private key, it breaks FIPS 204 compliance and poses significant risks to distributed ledgers and systems relying on signature uniqueness.

The Hook: When the Future Arrives Broken

We are standing on the precipice of the Post-Quantum era. The algorithms that have guarded our secrets for decades (RSA, ECC) are being retired in favor of lattice-based beasts like ML-DSA (Module-Lattice-Based Digital Signature Standard), formerly known as Dilithium. The promise is simple: math so complex even a quantum computer gets a headache trying to solve it.

But here is the cynical reality of security engineering: you can have the most theoretically sound mathematical proof in the universe, printed on gold leaf and blessed by NIST, but if the developer typing it into VS Code makes a typo, it’s game over. The ml-dsa crate, a flagship implementation in the RustCrypto ecosystem, fell victim to exactly this. It wasn't a buffer overflow (this is Rust, after all). It wasn't a use-after-free. It was a single character.

This vulnerability, CVE-2026-24850, is a classic case of "implementation divergence." The spec said one thing, the code did another, and for a brief window, the cryptographic guarantees of uniqueness were thrown out the window. It serves as a stark reminder that in cryptography, "close enough" is the same as "completely broken."

The Flaw: A Matter of Strictness

To understand this bug, we have to look at how ML-DSA handles "hints." In lattice-based cryptography, signatures involve a lot of noise handling. When verifying a signature, the verifier needs a little help to reconstruct the high-order bits of the polynomial vector. These helpers are called "hints." They are essentially indices pointing to coefficients that need adjustment.

The FIPS 204 specification (Algorithm 26, HintBitUnpack) is explicitly clear about how these hints should be encoded: the indices must be strictly increasing. That means if your first hint is at index 5, the next one must be 6 or higher. You cannot have hint 5 followed by hint 5. Mathematically, the set of indices must be strictly ordered ($y_i < y_{i+1}$).

The developers of ml-dsa attempted to enforce this validation using a helper function. However, in version 0.0.4, a regression slipped in. Instead of checking if the sequence was strictly increasing, they checked if it was merely monotonic (non-decreasing). They used a less-than-or-equal-to operator (<=) where a strictly less-than operator (<) was required. This subtle distinction meant that a signature containing the hint sequence [1, 5, 5, 9] was accepted as valid, even though the spec—and logic—demands [1, 5, 9].

The Code: The Smoking Gun

Let's look at the Rust code that caused the headache. The vulnerability lived in ml-dsa/src/hint.rs. The intention was to validate that the hint indices provided in the signature were sorted correctly.

The Vulnerable Code:

fn monotonic(a: &[usize]) -> bool {
    // The bug: allowing equality means duplicates are okay.
    // If a[i-1] == 5 and x == 5, this returns true.
    a.iter().enumerate().all(|(i, x)| i == 0 || a[i - 1] <= *x)
}

By using <=, the check passes even if a[i-1] is equal to *x. This allows duplicate hints to slide past the goaltender. In a high-stakes crypto library, this is the equivalent of a bouncer checking IDs but letting in photocopies.

The Fix (Commit 4009614):

The remediation was swift and decisive. The developers replaced the custom monotonic check with a functional approach using Rust's windows iterator to enforce strict inequality.

// The fixed logic enforces strict ascending order
if !indices.windows(2).all(|w| w[0] < w[1]) {
    return None;
}

They also fixed a secondary issue in use_hint where a boundary check was too aggressive (>) instead of inclusive (>=), ensuring that edge-case values near the field modulus are handled correctly.

The Exploit: Malleability Mayhem

So, how do we weaponize a duplicated index? We aren't recovering the private key here, so we can't forge signatures from scratch. However, we can perform a Signature Malleability attack. This is where an attacker takes an existing, valid signature (r, s) and transforms it into (r, s') which is also valid for the same message, but has a different byte representation.

The Attack Scenario:

  1. Intercept: Alice signs a transaction sending 100 coins to Bob. The signature $S$ contains the hint sequence [10, 42]. The transaction ID is Hash(Message || S).
  2. Mutate: Mallory (the attacker) observes this transaction in the mempool. She takes signature $S$ and modifies the hint encoding to duplicate an index, creating $S'$ with hints [10, 10, 42]. Because of the bug, the validator accepts this.
  3. Replay/DoS: Mallory broadcasts the transaction with signature $S'$. If the network accepts $S'$ first, the transaction ID changes to Hash(Message || S'). Alice's original wallet software, watching for the original ID, might assume the transaction failed and resend it, or the system might process the payment twice if deduplication logic relies solely on the signature hash.

This specific vector was caught by Wycheproof Test ID 18, which is specifically designed to feed signatures with repeated hints to verifiers to see if they choke. In this case, ml-dsa didn't choke—it swallowed the bad data with a smile.

The Impact: Why Uniqueness Matters

You might be thinking, "So what? The signature still verifies." In many contexts, like a simple JWT login, this is true. The server verifies the signature, sees it's valid, and logs you in. No harm done.

However, in financial systems, blockchain protocols, and distributed ledgers, uniqueness is security. Bitcoin faced a massive headache with transaction malleability (leading to the Mt. Gox implosion debates and eventually SegWit). If an attacker can change the unique identifier of a transaction without invalidating it, they can wreak havoc on accounting systems, bypass double-spend protections that rely on ID caching, and break smart contract logic that assumes SigHash is immutable.

Furthermore, this breaks FIPS 204 compliance. If you are a government contractor or a regulated entity claiming to use "Standardized Post-Quantum Crypto," running this version of the crate makes that claim false. You aren't running FIPS 204; you're running "FIPS 204-ish with loose validation."

The Fix: Remediation and Lessons

The fix is straightforward: upgrade. The maintainers released version 0.1.0-rc.4 which includes the strict monotonicity check and the boundary fix.

For Developers:

  1. Update your Cargo.toml: ml-dsa = "0.1.0-rc.4".
  2. Audit your data: If you have stored signatures in a database that were validated with the vulnerable version, you might now have "invalid" data sitting in your system. When you upgrade, verification of those specific malformed signatures will fail. You may need a migration strategy to re-sign or scrub non-canonical data.

The Lesson:

This vulnerability reinforces the importance of using negative test vectors. The Wycheproof project (maintained by Google) specifically tests for these edge cases because developers constantly get them wrong. When implementing crypto, passing the "Happy Path" tests is meaningless. You are only as secure as your ability to reject the "Unhappy Path."

Official Patches

RustCryptoCommit fixing the monotonicity check

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Probability
0.01%
Top 88% most exploited

Affected Systems

Rust applications using `ml-dsa` crate versions >= 0.0.4 and < 0.1.0-rc.4Blockchain systems implementing ML-DSA/Dilithium via RustCryptoSecure messaging protocols using early ML-DSA implementations

Affected Versions Detail

Product
Affected Versions
Fixed Version
ml-dsa
RustCrypto
>= 0.0.4, < 0.1.0-rc.40.1.0-rc.4
AttributeDetail
CWE IDCWE-347
CVSS5.3 (Medium)
Attack VectorNetwork
ImpactSignature Malleability / Integrity
Exploit StatusProof of Concept Available (Wycheproof)
LanguageRust

MITRE ATT&CK Mapping

T1565Data Manipulation
Impact
T1499Endpoint Denial of Service
Impact
CWE-347
Improper Verification of Cryptographic Signature

Improper Verification of Cryptographic Signature

Known Exploits & Detection

WycheproofTest ID 18 (signature with a repeated hint) triggers the vulnerability.

Vulnerability Timeline

Issue #894 reported regarding round-trip failures
2025-02-11
Comprehensive fix merged in PR #1187
2026-01-27
CVE-2026-24850 Published
2026-01-28

References & Sources

  • [1]GHSA-5x2r-hc65-25f9
  • [2]Issue #894: Round-trip failures
  • [3]FIPS 204 Standard

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.