GitHub Security Advisory: GHSA-j9xq-69pf-pcm8
Package: sm2 (RUST)
Severity: HIGH (CVSS 7.5)
CWE: CWE-20: Improper Input Validation
Vulnerable versions: >= 0.14.0-pre.0, <= 0.14.0-rc.4
Patched in: unpatched

Summary: RustCrypto Has Insufficient Length Validation in decrypt() in SM2-PKE

Details:
### 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_a