August 24, 2023·4 min read·engineeringpythontlssecuritystdlib

Python 3.11.5 repairs the boundary before a TLS handshake

An SSLSocket fix prevents pre-handshake plaintext from being treated as protected application data. Other fixes preserve useful errors at native and process boundaries.

A socket wrapped for TLS has not necessarily completed a TLS handshake. That distinction is central to CVE-2023-40217, fixed in Python 3.11.5. The failure allowed data sent before a handshake, followed by an immediate connection close, to reach an application as though it were protected data received after the handshake. Certificate verification is among the protections bypassed when that boundary fails.

The August 24 release page highlights this security fix. The CPython issue, opened August 22, attributes the report to Aapo Oksman through the Python Security Response Team. It describes an ssl.SSLSocket problem involving the transition into TLS, not a general defeat of TLS encryption.

I care about this distinction because a useful application-level abstraction should make its security state clear. Calling an object an SSL socket cannot substitute for completing the protocol steps that establish what the peer has proved and which data has protection.

Connection state changes what received data means

The reported sequence combines plaintext input with an immediate close. That matters because the application must distinguish data buffered before secure communication begins from data accepted through the established TLS channel. Returning bytes without preserving that distinction can give them a meaning they have not earned through the protocol.

Certificate checks belong to establishing the channel. If an application accepts pre-handshake bytes as post-handshake data, the problem occurs before those checks can provide their intended guarantee. Encryption strength does not repair a path that treats input as though the encrypted session already exists.

This is an implementation boundary inside the socket wrapper. Application code can use a high-level TLS object and still depend on its internal state transitions being correct. The security note therefore deserves a more precise reading than either assuming all TLS applications are equally exposed or dismissing it because the application never manipulates raw sockets directly.

The release identifies Gregory P. Smith as the patch author. Its versioned changelog also records a separate certificate-error repair. Some OpenSSL failures return SSL_ERROR_SYSCALL while carrying error details indicating certificate verification failure. Python now detects that combination and raises ssl.SSLCertVerificationError.

That second repair concerns error classification. An application deciding what to log or report needs the exception to describe the underlying failure. It is separate from preventing pre-handshake data from taking an unintended path. Both fixes improve the relationship between protocol state and the behavior Python exposes.

Failed operations must preserve their errors

Several other 3.11.5 entries concern errors being overwritten during failure handling. The changelog includes module initialization and C API helpers that construct exceptions. These are paths that execute when an operation has already failed, so another failure during reporting can obscure the original state or create additional problems.

The C API changes include saving an operating-system error code before decoding a filename. Decoding can itself perform work that affects error state. Capturing the code first preserves the information needed to report the operation that actually failed.

Other exception-construction helpers no longer crash or ignore errors when message formatting or filename decoding fails. They retain the corresponding error. This is useful maintenance work because diagnostic behavior has to remain defined precisely when resources or input are abnormal.

Python is often the interface to native libraries in the software I choose it for. Reliable exception behavior at that boundary matters to me. An extension failure is already difficult enough to interpret without losing the error while attempting to describe it.

Process APIs also need actionable failures

The same release prevents multiprocessing synchronization objects created under a fork context from being sent to a process created under a spawn context. Its changelog describes the result as an actionable RuntimeError in the parent in place of a segmentation fault. The incompatible combination is rejected where the programmer can identify it.

This does not make the two process arrangements interchangeable. It exposes an unsupported combination through Python error handling. A parent-side error is easier to connect to the construction of the synchronization object than a crash in a child process after launch.

Another fix addresses ProcessPoolExecutor hanging when a child crashes while data is being written to the call queue. A child failure must eventually become observable to the code coordinating the pool. Continuing to wait for progress that cannot happen leaves the application with less information than an explicit failure.

Python 3.11.5 also repairs tarfile.data_filter(). The filter now considers the location of a symbolic link when determining its target, avoiding rejection of some valid archives with LinkOutsideDestinationError. A restriction can be necessary and still reject legitimate input if its path interpretation is wrong.

This follows the extraction-policy mechanism added to the maintenance branch. The policy distinguishes data archives from full filesystem restoration, but its implementation still has to interpret archive links correctly. Preserving the intended security boundary includes accepting paths that remain within it, so callers can apply the restriction without unnecessarily abandoning valid archives.