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]