From 2ab612f2335c41a7a235fd62fb80b971e6eb5a47 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 12 Aug 2026 16:11:58 +0900 Subject: [PATCH 1/2] Fix rustls test_ssl compatibility Assisted-by: OpenAI Codex:GPT-5 --- crates/stdlib/src/ssl.rs | 16 ++++--- crates/stdlib/src/ssl/cert.rs | 40 +++++++++++------ crates/vm/src/stdlib/_thread.rs | 76 ++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 04b905d544e..043b855724d 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -323,15 +323,17 @@ mod _ssl { #[pyattr] const ALERT_DESCRIPTION_NO_APPLICATION_PROTOCOL: i32 = 120; - // Version info - reporting as OpenSSL 3.3.0 for compatibility + // `ssl.py` still requires OpenSSL-shaped numeric compatibility fields even + // for non-OpenSSL TLS providers. Keep them in the supported 3.x ABI range, + // but report the actual rustls/AWS-LC backend in the human-readable string. #[pyattr] - const OPENSSL_VERSION_NUMBER: i32 = 0x30300000; // OpenSSL 3.3.0 (808452096) + const OPENSSL_VERSION_NUMBER: i32 = 0x30300000; #[pyattr] - const OPENSSL_VERSION: &str = "OpenSSL 3.3.0 (rustls/0.23)"; + const OPENSSL_VERSION: &str = "AWS-LC (rustls/0.23)"; #[pyattr] - const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release + const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); #[pyattr] - const _OPENSSL_API_VERSION: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release + const _OPENSSL_API_VERSION: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // Default cipher list for rustls - using modern secure ciphers #[pyattr] @@ -2816,8 +2818,8 @@ mod _ssl { super::compat::SslError::create_ssl_error_with_reason( vm, Some("SSL"), - "CALLBACK_FAILED", - "[SSL: CALLBACK_FAILED] callback failed", + "PARSE_TLSEXT", + "[SSL: PARSE_TLSEXT] SNI callback owner is no longer available", ) })?; let server_name_py: PyObjectRef = match sni_name { diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index f12f4307239..47d11f730b2 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -287,9 +287,11 @@ pub(super) fn is_ca_certificate(cert_der: &[u8]) -> bool { return ext.value.ca; } - // No Basic Constraints extension -> NOT a CA certificate - // (matches OpenSSL X509_check_ca() behavior) - false + // X509_check_ca() also retains OpenSSL's legacy trust-anchor rule: a + // self-issued X.509v1 certificate has no extensions at all, but is still + // classified as a CA. CPython's test CA at capath/4e1295a3.0 exercises + // precisely this case. + cert.version().0 == 0 && cert.subject() == cert.issuer() } /// Convert an X509Name to Python nested tuple format for SSL certificate dicts @@ -867,26 +869,36 @@ impl ServerCertVerifier for NoVerifier { fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, ) -> Result { - // Accept all signatures without verification - Ok(HandshakeSignatureValid::assertion()) + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &CryptoExt::get_provider().signature_verification_algorithms, + ) } fn verify_tls13_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, ) -> Result { - // Accept all signatures without verification - Ok(HandshakeSignatureValid::assertion()) + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &CryptoExt::get_provider().signature_verification_algorithms, + ) } fn supported_verify_schemes(&self) -> Vec { - ALL_SIGNATURE_SCHEMES.to_vec() + CryptoExt::get_provider() + .signature_verification_algorithms + .supported_schemes() } } diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index 70304e63980..377c68dca74 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -605,14 +605,35 @@ pub(crate) mod _thread { vm.state.thread_count.fetch_sub(1); } + /// Default stack size for Python threads in **debug builds only**, where + /// Rust stack frames are substantially larger than in release. Rust's + /// `std::thread::Builder` otherwise defaults to 2 MB, which is too small + /// for the call chains the Python stdlib runs on helper threads in debug + /// (e.g. the SSL test server, see #7941). Release builds keep the prior + /// behavior — leave the stack size unset and let Rust's std default apply + /// — to avoid oversized virtual stack mappings when many threads spawn. + #[cfg(debug_assertions)] + const DEFAULT_THREAD_STACK_SIZE: usize = 8 * 1024 * 1024; + + /// Configure a `thread::Builder` with the stack size to use for a new + /// Python thread. Uses the value set via `threading.stack_size(N)` when + /// the user has provided one (non-zero). Otherwise, debug builds fall + /// back to [`DEFAULT_THREAD_STACK_SIZE`] and release builds leave the + /// builder unmodified (Rust's std default applies). fn apply_thread_stack_size( thread_builder: thread::Builder, vm: &VirtualMachine, ) -> thread::Builder { let configured = vm.state.stacksize.load(); if configured != 0 { - thread_builder.stack_size(configured) - } else { + return thread_builder.stack_size(configured); + } + #[cfg(debug_assertions)] + { + thread_builder.stack_size(DEFAULT_THREAD_STACK_SIZE) + } + #[cfg(not(debug_assertions))] + { thread_builder } } @@ -1996,4 +2017,55 @@ pub(crate) mod _thread { Ok(handle_clone) } + + #[cfg(test)] + mod tests { + #[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))] + use super::*; + #[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))] + use crate::Interpreter; + + /// Regression test for #7941: a Python thread started without an + /// explicit `threading.stack_size()` must not run on Rust's 2 MiB + /// std default in debug builds, where the call chains the stdlib + /// runs on helper threads (e.g. the SSL test server) overflowed it. + #[test] + #[cfg(all(debug_assertions, any(target_os = "linux", target_os = "macos")))] + fn default_python_thread_stack_size_debug() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + assert_eq!(vm.state.stacksize.load(), 0); + let builder = apply_thread_stack_size(thread::Builder::new(), vm); + let stack_size = builder + .spawn(current_thread_stack_size) + .expect("failed to spawn thread") + .join() + .expect("thread panicked"); + assert!( + stack_size >= DEFAULT_THREAD_STACK_SIZE, + "Python thread stack size is {stack_size} bytes, expected at least {DEFAULT_THREAD_STACK_SIZE}" + ); + }); + } + + #[cfg(all(debug_assertions, target_os = "linux"))] + fn current_thread_stack_size() -> usize { + use libc::{ + pthread_attr_destroy, pthread_attr_getstacksize, pthread_attr_t, + pthread_getattr_np, pthread_self, + }; + let mut attr: pthread_attr_t = unsafe { core::mem::zeroed() }; + unsafe { + assert_eq!(pthread_getattr_np(pthread_self(), &mut attr), 0); + let mut size = 0; + assert_eq!(pthread_attr_getstacksize(&attr, &mut size), 0); + pthread_attr_destroy(&mut attr); + size + } + } + + #[cfg(all(debug_assertions, target_os = "macos"))] + fn current_thread_stack_size() -> usize { + unsafe { libc::pthread_get_stacksize_np(libc::pthread_self()) } + } + } } From fea9e045988e928a7f209c1c74740b76a4ffc404 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 12 Aug 2026 16:42:57 +0900 Subject: [PATCH 2/2] Keep urllib3 compatible SSL version prefix Assisted-by: OpenAI Codex:GPT-5 --- crates/stdlib/src/ssl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 043b855724d..18d171a8583 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -329,7 +329,7 @@ mod _ssl { #[pyattr] const OPENSSL_VERSION_NUMBER: i32 = 0x30300000; #[pyattr] - const OPENSSL_VERSION: &str = "AWS-LC (rustls/0.23)"; + const OPENSSL_VERSION: &str = "OpenSSL 3.3.0-compatible (AWS-LC/rustls 0.23)"; #[pyattr] const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); #[pyattr]