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



GHSA-7587-4WV6-M68M
7.50.06%

Panic at the Keyring: Crashing rPGP with a Single Byte

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 14, 2026·7 min read·11 visits

PoC Available

Executive Summary (TL;DR)

The `rPGP` library (via the `rsa` crate < 0.9.10) contains a logic flaw in validating RSA private key components. By providing a crafted Secret Key Packet where one of the prime factors is set to 1, the library attempts to calculate modulus inverses using (p-1), resulting in a division-by-zero panic. This crashes the application process, leading to a Denial of Service (DoS) against keyservers, email clients, or signing services using the library.

In the world of safe systems programming, Rust is the golden child. It promises to save us from the memory corruption sins of C and C++. But while Rust protects memory, it doesn't protect logic. A critical denial-of-service vulnerability was discovered in the `rPGP` library (and its dependency, the `rsa` crate) where a mathematically impossible RSA key component triggers a hard panic. By setting a prime factor to '1', an attacker can trick the underlying arithmetic engine into a division-by-zero scenario, crashing any application attempting to parse the key. This is a story about how 'safe' languages still need defensive coding.

The Hook: Even Rust Can Bleed

We love Rust. It gives us warm fuzzy feelings about memory safety. No more use-after-free, no more buffer overflows, just pure, borrow-checked bliss. But here's the dirty secret: panic! is safe. Memory-safe, that is. If a Rust program encounters an unrecoverable state, it unwinds the stack and dies. In a command-line tool, that's annoying. In a high-availability web service processing thousands of PGP keys, that's a disaster.

rPGP is the premier pure-Rust implementation of OpenPGP. It's designed to replace GnuPG in modern stacks. It handles the messy, decades-old RFC 4880 standard so you don't have to. But parsing OpenPGP is like walking through a minefield while blindfolded. The format allows for complex nested packets, and deeply buried within those packets are the cryptographic primitives: RSA, DSA, Ed25519.

This vulnerability isn't about memory corruption. It's about math. specifically, the kind of math that breaks computers. It resides in how the underlying rsa crate handles the components of a private key. It turns out, if you lie to the library about your prime numbers, you can trick it into committing the cardinal sin of arithmetic: dividing by zero. The result? The application vanishes into the void.

The Flaw: The Mathematical Impossibility

To understand this bug, we have to go back to RSA 101. An RSA private key consists of a modulus $n$, a public exponent $e$, and a private exponent $d$. But to optimize calculations (specifically using the Chinese Remainder Theorem, or CRT), the private key usually also stores the two prime factors, $p$ and $q$, such that $n = p \times q$.

When rPGP parses a Secret-Key Packet (Tag 5), it extracts these numbers and passes them to the rsa crate to build an RsaPrivateKey object. The rsa crate, being responsible, tries to validate these numbers. You can't just accept any garbage integers; they have to look like primes.

Here lies the error. In rsa versions prior to 0.9.10, the validation logic checked if the primes were "too small". Specifically, it checked if a prime was effectively zero. But BigUint (unsigned big integers) don't have negative numbers. The check looked something like if prime < 1. This catches 0. But it allows 1.

Why is 1 dangerous? Because nearly every RSA-CRT optimization involves the value $(p-1)$. For example, Euler's totient function $\phi(n) = (p-1)(q-1)$. If $p=1$, then $p-1=0$. Suddenly, you are calculating modulo 0 or dividing by 0. The num-bigint library, which handles the heavy lifting, essentially says "I can't do that" and triggers a thread-terminating panic.

The Code: One Character Difference

Let's look at the smoking gun. This is found in src/key.rs of the rsa crate. The developers intended to ensure the prime was valid, but they missed an edge case.

The Vulnerable Code (rsa < 0.9.10):

// Iterate over the primes provided in the key components
for prime in &self.primes {
    // ALERT: This only errors if prime is 0. 
    // If prime is 1, this check passes!
    if *prime < BigUint::one() {
        return Err(Error::InvalidPrime);
    }
    m *= prime;
}
// ... later, (prime - 1) causes panic ...

Because BigUint is an unsigned integer type, < 1 is strictly equivalent to == 0. The integer 1 slips past the guard. Moments later, the code attempts to compute derived values, likely involving prime - 1 for CRT parameters. When that subtraction results in a BigUint of zero, and that zero is used as a divisor, the program crashes.

The Fix (Commit 2926c91):

for prime in &self.primes {
    // The fix: change '<' to '<='
    // Now, if prime is 0 OR 1, it rejects it.
    if *prime <= BigUint::one() {
        return Err(Error::InvalidPrime);
    }
    m *= prime;
}

It is almost comical that a single equals sign = stands between a stable server and a denial of service. This highlights a classic issue in input validation: checking for the presence of data rather than the semantic validity of data.

The Exploit: Crafting the Poison Packet

