Background
truststore on Linux currently calls ctx.set_default_verify_paths() on every
wrap_socket() call (see _openssl.py:_configure_context, added in v0.10.0).
OpenSSL versions before 3.4 do not deduplicate the default CApath when
set_default_verify_paths() is called repeatedly, so each call appends a new
X509_LOOKUP_hash_dir entry to the X509_STORE. The verify path then iterates
all entries, leading to O(N²) opendir(capath) calls over the lifetime of a
shared SSLContext.
The duplicate-add behavior in OpenSSL was fixed in openssl/openssl#24140
("Avoid Duplicate Dir Search", merged April 2024, released in OpenSSL 3.4.0):
openssl/openssl#24140
But for users on:
- Debian 12 (OpenSSL 3.0.x)
- Ubuntu 22.04 LTS (OpenSSL 3.0.2)
- Ubuntu 24.04 LTS (OpenSSL 3.0.x)
- RHEL 9 (OpenSSL 3.0.x)
...the bug is unfixed at the OpenSSL layer, and truststore's pattern is the
amplifier. This affects every long-running Python service that uses truststore
on these distros.
The underlying performance regression was also independently noticed in
python/cpython#95031 (still open since 2022), though that issue stopped at
"5x slower" without identifying the accumulation mechanism.
Why fix in truststore even though OpenSSL 3.4+ has dedup
- All current stable LTS distros ship OpenSSL 3.0.x.
These won't get 3.4+ until their next major release (e.g., Debian 13).
- Even on OpenSSL 3.4+, repeatedly calling
set_default_verify_paths() on
the same SSLContext is wasted work.
- The fix in truststore is small: cache "we already set default verify paths
on this SSLContext" and skip on subsequent calls.
Reproduction
Image 1:debian:bookworm(OpenSSL 3.0.20,reproduced)
docker run --rm \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
-v $PWD/truststore_repro.sh:/repro.sh \
debian:bookworm bash -c '
apt-get update -qq
apt-get install -qq -y python3 python3-pip strace openssl ca-certificates >/dev/null 2>&1
pip3 install --break-system-packages -q truststore 2>&1 | tail -1
bash /repro.sh
'
output:
== Environment ==
python = 3.11.2
openssl = OpenSSL 3.0.20 ...
truststore = 0.10.4
cafile = /usr/lib/ssl/cert.pem (exists=True)
capath = /usr/lib/ssl/certs (exists=True)
capath has 285 hash entries
== strace sanity check ==
strace works ✓
== Quadratic growth in opendir(capath) ==
n= 5 opendir= 35 opendir/n= 7.0
n= 20 opendir= 440 opendir/n= 22.0
n= 50 opendir= 2600 opendir/n= 52.0
n=200 opendir= 40400 opendir/n= 202.0
Image 2:python:3.11-slim(OpenSSL 3.5.6,Can't reproduce / upstream already fixed)
docker run --rm \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
-v $PWD/truststore_repro.sh:/repro.sh \
python:3.11-slim bash -c '
apt-get update -qq && apt-get install -qq -y strace openssl >/dev/null 2>&1
pip install -q truststore
bash /repro.sh
'
output:
== Environment ==
python = 3.11.15
openssl = OpenSSL 3.5.6 ...
capath = /usr/lib/ssl/certs (exists=True)
capath has 301 hash entries
== Quadratic growth in opendir(capath) ==
n= 5 opendir= 0 opendir/n= 0.0
n= 20 opendir= 0 opendir/n= 0.0
n= 50 opendir= 0 opendir/n= 0.0
n=200 opendir= 0 opendir/n= 0.0
truststore_repro.sh
Background
truststore on Linux currently calls
ctx.set_default_verify_paths()on everywrap_socket()call (see_openssl.py:_configure_context, added in v0.10.0).OpenSSL versions before 3.4 do not deduplicate the default CApath when
set_default_verify_paths()is called repeatedly, so each call appends a newX509_LOOKUP_hash_direntry to theX509_STORE. The verify path then iteratesall entries, leading to O(N²)
opendir(capath)calls over the lifetime of ashared
SSLContext.The duplicate-add behavior in OpenSSL was fixed in openssl/openssl#24140
("Avoid Duplicate Dir Search", merged April 2024, released in OpenSSL 3.4.0):
openssl/openssl#24140
But for users on:
...the bug is unfixed at the OpenSSL layer, and truststore's pattern is the
amplifier. This affects every long-running Python service that uses truststore
on these distros.
The underlying performance regression was also independently noticed in
python/cpython#95031 (still open since 2022), though that issue stopped at
"5x slower" without identifying the accumulation mechanism.
Why fix in truststore even though OpenSSL 3.4+ has dedup
These won't get 3.4+ until their next major release (e.g., Debian 13).
set_default_verify_paths()onthe same SSLContext is wasted work.
on this SSLContext" and skip on subsequent calls.
Reproduction
Image 1:
debian:bookworm(OpenSSL 3.0.20,reproduced)output:
Image 2:
python:3.11-slim(OpenSSL 3.5.6,Can't reproduce / upstream already fixed)output:
truststore_repro.sh