From eca26d131177dfb9496cd2440dd7144cfc26b823 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Mon, 25 May 2026 21:22:24 -0400 Subject: [PATCH] Fix `ssl-vendor` (OpenSSL) Closes: #7893 Fix 1: `foreign-types-shared` needs to match `openssl`'s version. Bumping it is a SemVer violation because the latest versions of the crate aren't backwards compatible with older versions. See: rust-openssl/rust-openssl#2461 Fix 2: The second fix is to align the `openssl` module with the latest `host_env` and `ssl` changes. --- .github/workflows/ci.yaml | 4 +++ Cargo.toml | 1 + crates/stdlib/src/openssl.rs | 64 ++++++++++++++++------------------ crates/stdlib/src/ssl/error.rs | 8 +++++ 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 59b9cb6c7bf..2a6a5264c21 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -140,6 +140,10 @@ jobs: run: cargo build --no-default-features --features ssl-openssl if: runner.os == 'Linux' + - name: Test vendored OpenSSL build + run: cargo build --no-default-features --features ssl-vendor + if: runner.os == 'Linux' + # - name: Install tk-dev for tkinter build # run: sudo apt-get update && sudo apt-get install -y tk-dev # if: runner.os == 'Linux' diff --git a/Cargo.toml b/Cargo.toml index 916804bd951..a63656ebea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -217,6 +217,7 @@ exitcode = "1.1.2" flame = "0.2.2" flamer = "0.5" flate2 = { version = "1.1.9", default-features = false } +# Bump only when the openssl crate bumps it foreign-types-shared = "0.1" gethostname = "1.0.2" getrandom = { version = "0.3", features = ["std"] } diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index a7f9cb2a49d..76309d3c21d 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -65,7 +65,7 @@ mod _ssl { LazyLock, PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, }, - socket::{self, PySocket}, + socket::{self, PySocket, SockWaitKind, sock_wait}, vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ @@ -2297,34 +2297,31 @@ mod _ssl { needs: SslNeeds, deadline: &SocketDeadline, vm: &VirtualMachine, - ) -> SelectRet { - let sock = match self.0.sock_opt() { - Some(s) => s, - None => return SelectRet::Closed, + ) -> PyResult { + let Some(sock) = self.0.sock_opt() else { + return Ok(SelectRet::Closed); }; - // For blocking sockets without timeout, call sock_wait with None timeout + // For blocking sockets without timeout, call sock_select with None timeout // to actually block waiting for data instead of busy-looping let timeout = match &deadline { Ok(deadline) => match deadline.checked_duration_since(Instant::now()) { Some(d) => Some(d), - None => return SelectRet::TimedOut, + None => return Ok(SelectRet::TimedOut), }, Err(true) => None, // Blocking: no timeout, wait indefinitely - Err(false) => return SelectRet::Nonblocking, + Err(false) => return Ok(SelectRet::Nonblocking), }; - let res = socket::sock_wait( - &sock, - match needs { - SslNeeds::Read => socket::SockWaitKind::Read, - SslNeeds::Write => socket::SockWaitKind::Write, - }, - timeout, - vm, - ); - match res { - Ok(true) => SelectRet::TimedOut, - _ => SelectRet::Ok, - } + let wait_kind = match needs { + SslNeeds::Read => SockWaitKind::Read, + SslNeeds::Write => SockWaitKind::Write, + }; + sock_wait(&*sock, wait_kind, timeout, vm).map(|timed_out| { + if timed_out { + SelectRet::TimedOut + } else { + SelectRet::Ok + } + }) } fn socket_needs( @@ -2332,14 +2329,15 @@ mod _ssl { err: &ssl::Error, deadline: &SocketDeadline, vm: &VirtualMachine, - ) -> (Option, SelectRet) { + ) -> PyResult<(Option, SelectRet)> { let needs = match err.code() { ssl::ErrorCode::WANT_READ => Some(SslNeeds::Read), ssl::ErrorCode::WANT_WRITE => Some(SslNeeds::Write), _ => None, }; - let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline, vm)); - (needs, state) + let state = + needs.map_or(Ok(SelectRet::Ok), |needs| self.select(needs, deadline, vm))?; + Ok((needs, state)) } } @@ -2857,7 +2855,7 @@ mod _ssl { break; } // Wait briefly for peer's close_notify before retrying - match socket_stream.select(SslNeeds::Read, &deadline, vm) { + match socket_stream.select(SslNeeds::Read, &deadline, vm)? { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( vm, @@ -2895,7 +2893,7 @@ mod _ssl { }; // Wait on the socket - match socket_stream.select(needs, &deadline, vm) { + match socket_stream.select(needs, &deadline, vm)? { SelectRet::TimedOut => { let msg = if err == sys::SSL_ERROR_WANT_READ { "The read operation timed out" @@ -2991,7 +2989,7 @@ mod _ssl { let (needs, state) = stream .get_ref() .expect("handshake called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout, vm); + .socket_needs(&err, &timeout, vm)?; match state { SelectRet::TimedOut => { // Clean up SNI ex_data before returning error @@ -3045,7 +3043,7 @@ mod _ssl { .get_ref() .expect("write called in bio mode; should only be called in socket mode"); let timeout = socket_ref.timeout_deadline(); - let state = socket_ref.select(SslNeeds::Write, &timeout, vm); + let state = socket_ref.select(SslNeeds::Write, &timeout, vm)?; match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -3065,7 +3063,7 @@ mod _ssl { let (needs, state) = stream .get_ref() .expect("write called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout, vm); + .socket_needs(&err, &timeout, vm)?; match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -3236,7 +3234,7 @@ mod _ssl { let (needs, state) = stream .get_ref() .expect("read called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout, vm); + .socket_needs(&err, &timeout, vm)?; match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -4149,7 +4147,7 @@ mod bio { use openssl_sys as sys; use std::marker::PhantomData; - pub struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>); + pub(super) struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>); impl Drop for MemBioSlice<'_> { fn drop(&mut self) { @@ -4160,7 +4158,7 @@ mod bio { } impl<'a> MemBioSlice<'a> { - pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { + pub(super) fn new(buf: &'a [u8]) -> Result, ErrorStack> { openssl::init(); assert!(buf.len() <= c_int::MAX as usize); @@ -4172,7 +4170,7 @@ mod bio { Ok(MemBioSlice(bio, PhantomData)) } - pub fn as_ptr(&self) -> *mut sys::BIO { + pub(super) fn as_ptr(&self) -> *mut sys::BIO { self.0 } } diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index 07ff4488698..4e5def82bd5 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -125,6 +125,10 @@ pub(crate) mod ssl_error { ) } + #[cfg_attr( + all(feature = "ssl-openssl", not(feature = "ssl-rustls")), + expect(dead_code) + )] pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef { vm.new_os_subtype_error( PySSLZeroReturnError::class(&vm.ctx).to_owned(), @@ -133,6 +137,10 @@ pub(crate) mod ssl_error { ) } + #[cfg_attr( + all(feature = "ssl-openssl", not(feature = "ssl-rustls")), + expect(dead_code) + )] pub(crate) fn create_ssl_syscall_error( vm: &VirtualMachine, msg: impl Into,