Skip to content

Commit 24bd3b3

Browse files
authored
ssl: pass test_ssl with the rustls backend (#8502)
* Fix rustls test_ssl compatibility Assisted-by: OpenAI Codex:GPT-5 * Keep urllib3 compatible SSL version prefix Assisted-by: OpenAI Codex:GPT-5
1 parent d64cc2c commit 24bd3b3

3 files changed

Lines changed: 109 additions & 23 deletions

File tree

crates/stdlib/src/ssl.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,17 @@ mod _ssl {
323323
#[pyattr]
324324
const ALERT_DESCRIPTION_NO_APPLICATION_PROTOCOL: i32 = 120;
325325

326-
// Version info - reporting as OpenSSL 3.3.0 for compatibility
326+
// `ssl.py` still requires OpenSSL-shaped numeric compatibility fields even
327+
// for non-OpenSSL TLS providers. Keep them in the supported 3.x ABI range,
328+
// but report the actual rustls/AWS-LC backend in the human-readable string.
327329
#[pyattr]
328-
const OPENSSL_VERSION_NUMBER: i32 = 0x30300000; // OpenSSL 3.3.0 (808452096)
330+
const OPENSSL_VERSION_NUMBER: i32 = 0x30300000;
329331
#[pyattr]
330-
const OPENSSL_VERSION: &str = "OpenSSL 3.3.0 (rustls/0.23)";
332+
const OPENSSL_VERSION: &str = "OpenSSL 3.3.0-compatible (AWS-LC/rustls 0.23)";
331333
#[pyattr]
332-
const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release
334+
const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15);
333335
#[pyattr]
334-
const _OPENSSL_API_VERSION: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release
336+
const _OPENSSL_API_VERSION: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15);
335337

336338
// Default cipher list for rustls - using modern secure ciphers
337339
#[pyattr]
@@ -2816,8 +2818,8 @@ mod _ssl {
28162818
super::compat::SslError::create_ssl_error_with_reason(
28172819
vm,
28182820
Some("SSL"),
2819-
"CALLBACK_FAILED",
2820-
"[SSL: CALLBACK_FAILED] callback failed",
2821+
"PARSE_TLSEXT",
2822+
"[SSL: PARSE_TLSEXT] SNI callback owner is no longer available",
28212823
)
28222824
})?;
28232825
let server_name_py: PyObjectRef = match sni_name {

crates/stdlib/src/ssl/cert.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,11 @@ pub(super) fn is_ca_certificate(cert_der: &[u8]) -> bool {
287287
return ext.value.ca;
288288
}
289289

290-
// No Basic Constraints extension -> NOT a CA certificate
291-
// (matches OpenSSL X509_check_ca() behavior)
292-
false
290+
// X509_check_ca() also retains OpenSSL's legacy trust-anchor rule: a
291+
// self-issued X.509v1 certificate has no extensions at all, but is still
292+
// classified as a CA. CPython's test CA at capath/4e1295a3.0 exercises
293+
// precisely this case.
294+
cert.version().0 == 0 && cert.subject() == cert.issuer()
293295
}
294296

295297
/// Convert an X509Name to Python nested tuple format for SSL certificate dicts
@@ -867,26 +869,36 @@ impl ServerCertVerifier for NoVerifier {
867869

868870
fn verify_tls12_signature(
869871
&self,
870-
_message: &[u8],
871-
_cert: &CertificateDer<'_>,
872-
_dss: &DigitallySignedStruct,
872+
message: &[u8],
873+
cert: &CertificateDer<'_>,
874+
dss: &DigitallySignedStruct,
873875
) -> Result<HandshakeSignatureValid, rustls::Error> {
874-
// Accept all signatures without verification
875-
Ok(HandshakeSignatureValid::assertion())
876+
rustls::crypto::verify_tls12_signature(
877+
message,
878+
cert,
879+
dss,
880+
&CryptoExt::get_provider().signature_verification_algorithms,
881+
)
876882
}
877883

878884
fn verify_tls13_signature(
879885
&self,
880-
_message: &[u8],
881-
_cert: &CertificateDer<'_>,
882-
_dss: &DigitallySignedStruct,
886+
message: &[u8],
887+
cert: &CertificateDer<'_>,
888+
dss: &DigitallySignedStruct,
883889
) -> Result<HandshakeSignatureValid, rustls::Error> {
884-
// Accept all signatures without verification
885-
Ok(HandshakeSignatureValid::assertion())
890+
rustls::crypto::verify_tls13_signature(
891+
message,
892+
cert,
893+
dss,
894+
&CryptoExt::get_provider().signature_verification_algorithms,
895+
)
886896
}
887897

888898
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
889-
ALL_SIGNATURE_SCHEMES.to_vec()
899+
CryptoExt::get_provider()
900+
.signature_verification_algorithms
901+
.supported_schemes()
890902
}
891903
}
892904

