As our security policy documents, PanicExceptions are not vulnerabilities:
https://cryptography.io/en/latest/security/#what-is-a-security-issue

That said this was also already fixed:
https://github.com/pyca/cryptography/pull/14428

Finally, this is a public mailing list so this is an incorrect way to
disclose an actual security vulnerability.

Alex

On Mon, Mar 9, 2026 at 10:37 PM Amartya Jha <[email protected]> wrote:

> To the Python Cryptographic Authority / cryptography maintainers,
>
> I am a security researcher at CodeAnt AI. I have discovered a
> denial-of-service vulnerability in the cryptography package's PBKDF2HMAC
> key derivation function. When iterations=0 is passed, the Rust backend
> raises a pyo3_runtime.PanicException, a direct subclass of BaseException,
> not Exception — which silently escapes all standard Python exception
> handlers and can crash the application process.
>
> Package: cryptography
> Affected Versions: All modern versions (36.x through 46.x) using the
> PyO3/Rust backend
> Tested Version: 46.0.5
>
> CVSS: 5.3 MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)
> CWE: CWE-703 (Improper Check or Handling of Exceptional Conditions),
> CWE-20 (Improper Input Validation)
>
> ---
>
> What is the Issue?
>
> PBKDF2HMAC.__init__() in cryptography/hazmat/primitives/kdf/pbkdf2.py
> stores the iterations parameter directly without any bounds validation:
>
>     def __init__(self, algorithm, length, salt, iterations, backend=None):
>         ...
>         self._iterations = iterations   # stored directly, no bounds check
>
> When derive() is called with iterations=0, this value is passed to the
> Rust layer in src/rust/src/backend/kdf.rs:30, which calls .unwrap() on the
> OpenSSL error rather than propagating it via map_err. This causes a Rust
> panic:
>
>     thread '<unnamed>' panicked at src/rust/src/backend/kdf.rs:30:87:
>     called `Result::unwrap()` on an `Err` value: ErrorStack([Error { code:
> 478150779,
>       library: "Provider routines",
>       function: "kdf_pbkdf2_set_ctx_params",
>       reason: "invalid iteration count",
>       file: "providers/implementations/kdfs/pbkdf2.c", line: 305 }])
>
> PyO3 surfaces Rust panics as pyo3_runtime.PanicException. This class
> inherits from BaseException, NOT Exception:
>
>     pyo3_runtime.PanicException → BaseException → object
>
> ---
>
> What is the Impact?
>
> 1. Process crash via DoS: Any application that accepts a user-controlled
> iterations value and passes it to PBKDF2HMAC (e.g., via JSON API payload,
> config file, URL parameter, or database-stored value) can be crashed by a
> single request with iterations=0.
>
> 2. Silent bypass of all error handling: The universally expected Python
> idiom "except Exception:" does NOT catch BaseException subclasses. Every
> standard web framework error handler, request handler, and try/except block
> in the application stack silently fails to catch this crash:
>
>     try:
>         kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
> salt=b"salt", iterations=0)
>         result = kdf.derive(b"password")
>     except Exception as e:
>         print("Caught:", e)   # <-- NEVER REACHED
>
> 3. Contrast with iterations=-1: Passing -1 correctly raises OverflowError
> (a subclass of Exception) and IS catchable. Only iterations=0 hits the Rust
> panic path.
>
> ---
>
> Proof of Concept:
>
>     from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
>     from cryptography.hazmat.primitives import hashes
>
>     kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=b"salt",
> iterations=0)
>     print("__init__ passed: no error raised here")
>
>     try:
>         result = kdf.derive(b"password")
>     except Exception as e:
>         print("Caught (Exception):", e)   # NEVER REACHED
>
> Realistic attack vector (API endpoint accepting PBKDF2 parameters):
>
>     @app.post("/derive-key")
>     async def derive_key(params: KDFParams):
>         try:
>             kdf = PBKDF2HMAC(
>                 algorithm=hashes.SHA256(),
>                 length=params.key_length,
>                 salt=params.salt.encode(),
>                 iterations=params.iterations   # user-controlled!
>             )
>             return {"key": kdf.derive(params.password.encode()).hex()}
>         except Exception as e:
>             raise HTTPException(status_code=400, detail=str(e))
>         # PanicException bypasses the except Exception block above
>
> ---
>
> Recommended Fix:
>
> Option 1 — Python-level validation (fastest fix):
>
>     def __init__(self, algorithm, length, salt, iterations, backend=None):
>         ...
>         if not isinstance(iterations, int) or iterations < 1:
>             raise ValueError(
>                 "iterations must be a positive integer, got
> {!r}".format(iterations)
>             )
>         self._iterations = iterations
>
> Option 2 — Rust-level fix (proper long-term fix):
> Replace .unwrap() in src/rust/src/backend/kdf.rs:30 with proper error
> propagation using map_err so that OpenSSL errors surface as catchable
> Python exceptions via PyO3's error conversion.
>
> Both fixes should be applied: the Python-level check as a fast guard with
> a clear error message, and the Rust-level fix to make the entire error path
> robust.
>
> ---
>
> I am happy to provide a complete test harness, coordinate a patch review,
> or adjust the disclosure timeline.
>
> ---
>
> Researcher: CodeAnt AI Security Research Team, part of
> https://www.codeant.ai/
> Contact: [email protected]
> _______________________________________________
> Cryptography-dev mailing list -- [email protected]
> To unsubscribe send an email to [email protected]
> https://mail.python.org/mailman3//lists/cryptography-dev.python.org
> Member address: [email protected]
>


-- 
All that is necessary for evil to succeed is for good people to do nothing.
_______________________________________________
Cryptography-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/cryptography-dev.python.org
Member address: [email protected]

Reply via email to