Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions crates/stdlib/src/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "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); // 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]
Expand Down Expand Up @@ -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 {
Expand Down
40 changes: 26 additions & 14 deletions crates/stdlib/src/ssl/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<HandshakeSignatureValid, rustls::Error> {
// 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<HandshakeSignatureValid, rustls::Error> {
// 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<SignatureScheme> {
ALL_SIGNATURE_SCHEMES.to_vec()
CryptoExt::get_provider()
.signature_verification_algorithms
.supported_schemes()
}
}

Expand Down
76 changes: 74 additions & 2 deletions crates/vm/src/stdlib/_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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()) }
}
}
}
Loading