Exploiting this is trivial for anyone who understands the OpenPGP packet format (RFC 4880). We don't need to break encryption; we just need to construct a structurally valid but mathematically suicidal packet.

The Attack Chain

  1. Packet Header: Create a Secret-Key Packet (Tag 5).
  2. Version: Use Version 4 (standard).
  3. Algorithm: Select RSA (ID 1).
  4. Public Key Data: Generate a valid Modulus ($n$) and Exponent ($e$). These can be real or junk, as long as they parse as MPIs (Multiprecision Integers).
  5. Encrypted Secret Data: Standard OpenPGP keys encrypt the secret parts (d, p, q, u) with a passphrase. However, we can set the "String-to-Key Usage" byte to 0, indicating unencrypted secret data. This bypasses the need for a password and forces the library to parse the raw integers immediately.
  6. The Payload: When writing the MPIs for the secret components:
    • d: Arbitrary integer.
    • p: Set this to 1.
    • q: Arbitrary integer.
    • u: Arbitrary integer.

When rPGP reads this packet, it deserializes the MPIs. It sees that the key is unencrypted, so it attempts to construct the RsaPrivateKey struct immediately to validate it. It calls RsaPrivateKey::from_components(n, e, d, vec![p, q]).

Inside that function, p (which is 1) passes the Check < 1. Then, the math engine attempts a modular inverse or reduction. BOOM. The panic unwinds the stack. If the calling application hasn't explicitly wrapped this in a catch_unwind (and most don't, because libraries shouldn't panic on inputs), the process terminates.

The Impact: Availability is the 'A' in CIA

Why does this matter? "It's just a crash," you say. In the modern cloud era, a crash is a vulnerability.

Consider the following scenarios:

  • Keyservers: Public OpenPGP keyservers (like keys.openpgp.org or internal enterprise ones) accept key uploads from anonymous users. An attacker can upload this poisoned key. The moment the server tries to index or validate the packet, it crashes.
  • Email Gateways: Secure email gateways that automatically parse incoming PGP-signed emails to verify signatures or decrypt content. An attacker sends an email with this key attached. The gateway process parses the attachment and dies, stopping mail flow.
  • CI/CD Pipelines: Many automated release tools use rPGP to sign artifacts. If a malicious dependency or config file injects this key, the build pipeline collapses.

This is an unauthenticated remote DoS. It requires zero privileges. The attacker just needs to get the bytes into the parser. While it doesn't leak data (Confidentiality) or allow code execution (Integrity), it completely compromises Availability.

The Fix: Remediation

The fix is straightforward but requires action across the dependency tree. The vulnerability is technically in rsa, but pgp (rPGP) is the vector.

For Rust Developers:

  1. Check your lockfile: Run cargo tree -i rsa. If you see rsa v0.9.9 or lower, you are vulnerable.
  2. Update: Run cargo update -p rsa. This should pull in v0.9.10 or later, which contains the fix (if *prime <= BigUint::one()).
  3. Verify: Ensure your Cargo.lock shows the new version.

For System Administrators:

If you are running compiled binaries (like rpgp-cli or custom internal tools), you must recompile them with the updated dependencies. There is no runtime configuration workaround for this, as the panic happens deep inside the compiled logic.

Lesson Learned:

Never assume your cryptographic primitives are doing semantic validation for you. If you are parsing complex structures, wrap them in panic handlers or, better yet, audit the libraries you rely on to ensure they return Result::Err rather than panic! on bad user input.

Official Patches

RustCryptoRSA Crate v0.9.10 Release Notes
rPGPGitHub Security Advisory

Fix Analysis (2)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Probability
0.06%
Top 88% most exploited

Affected Systems

Rust applications using `rPGP` crateRust applications using `rsa` crate directly (< 0.9.10)OpenPGP Keyservers based on rPGPSecure email gateways using Rust backends

Affected Versions Detail

Product
Affected Versions
Fixed Version
rsa
RustCrypto
< 0.9.100.9.10
pgp
rPGP
< 0.14.0Dependent on rsa update
AttributeDetail
CVE IDCVE-2026-21895
CWE IDCWE-703 (Unhandled Exception)
CVSS7.5 (High)
Attack VectorNetwork
ImpactDenial of Service (Process Crash)
Root CauseDivision by Zero Panic

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
T1499.003Application or System Exploitation
Impact
CWE-703
Improper Check or Handling of Exceptional Conditions

The software does not handle or incorrectly handles an exceptional condition.

Known Exploits & Detection

GitHubOriginal issue report demonstrating the panic with p=1

Vulnerability Timeline

Patch committed to RustCrypto/RSA
2026-01-06
rPGP updates dependency
2026-01-07
GHSA-7587-4WV6-M68M Published
2026-01-08

References & Sources

  • [1]RustSec Advisory
  • [2]rPGP Repository

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.