crates/vm/src/stdlib/_thread.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,14 +605,35 @@ pub(crate) mod _thread {
605605
vm.state.thread_count.fetch_sub(1);
606606
}
607607

608+
/// Default stack size for Python threads in **debug builds only**, where
609+
/// Rust stack frames are substantially larger than in release. Rust's
610+
/// `std::thread::Builder` otherwise defaults to 2 MB, which is too small
611+
/// for the call chains the Python stdlib runs on helper threads in debug
612+
/// (e.g. the SSL test server, see #7941). Release builds keep the prior
613+
/// behavior — leave the stack size unset and let Rust's std default apply
614+
/// — to avoid oversized virtual stack mappings when many threads spawn.
615+
#[cfg(debug_assertions)]
616+
const DEFAULT_THREAD_STACK_SIZE: usize = 8 * 1024 * 1024;
617+
618+
/// Configure a `thread::Builder` with the stack size to use for a new
619+
/// Python thread. Uses the value set via `threading.stack_size(N)` when
620+
/// the user has provided one (non-zero). Otherwise, debug builds fall
621+
/// back to [`DEFAULT_THREAD_STACK_SIZE`] and release builds leave the
622+
/// builder unmodified (Rust's std default applies).
608623
fn apply_thread_stack_size(
609624
thread_builder: thread::Builder,
610625
vm: &VirtualMachine,
611626
) -> thread::Builder {
612627
let configured = vm.state.stacksize.load();
613628
if configured != 0 {
614-
thread_builder.stack_size(configured)
615-
} else {
629+
return thread_builder.stack_size(configured);
630+
}
631+
#[cfg(debug_assertions)]
632+
{
633+
thread_builder.stack_size(DEFAULT_THREAD_STACK_SIZE)
634+
}
635+
#[cfg(not(debug_assertions))]
636+
{
616637
thread_builder
617638
}
618639
}
@@ -1996,4 +2017,55 @@ pub(crate) mod _thread {
19962017

19972018
Ok(handle_clone)
19982019
}
2020+
2021+
#[cfg(test)]
2022+
mod tests {
2023+
#[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))]
2024+
use super::*;
2025+
#[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))]
2026+
use crate::Interpreter;
2027+
2028+
/// Regression test for #7941: a Python thread started without an
2029+
/// explicit `threading.stack_size()` must not run on Rust's 2 MiB
2030+
/// std default in debug builds, where the call chains the stdlib
2031+
/// runs on helper threads (e.g. the SSL test server) overflowed it.
2032+
#[test]
2033+
#[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))]
2034+
fn default_python_thread_stack_size_debug() {
2035+
Interpreter::without_stdlib(Default::default()).enter(|vm| {
2036+
assert_eq!(vm.state.stacksize.load(), 0);
2037+
let builder = apply_thread_stack_size(thread::Builder::new(), vm);
2038+
let stack_size = builder
2039+
.spawn(current_thread_stack_size)
2040+
.expect("failed to spawn thread")
2041+
.join()
2042+
.expect("thread panicked");
2043+
assert!(
2044+
stack_size >= DEFAULT_THREAD_STACK_SIZE,
2045+
"Python thread stack size is {stack_size} bytes, expected at least {DEFAULT_THREAD_STACK_SIZE}"
2046+
);
2047+
});
2048+
}
2049+
2050+
#[cfg(all(debug_assertions, target_os = "linux"))]
2051+
fn current_thread_stack_size() -> usize {
2052+
use libc::{
2053+
pthread_attr_destroy, pthread_attr_getstacksize, pthread_attr_t,
2054+
pthread_getattr_np, pthread_self,
2055+
};
2056+
let mut attr: pthread_attr_t = unsafe { core::mem::zeroed() };
2057+
unsafe {
2058+
assert_eq!(pthread_getattr_np(pthread_self(), &mut attr), 0);
2059+
let mut size = 0;
2060+
assert_eq!(pthread_attr_getstacksize(&attr, &mut size), 0);
2061+
pthread_attr_destroy(&mut attr);
2062+
size
2063+
}
2064+
}
2065+
2066+
#[cfg(all(debug_assertions, target_os = "macos"))]
2067+
fn current_thread_stack_size() -> usize {
2068+
unsafe { libc::pthread_get_stacksize_np(libc::pthread_self()) }
2069+
}
2070+
}
19992071
}

0 commit comments

Comments
 (0)