Implement rustls-backed _ssl module - #1167
Conversation
WalkthroughThis PR adds a native Rustls-backed ChangesNative TLS implementation
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant PythonSSL
participant TlsConnection
participant Rustls
participant Peer
PythonSSL->>TlsConnection: create client or server connection
TlsConnection->>Rustls: initialize configuration and handshake state
TlsConnection->>Peer: exchange TLS records
Rustls-->>TlsConnection: produce handshake and plaintext results
TlsConnection-->>PythonSSL: return I/O, metadata, or TLS errors
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
47c948e to
be37310
Compare
5cbcf42 to
4462a84
Compare
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 4462a84). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)None identified in the reviewed changed paths. The socket changes are parity improvements: the process-wide resolver lock and copying of 4. Structural adaptations
|
There was a problem hiding this comment.
💡 Codex Review
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-native/src/ssl.rs#L1888-L1889
Preserve required client authentication without eager roots
When a server context uses CERT_REQUIRED with no eagerly loaded roots—including the common case where trust was configured only through capath—this branch selects with_no_client_auth(). The server therefore does not request or validate a client certificate and can accept unauthenticated clients despite the explicitly required mode; an empty trust store should cause authentication to fail, while capath certificates must be incorporated into the verifier rather than disabling it.
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-native/src/ssl.rs#L1891-L1892
Apply configured CRLs to client-certificate verification
When a server loads a CA file containing CRLs and enables VERIFY_CRL_CHECK_LEAF or VERIFY_CRL_CHECK_CHAIN, this verifier is built only from context.roots; unlike the client verifier, it never receives context.crls. Consequently a revoked client certificate can still authenticate to a server that explicitly requested revocation checking.
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-interpreter/src/module/_ssl/mod.rs#L905-L907
Honor SSL_CERT_DIR during default trust loading
When deployments provide additional trust through SSL_CERT_DIR, set_default_verify_paths() never reads or registers that directory, and the early return for a valid SSL_CERT_FILE also prevents any second source from being considered. Clients relying on hashed enterprise or container CA directories will therefore fail certificate verification even though _ssl.get_default_verify_paths() advertises this environment variable.
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-interpreter/src/module/_ssl/mod.rs#L711-L712
Apply num_tickets to generated server configurations
When a server sets context.num_tickets—especially to 0 to disable TLS 1.3 session tickets—the setter only updates this interpreter-side field, which is never passed to pyre_native::ssl::Context or read by server_config(). Rustls therefore continues using its default ticket behavior while the property reports the requested value, defeating session-resumption policy configured by the application.
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-interpreter/src/module/_ssl/mod.rs#L949-L952
Report the context's filtered cipher list
After set_ciphers() restricts the context's TLS 1.2 suites, get_ciphers() still iterates the process-wide static cipher census rather than the suites stored on this context. Callers therefore see disabled suites reported as enabled, which breaks feature detection and configuration auditing even though connection creation applies the filter.
https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-interpreter/src/module/_socket/interp_socket.rs#L2068-L2070
Preserve the accept timeout deadline across EINTR
When poll() is repeatedly interrupted by handled signals, every retry receives the original full timeout_ms instead of the remaining duration. A socket with a positive accept timeout can consequently block well beyond its configured deadline, potentially indefinitely under a steady signal stream; compute an absolute deadline once and reduce the timeout after each EINTR.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 20
🔇 Additional comments (42)
pyre/pyre-interpreter/src/module/_socket/interp_socket.rs (6)
49-58: LGTM!Also applies to: 70-78
874-895: LGTM!Also applies to: 924-924, 958-958
1608-1669: LGTM!Also applies to: 1998-1998, 2383-2395, 2443-2452
2581-2592: 🩺 Stability & Availability
⚠️ Unverified finding
Sandbox verification was unavailable.Verify the uninitialized descriptor sentinel.
__new__can allocate an instance before__init__installs_fd. Confirm thatsocket_get_attr_i64(obj, "_fd")returns a negative value when_fdis absent. Otherwise finalization can close file descriptor 0 or fail during cleanup.
1019-1030: 🎯 Functional CorrectnessCheck the resolver buffer lifetime.
If the second resolver call can overwrite the storage before consuming
first_addr, copy the address bytes to local storage first.
2917-2934: 🩺 Stability & AvailabilityVerify the blocking state before
accept.Both paths call
socket_wait_readable(obj, fd)?beforelibc::accept(...). Confirm that a positive timeout cannot leavefdblocking after another thread consumes the pending connection. If it can, use an accept path that remains bounded by the timeout.Cargo.toml (1)
172-178: 🗄️ Data Integrity & Integration
⚠️ Unverified finding
Sandbox verification was unavailable.Verify the published versions and the cross-crate compatibility of the RustCrypto set.
pkcs8 0.11andder 0.8must agree on the samedermajor version, becausessl.rspasses ader::SecretDocumentbyte slice intopkcs8::EncryptedPrivateKeyInfoRef. A mismatched pair fails to compile or silently pulls twodercopies. Also confirm thatx509-parser 0.18andpem-rfc7468 1exist as released versions.pyre/pyre-native/Cargo.toml (1)
19-26: LGTM!pyre/pyre-native/src/lib.rs (1)
8-9: LGTM!pyre/pyre-native/src/ssl.rs (16)
20-32: LGTM!Also applies to: 39-127
178-202: 🎯 Functional Correctness
⚠️ Unverified finding
Sandbox verification was unavailable.Verify the accepted protocol set against the
_sslconstants the interpreter exports.This match accepts
2,16,17,5, and6. CPython definesPROTOCOL_TLSv1 == 3andPROTOCOL_TLSv1_1 == 4, and both are rejected here, sossl.SSLContext(ssl.PROTOCOL_TLSv1)raises "invalid or unsupported protocol version". CPython also defines no protocol constant with value6, yet6maps to a TLS 1.3-only context.Confirm that
5and6match the values the interpreter_sslmodule publishes, and confirm that rejecting3and4is intended for this backend.
335-344: LGTM!Also applies to: 347-356, 358-387, 398-431, 433-447, 452-483, 491-497, 504-506, 513-527, 529-539, 547-559
561-574: LGTM!Also applies to: 581-605, 632-679, 681-691, 693-793, 796-805, 810-814, 819-821, 830-832, 834-952
1104-1142: LGTM!
1150-1178: LGTM!
1183-1233: LGTM!
1270-1336: LGTM!Also applies to: 1375-1404, 1432-1456
1481-1492: LGTM!Also applies to: 1526-1547, 1554-1581
1599-1684: LGTM!Also applies to: 1686-1761, 1773-1815
1817-1876: LGTM!Also applies to: 1911-1930
1963-1965: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
⚠️ Unverified finding
Sandbox verification was unavailable.Bound the observer buffers and confirm that
message_eventsis always drained.Two accumulation paths have no bound.
message_eventsgrows on every observed record and is emptied only byconnection_take_message_events. If the interpreter calls that function only when a Python_msg_callbackis installed, then every connection without a callback retains oneTlsMessageEventper record, each holding aVec<u8>copy of the payload, for the whole connection lifetime.
self.handshakesaccepts a 24-bitmessage_len, so a peer can declare a 16 MiB handshake message and drip bytes.observeruns at line 2391 on raw received bytes, before rustls inspects them, so rustls' own limits do not cap this buffer.Drop events when no callback is registered, and reject a handshake length above the TLS record limits.
Also applies to: 2006-2027
2034-2073: LGTM!Also applies to: 2075-2107
2144-2145: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
⚠️ Unverified finding
Sandbox verification was unavailable.
server_contextis an unowned raw pointer that is dereferenced after the handshake.
connection_accept_serverstores the caller's*const Contextat line 2469, andnote_server_resumptiondereferences it at line 2237 on everyprocess_received_tlscall.TlsConnectionholds no reference count on theContext. Every other native value in this file is owned throughBox::into_rawand freed explicitly, so nothing in the native layer keeps thisContextalive.If the Python
SSLContextis collected while theSSLSocketstill exists, this dereference reads freed memory.connection_newat line 2298 also dereferences a borrowedContext, but that deref completes before the function returns; this one outlives the call.Confirm that the interpreter
W_SSLSocketholds a strong reference to itsW_SSLContextfor the socket's whole lifetime, including afterconnection_accept_server. If it does not, store the resumption counter behind anArcshared with theContextinstead of a raw pointer.Also applies to: 2226-2242
2149-2192: LGTM!Also applies to: 2244-2284
2291-2367: LGTM!Also applies to: 2384-2413, 2421-2433, 2441-2480, 2488-2505, 2512-2516, 2518-2529, 2537-2539, 2546-2570, 2575-2588, 2593-2602, 2607-2634, 2639-2702, 2711-2730, 2776-2785
pyre/pyre-interpreter/src/module/mod.rs (1)
61-62: LGTM!pyre/pyre-interpreter/src/importing.rs (1)
683-685: LGTM!pyre/pyre-interpreter/src/lib.rs (2)
1149-1160: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
⚠️ Unverified finding
Sandbox verification was unavailable.The truncation gate does not match the alias gate.
The five
_sslaliases at lines 1130-1139 are compiled out when the target iswasm32or thesandboxfeature is on. The truncation here applies only when the target is notwasm32andsandboxis on. Awasm32build therefore drops the five aliases but keeps the full hierarchy, including the five trailing_sslslots.The doc comment states that
pyre-objectmust not learn thesandboxfeature, soSUBCLASS_RANGE_HIERARCHYalways contains those slots. Onwasm32the hierarchy then declares slots 170-174 with no registered type, and their parent slot 169 (posix::W_DirEntry) is also absent. The comment at lines 1123-1124 stated that nothing unconditional followed slot 169; the_sslslots now do.
SSL_HIERARCHY_SLOTSis also a hardcoded5that must track the alias list by hand.Use one predicate for both the aliases and the truncation, and derive the slot count from a single constant.
🛠️ Proposed direction
pub fn active_subclass_range_hierarchy() -> &'static [(u32, Option<u32>)] { let hierarchy = pyre_object::pyobject::SUBCLASS_RANGE_HIERARCHY; - #[cfg(all(not(target_arch = "wasm32"), feature = "sandbox"))] + #[cfg(any(target_arch = "wasm32", feature = "sandbox"))] { const SSL_HIERARCHY_SLOTS: usize = 5; &hierarchy[..hierarchy.len() - SSL_HIERARCHY_SLOTS] } - #[cfg(not(all(not(target_arch = "wasm32"), feature = "sandbox")))] + #[cfg(not(any(target_arch = "wasm32", feature = "sandbox")))] { hierarchy } }Confirm the
wasm32requirement before you apply this. Ifwasm32also drops slot 169, the truncation count differs again for that target.Run the following script to check how the hierarchy is declared and consumed:
1127-1140: LGTM!pyre/pyre-interpreter/src/module/_ssl/mod.rs (7)
16-94: LGTM!
204-254: LGTM!
256-362: LGTM!
649-668: 🗄️ Data Integrity & Integration
⚠️ Unverified finding
Sandbox verification was unavailable.Verify the un-normalized TLS version sentinel reaching the native setters.
set_minimum_versionmaps-1to0x304but forwards-2unchanged.set_maximum_versionmaps-2to0x303but forwards-1unchanged. Each setter normalizes one sentinel and passes the other raw intopyre_native::ssl.Confirm that
context_set_minimum_versionandcontext_set_maximum_versioninterpret the raw sentinel correctly, and that a laterminimum_version/maximum_versionread returns the sentinel CPython returns.
1258-1316: LGTM!
1778-1810: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
⚠️ Unverified finding
Sandbox verification was unavailable.
WritableBufferis acquired but never released, and it spans a GC-triggering Python call.Two concerns on the same acquisition at Lines 1778-1786.
First, no
release()call.writablegoes out of scope at the end ofreadwithout one. Every other buffer acquisition in this module releases explicitly, includingW_MemoryBIO::writeat Line 1299 andW_SSLSocket::writeat Line 1751.ssl.SSLSocket.recv_intodrives this path on every read, so a missing release leaks one export per call and permanently locks the targetbytearrayormemoryviewagainst resize.Second, the acquisition is live across the loop at Lines 1797-1806.
receive_transportcalls the Pythonsocket.recvmethod, which can run arbitrary code and trigger a moving collection. Line 1808 then callsbuffer.as_mut_slice()and writes through it. IfWritableBuffercaches a raw data pointer at acquire time, that pointer is stale after a move and the write corrupts memory.Confirm both properties of
crate::builtins::WritableBufferbefore merge.
2034-2068: LGTM!pyre/pyre-object/src/pyobject.rs (2)
691-705: LGTM!Also applies to: 723-724, 772-777
659-672: 🩺 Stability & Availability | ⚡ Quick win
⚠️ Unverified finding
Sandbox verification was unavailable.The
_sslsubclass-range census and the_sslGC registrations use different cfg gates.SUBCLASS_RANGE_HIERARCHYdeclares ids 170-174 undernot(target_arch = "wasm32")alone, while the GC registrations that must match those ids are additionally gated onnot(feature = "sandbox"). On a native--features sandboxbuild the two sets disagree unlessactive_subclass_range_hierarchy()andall_subclass_range_aliases()both truncate the_ssltail. The assertion atpyre/pyre-jit/src/eval.rsLines 3971-3975 and the alias assertion insidecompute_subclass_ranges_from_hierarchyboth panic at interpreter start if the truncation is missing.
pyre/pyre-object/src/pyobject.rs#L659-L672: confirm that the interpreter truncates these five entries undersandbox, as the comment claims; if it does not, gate the entries onnot(feature = "sandbox")as well.pyre/pyre-jit/src/eval.rs#L3623-L3719: confirm thesandboxgate on this block leavesactual_hierarchyending at id 169 and that Line 3973 receives a matching truncated slice.pyre/pyre-interpreter/src/typedef.rs#L277-L280: confirmcrate::all_subclass_range_aliases()omits the_sslaliases undersandbox, so thewitnesses[alias.type_id].is_some()assertion does not fire.Add a sandbox build to CI so the mismatch cannot regress silently.
pyre/pyre-interpreter/src/objspace/std/mapdict.rs (1)
559-578: LGTM!Also applies to: 599-599
pyre/pyre-jit/src/eval.rs (1)
693-719: LGTM!Also applies to: 776-808
pyre/pyre-jit/Cargo.toml (1)
24-27: LGTM!pyre/pyrex/Cargo.toml (1)
43-43: LGTM!
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pyre/pyre-interpreter/src/module/_socket/interp_socket.rs`:
- Around line 2061-2081: Update the poll loop around timeout_ms to compute a
monotonic deadline once before entering the loop, then derive each poll timeout
from the remaining duration after EINTR. Ensure expired deadlines return the
existing timeout error, and round any positive remaining duration below one
millisecond up to a one-millisecond poll timeout.
- Around line 2025-2037: Update timeout handling so positive integer values
stored in _timeout are treated like positive floats, both in the
EAGAIN/EWOULDBLOCK conversion path and in socket_wait_readable. Prefer
normalizing _timeout when set; otherwise update the relevant type checks to
accept both integer and float values while preserving TimeoutError conversion
and polling behavior.
- Around line 3103-3106: Update the error paths for sendto, recvfrom, recv_into,
recvfrom_into, recvmsg, recvmsg_into, and sendmsg to call
socket_io_err_for_operation with the socket object and underlying OS error,
matching send, sendall, and recv. Ensure all timed I/O operations consistently
convert EAGAIN according to the socket timeout.
In `@pyre/pyre-interpreter/src/module/_ssl/mod.rs`:
- Around line 2192-2199: Update get_default_verify_paths to derive its
certificate file and directory values from the same native trust-store source
used by context_load_native_roots and set_default_verify_paths, rather than
returning hardcoded paths. Report the actual resolved paths for the current
platform, preserving the expected tuple structure and environment variable
names.
- Around line 994-1014: Update load_dh_params to avoid silently reporting
successful configuration when the validated DH parameters are not applied by the
backend. After preserving required-argument, file-reading, and PEM validation
behavior, raise an ssl_error stating that custom DH parameters are unsupported,
following the unsupported-capability handling used by _set_npn_protocols; do not
return Ok(()) for valid-but-ignored parameters.
- Around line 1211-1218: Update _set_alpn_protocols so the acquired buffer is
released before propagating errors from parse_length_prefixed_protocols, while
retaining the existing release after successful parsing. Follow the cleanup
pattern used by W_MemoryBIO::write and the cadata branch, ensuring malformed
ALPN input cannot leave the exporter held.
- Around line 426-448: Define named constants for the socket and owner positions
beside the shadow-stack root list, and use those constants when retrieving and
replacing entries instead of hardcoded first + 1 and first + 6 offsets. Keep the
constants aligned with the corresponding entries so future reordering requires
an explicit update.
- Around line 146-198: Refactor tls_error to avoid constructing and discarding
the initial exception from ssl_error: create the base PyError without an
exception object, then attach the class-specific exception built in its existing
lookup path. Extract the duplicated “[library: reason]” parsing from ssl_error
and tls_error into a shared split_library_reason helper, and reuse it in both
functions while preserving the existing library and reason attributes.
- Around line 898-912: Update set_default_verify_paths so a valid SSL_CERT_FILE
is loaded without returning early; always call context_load_native_roots
afterward so native trust directories and roots remain available. Preserve the
existing behavior for invalid or unset SSL_CERT_FILE values.
- Around line 779-792: Replace the raw key-file scan in the key-loading flow
with parse-result-driven handling: update context_load_cert_chain to return a
distinct password-required outcome, then invoke password_bytes only for that
outcome and retry loading with the supplied password. Preserve the existing
no-callback behavior for unencrypted keys and the callback ordering, while
removing the duplicate std::fs::read probe.
In `@pyre/pyre-native/src/ssl.rs`:
- Around line 1081-1085: In pyre/pyre-native/src/ssl.rs lines 1081-1085, add a
single explicit rustls CipherSuite-variant-to-stable-name mapping and use it in
cipher_pattern_matches instead of Debug output; apply the same mapped name in
openssl_cipher_name at lines 2753-2769 when constructing the OpenSSL-style value
returned by SSLSocket.cipher().
- Around line 1494-1524: Update capath_certificates and its client_config call
path to cache parsed, deduplicated certificates per Context, keyed by the
current capaths list, so directory scanning and PEM parsing occur only when the
key changes. Add storage for the cached key and result on Context, and
invalidate that cache in context_add_verify_dir whenever a directory is added.
- Around line 2328-2330: Replace address-based context identity checks in the
relevant session creation and reuse paths with a process-wide monotonic ID: add
an AtomicU64 counter, assign its fetch_add result to Context::identity in
Context::new, and compare context.identity instead of casting Context pointers
to usize. Update both context_identity capture sites while preserving the
existing mismatch error behavior.
- Around line 172-173: Define descriptive named constants for each OpenSSL
option bit used by DEFAULT_OPTIONS, confirming each literal’s meaning before
assigning names, then compose DEFAULT_OPTIONS from those constants instead of
raw literals. Reuse the same constants in enabled_versions for the 0x0800_0000
and 0x2000_0000 version-disable checks so both sites share one authoritative
definition.
- Around line 1888-1901: Update the client-authentication selection around
wants_server_cert so an empty context.roots does not fall back to
with_no_client_auth() when verify_mode is CERT_REQUIRED or CERT_OPTIONAL. Return
an error for either certificate-verifying mode without configured roots, while
preserving no-client-auth behavior for CERT_NONE and existing verifier
construction when roots are available.
- Around line 1854-1856: Define named constants for the OpenSSL verify_flags
values 32 and 12 alongside VERIFY_X509_PARTIAL_CHAIN, then update the
require_authority_key_identifier and require_crl checks to use those constants
instead of numeric literals.
- Around line 2110-2126: Refactor certificate_error_details to derive its
code-to-message entries from the existing certificate_verify_message table,
making certificate_verify_message the single source of truth. Preserve the
current fallback and exception-detail behavior while removing the duplicated
mappings so SSLCertVerificationError.verify_message and exception text remain
consistent.
- Around line 2741-2751: The connection_tls_unique function must not derive or
return an exporter as tls-unique. Replace its successful TLS 1.2 exporter path
with the CPython-compatible unsupported-binding error behavior, and keep any RFC
9266 exporter functionality separate under its own API if required.
- Around line 1370-1374: Update the CRL verification configuration near the
require_crl/has_crl handling to distinguish CRL_CHECK from CRL_CHECK_ALL: use
end-entity-only checking when only CRL_CHECK is set, while retaining full-chain
checking for CRL_CHECK_ALL. Preserve the existing missing-CRL error behavior and
use the surrounding verify_flags logic to make the distinction.
- Around line 2211-2216: Replace the `read_tls` error-text match in the
surrounding TLS read flow with a `wants_read()` check on the rustls connection,
using it to detect plaintext backpressure and return successfully without
relying on `ErrorKind::Other` or the unstable `"received plaintext buffer full"`
text. Add a test covering a full plaintext buffer and verifying the read path
handles it successfully.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 16d502d8-cc98-4a08-8823-73684bd7d35d
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
Cargo.tomlpyre/pyre-interpreter/src/importing.rspyre/pyre-interpreter/src/lib.rspyre/pyre-interpreter/src/module/_socket/interp_socket.rspyre/pyre-interpreter/src/module/_ssl/mod.rspyre/pyre-interpreter/src/module/mod.rspyre/pyre-interpreter/src/objspace/std/mapdict.rspyre/pyre-interpreter/src/typedef.rspyre/pyre-jit/Cargo.tomlpyre/pyre-jit/src/eval.rspyre/pyre-native/Cargo.tomlpyre/pyre-native/src/lib.rspyre/pyre-native/src/ssl.rspyre/pyre-object/src/pyobject.rspyre/pyrex/Cargo.toml
| if errno == libc::EAGAIN || errno == libc::EWOULDBLOCK { | ||
| let d = crate::baseobjspace::getdict_native(obj); | ||
| if !d.is_null() | ||
| && let Some(timeout) = unsafe { pyre_object::w_dict_getitem_str(d, "_timeout") } | ||
| && unsafe { pyre_object::is_float(timeout) } | ||
| && unsafe { pyre_object::floatobject::w_float_get_value(timeout) } > 0.0 | ||
| { | ||
| // RPython's RSocket._select() turns expiry of a positive timeout | ||
| // into SocketTimeout, which interp_socket maps to TimeoutError. | ||
| // This backend uses SO_RCVTIMEO/SO_SNDTIMEO, whose equivalent | ||
| // expiry signal is EAGAIN/EWOULDBLOCK. | ||
| return socket_converted_error("timeout", None, "timed out"); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Handle positive integer timeout values.
settimeout(1) stores an integer in _timeout, but this check accepts only floats. EAGAIN then becomes a generic socket error instead of TimeoutError. The same type check also makes socket_wait_readable skip polling for integer timeouts. Normalize _timeout when it is set, or accept both int and float here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/_socket/interp_socket.rs` around lines 2025
- 2037, Update timeout handling so positive integer values stored in _timeout
are treated like positive floats, both in the EAGAIN/EWOULDBLOCK conversion path
and in socket_wait_readable. Prefer normalizing _timeout when set; otherwise
update the relevant type checks to accept both integer and float values while
preserving TimeoutError conversion and polling behavior.
| let timeout_ms = (timeout * 1000.0 + 0.5).min(i32::MAX as f64) as i32; | ||
| loop { | ||
| let mut pollfd = libc::pollfd { | ||
| fd, | ||
| events: libc::POLLIN, | ||
| revents: 0, | ||
| }; | ||
| let (ready, errno) = crate::module::thread::call_external_function(|| unsafe { | ||
| libc::poll(&mut pollfd, 1, timeout_ms) | ||
| }); | ||
| if ready > 0 { | ||
| return Ok(()); | ||
| } | ||
| if ready == 0 { | ||
| return Err(socket_converted_error("timeout", None, "timed out")); | ||
| } | ||
| if errno != libc::EINTR { | ||
| return Err(socket_io_err(std::io::Error::from_raw_os_error(errno))); | ||
| } | ||
| crate::module::signal::interp_signal::checksignals_now()?; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Preserve the original timeout deadline across EINTR.
Each interrupted poll restarts timeout_ms. Repeated signals can extend a finite timeout indefinitely. Compute a monotonic deadline once, poll only for the remaining duration, and round a positive sub-millisecond remainder up to one millisecond.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/_socket/interp_socket.rs` around lines 2061
- 2081, Update the poll loop around timeout_ms to compute a monotonic deadline
once before entering the loop, then derive each poll timeout from the remaining
duration after EINTR. Ensure expired deadlines return the existing timeout
error, and round any positive remaining duration below one millisecond up to a
one-millisecond poll timeout.
| return Err(socket_io_err_for_operation( | ||
| obj, | ||
| std::io::Error::from_raw_os_error(errno), | ||
| )); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Apply timeout conversion to every timed I/O operation.
send, sendall, and recv use socket_io_err_for_operation, but sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into, and sendmsg still use socket_io_err. A positive socket timeout can therefore produce different exception types for equivalent EAGAIN results. Use the operation-aware conversion consistently.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/_socket/interp_socket.rs` around lines 3103
- 3106, Update the error paths for sendto, recvfrom, recv_into, recvfrom_into,
recvmsg, recvmsg_into, and sendmsg to call socket_io_err_for_operation with the
socket object and underlying OS error, matching send, sendall, and recv. Ensure
all timed I/O operations consistently convert EAGAIN according to the socket
timeout.
| fn tls_error(code: i32, message: String) -> crate::PyError { | ||
| let verify_code = (code >= pyre_native::ssl::TLS_ERROR_CERT_VERIFY_BASE) | ||
| .then_some(code - pyre_native::ssl::TLS_ERROR_CERT_VERIFY_BASE); | ||
| let class_name = match code { | ||
| pyre_native::ssl::TLS_ERROR_WANT_READ => "_ssl.SSLWantReadError", | ||
| pyre_native::ssl::TLS_ERROR_WANT_WRITE => "_ssl.SSLWantWriteError", | ||
| pyre_native::ssl::TLS_ERROR_ZERO_RETURN => "_ssl.SSLZeroReturnError", | ||
| pyre_native::ssl::TLS_ERROR_EOF => "_ssl.SSLEOFError", | ||
| _ if verify_code.is_some() => "_ssl.SSLCertVerificationError", | ||
| _ => "_ssl.SSLError", | ||
| }; | ||
| let mut error = ssl_error(message.clone()); | ||
| let public_errno = if verify_code.is_some() { | ||
| pyre_native::ssl::TLS_ERROR_SSL | ||
| } else { | ||
| code | ||
| }; | ||
| if let Some(class) = crate::builtins::lookup_exc_class(class_name) | ||
| && let Ok(exception) = crate::builtins::exc_os_error_new(&[ | ||
| class, | ||
| w_int_new(public_errno as i64), | ||
| w_str_new(&message), | ||
| ]) | ||
| { | ||
| if let Some(close) = message.find(']') | ||
| && message.starts_with('[') | ||
| && let Some((library, reason)) = message[1..close].split_once(": ") | ||
| { | ||
| let _ = crate::baseobjspace::setattr_str(exception, "library", w_str_new(library)); | ||
| let _ = crate::baseobjspace::setattr_str(exception, "reason", w_str_new(reason)); | ||
| } | ||
| if let Some(verify_code) = verify_code { | ||
| let _ = crate::baseobjspace::setattr_str( | ||
| exception, | ||
| "verify_code", | ||
| w_int_new(verify_code as i64), | ||
| ); | ||
| let _ = crate::baseobjspace::setattr_str( | ||
| exception, | ||
| "verify_message", | ||
| w_str_new(pyre_native::ssl::certificate_verify_message(verify_code)), | ||
| ); | ||
| let _ = crate::baseobjspace::setattr_str(exception, "library", w_str_new("SSL")); | ||
| let _ = crate::baseobjspace::setattr_str( | ||
| exception, | ||
| "reason", | ||
| w_str_new("CERTIFICATE_VERIFY_FAILED"), | ||
| ); | ||
| } | ||
| error.exc_object = exception; | ||
| } | ||
| error | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Avoid the redundant exception construction in tls_error.
tls_error calls ssl_error(message.clone()) at Line 157. ssl_error builds a complete _ssl.SSLError instance and sets library and reason on it. Lines 163-196 then build a second exception of the specific class and overwrite error.exc_object, so the first instance is discarded.
TLS_ERROR_WANT_READ and TLS_ERROR_WANT_WRITE travel this path on every non-blocking handshake step and every read() that needs more data. Each raise allocates two exception objects and performs duplicate attribute stores.
Build the base PyError without the exception object, then attach the class-specific instance. The [library: reason] parsing at Lines 103-109 and Lines 170-176 is also identical; extract it into one helper.
♻️ Proposed refactor
+fn split_library_reason(message: &str) -> Option<(&str, &str)> {
+ let close = message.find(']')?;
+ if !message.starts_with('[') {
+ return None;
+ }
+ message[1..close].split_once(": ")
+}
+
fn tls_error(code: i32, message: String) -> crate::PyError {
let verify_code = (code >= pyre_native::ssl::TLS_ERROR_CERT_VERIFY_BASE)
.then_some(code - pyre_native::ssl::TLS_ERROR_CERT_VERIFY_BASE);
@@
- let mut error = ssl_error(message.clone());
+ let mut error = crate::PyError::os_error(message.clone());
let public_errno = if verify_code.is_some() {Then reuse split_library_reason in both ssl_error and tls_error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/_ssl/mod.rs` around lines 146 - 198,
Refactor tls_error to avoid constructing and discarding the initial exception
from ssl_error: create the base PyError without an exception object, then attach
the class-specific exception built in its existing lookup path. Extract the
duplicated “[library: reason]” parsing from ssl_error and tls_error into a
shared split_library_reason helper, and reuse it in both functions while
preserving the existing library and reason attributes.
| for value in [ | ||
| context, | ||
| socket, | ||
| socket_send, | ||
| socket_recv, | ||
| incoming, | ||
| outgoing, | ||
| owner, | ||
| server_hostname, | ||
| ] { | ||
| pyre_object::gc_roots::pin_root(value); | ||
| } | ||
| let first = pyre_object::gc_roots::shadow_stack_len() - 8; | ||
| let rooted_socket = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 1) }; | ||
| if !unsafe { is_none(rooted_socket) } { | ||
| let weak_socket = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_socket); | ||
| pyre_object::gc_roots::shadow_stack_set(first + 1, weak_socket); | ||
| } | ||
| let rooted_owner = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 6) }; | ||
| if !unsafe { is_none(rooted_owner) } { | ||
| let weak_owner = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_owner); | ||
| pyre_object::gc_roots::shadow_stack_set(first + 6, weak_owner); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Remove the positional slot coupling in the shadow-stack indices.
Lines 438-447 patch shadow-stack slots first + 1 and first + 6 to weakref boxes. Those indices depend on the exact order of the array literal at Lines 426-435. If a future change inserts or reorders a field in that literal, the weakref boxing silently applies to the wrong value, and the socket or owner becomes a strong reference that keeps a cycle alive.
Bind the two indices to named constants next to the literal so the coupling is explicit and a reorder is visible.
♻️ Proposed refactor
+ // Slot offsets into the pinned-root array below; keep in lockstep.
+ const SOCKET_SLOT: usize = 1;
+ const OWNER_SLOT: usize = 6;
for value in [
context,
socket,
@@
let first = pyre_object::gc_roots::shadow_stack_len() - 8;
- let rooted_socket = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 1) };
+ let rooted_socket = unsafe { pyre_object::gc_roots::shadow_stack_get(first + SOCKET_SLOT) };
if !unsafe { is_none(rooted_socket) } {
let weak_socket = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_socket);
- pyre_object::gc_roots::shadow_stack_set(first + 1, weak_socket);
+ pyre_object::gc_roots::shadow_stack_set(first + SOCKET_SLOT, weak_socket);
}
- let rooted_owner = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 6) };
+ let rooted_owner = unsafe { pyre_object::gc_roots::shadow_stack_get(first + OWNER_SLOT) };
if !unsafe { is_none(rooted_owner) } {
let weak_owner = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_owner);
- pyre_object::gc_roots::shadow_stack_set(first + 6, weak_owner);
+ pyre_object::gc_roots::shadow_stack_set(first + OWNER_SLOT, weak_owner);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for value in [ | |
| context, | |
| socket, | |
| socket_send, | |
| socket_recv, | |
| incoming, | |
| outgoing, | |
| owner, | |
| server_hostname, | |
| ] { | |
| pyre_object::gc_roots::pin_root(value); | |
| } | |
| let first = pyre_object::gc_roots::shadow_stack_len() - 8; | |
| let rooted_socket = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 1) }; | |
| if !unsafe { is_none(rooted_socket) } { | |
| let weak_socket = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_socket); | |
| pyre_object::gc_roots::shadow_stack_set(first + 1, weak_socket); | |
| } | |
| let rooted_owner = unsafe { pyre_object::gc_roots::shadow_stack_get(first + 6) }; | |
| if !unsafe { is_none(rooted_owner) } { | |
| let weak_owner = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_owner); | |
| pyre_object::gc_roots::shadow_stack_set(first + 6, weak_owner); | |
| } | |
| // Slot offsets into the pinned-root array below; keep in lockstep. | |
| const SOCKET_SLOT: usize = 1; | |
| const OWNER_SLOT: usize = 6; | |
| for value in [ | |
| context, | |
| socket, | |
| socket_send, | |
| socket_recv, | |
| incoming, | |
| outgoing, | |
| owner, | |
| server_hostname, | |
| ] { | |
| pyre_object::gc_roots::pin_root(value); | |
| } | |
| let first = pyre_object::gc_roots::shadow_stack_len() - 8; | |
| let rooted_socket = | |
| unsafe { pyre_object::gc_roots::shadow_stack_get(first + SOCKET_SLOT) }; | |
| if !unsafe { is_none(rooted_socket) } { | |
| let weak_socket = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_socket); | |
| pyre_object::gc_roots::shadow_stack_set(first + SOCKET_SLOT, weak_socket); | |
| } | |
| let rooted_owner = | |
| unsafe { pyre_object::gc_roots::shadow_stack_get(first + OWNER_SLOT) }; | |
| if !unsafe { is_none(rooted_owner) } { | |
| let weak_owner = pyre_object::weakref::w_gc_weakref_box_new_or_strong(rooted_owner); | |
| pyre_object::gc_roots::shadow_stack_set(first + OWNER_SLOT, weak_owner); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/_ssl/mod.rs` around lines 426 - 448, Define
named constants for the socket and owner positions beside the shadow-stack root
list, and use those constants when retrieving and replacing entries instead of
hardcoded first + 1 and first + 6 offsets. Keep the constants aligned with the
corresponding entries so future reordering requires an explicit update.
| let wants_server_cert = if context.verify_mode == CERT_NONE || context.roots.is_empty() { | ||
| builder.with_no_client_auth() | ||
| } else { | ||
| let verifier = | ||
| rustls::server::WebPkiClientVerifier::builder(Arc::new(context.roots.clone())); | ||
| let verifier = if context.verify_mode == CERT_OPTIONAL { | ||
| verifier.allow_unauthenticated() | ||
| } else { | ||
| verifier | ||
| } | ||
| .build() | ||
| .map_err(|error| (0, format!("[SSL] cannot build client verifier: {error}")))?; | ||
| builder.with_client_cert_verifier(verifier) | ||
| }; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not drop required client authentication when the trust store is empty.
The condition treats context.roots.is_empty() the same as CERT_NONE and selects with_no_client_auth(). A server context with verify_mode == CERT_REQUIRED and no CA loaded then accepts every client without a certificate. OpenSSL fails the handshake in that configuration, so this weakens the guarantee that the caller requested and does so silently.
Return an error when verify_mode is CERT_REQUIRED or CERT_OPTIONAL and no root is configured.
🔒️ Proposed fix
- let wants_server_cert = if context.verify_mode == CERT_NONE || context.roots.is_empty() {
+ if context.verify_mode != CERT_NONE && context.roots.is_empty() {
+ return Err((
+ 0,
+ "[SSL] client authentication requires at least one trusted CA certificate".to_string(),
+ ));
+ }
+ let wants_server_cert = if context.verify_mode == CERT_NONE {
builder.with_no_client_auth()
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let wants_server_cert = if context.verify_mode == CERT_NONE || context.roots.is_empty() { | |
| builder.with_no_client_auth() | |
| } else { | |
| let verifier = | |
| rustls::server::WebPkiClientVerifier::builder(Arc::new(context.roots.clone())); | |
| let verifier = if context.verify_mode == CERT_OPTIONAL { | |
| verifier.allow_unauthenticated() | |
| } else { | |
| verifier | |
| } | |
| .build() | |
| .map_err(|error| (0, format!("[SSL] cannot build client verifier: {error}")))?; | |
| builder.with_client_cert_verifier(verifier) | |
| }; | |
| if context.verify_mode != CERT_NONE && context.roots.is_empty() { | |
| return Err(( | |
| 0, | |
| "[SSL] client authentication requires at least one trusted CA certificate".to_string(), | |
| )); | |
| } | |
| let wants_server_cert = if context.verify_mode == CERT_NONE { | |
| builder.with_no_client_auth() | |
| } else { | |
| let verifier = | |
| rustls::server::WebPkiClientVerifier::builder(Arc::new(context.roots.clone())); | |
| let verifier = if context.verify_mode == CERT_OPTIONAL { | |
| verifier.allow_unauthenticated() | |
| } else { | |
| verifier | |
| } | |
| .build() | |
| .map_err(|error| (0, format!("[SSL] cannot build client verifier: {error}")))?; | |
| builder.with_client_cert_verifier(verifier) | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-native/src/ssl.rs` around lines 1888 - 1901, Update the
client-authentication selection around wants_server_cert so an empty
context.roots does not fall back to with_no_client_auth() when verify_mode is
CERT_REQUIRED or CERT_OPTIONAL. Return an error for either certificate-verifying
mode without configured roots, while preserving no-client-auth behavior for
CERT_NONE and existing verifier construction when roots are available.
| pub fn certificate_verify_message(code: i32) -> &'static str { | ||
| match code { | ||
| 10 => "certificate has expired", | ||
| 9 => "certificate is not yet valid", | ||
| 23 => "certificate revoked", | ||
| 20 => "unable to get local issuer certificate", | ||
| 7 => "certificate signature failure", | ||
| 62 => "hostname mismatch", | ||
| 26 => "unsuitable certificate purpose", | ||
| 5 => "unable to decode certificate", | ||
| 34 => "unhandled critical extension", | ||
| 3 => "unable to get certificate CRL", | ||
| 12 => "CRL has expired", | ||
| 50 => "application verification failure", | ||
| _ => "certificate verify failed", | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Derive one verification-code table instead of two.
certificate_verify_message repeats the code-to-message pairs already defined in certificate_error_details at lines 2039-2073. The two tables must stay in sync. If one changes, SSLCertVerificationError.verify_message and the exception text disagree.
Build both from one source.
♻️ Proposed refactor
fn certificate_error_details(error: &rustls::CertificateError) -> (i32, &'static str) {
use rustls::CertificateError;
- match error {
+ let code = match error {
CertificateError::Expired | CertificateError::ExpiredContext { .. } => {
- (10, "certificate has expired")
+ 10
}
@@
- _ => (1, "certificate verify failed"),
- }
+ _ => 1,
+ };
+ (code, certificate_verify_message(code))
}Keep certificate_verify_message as the single code-to-message table.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-native/src/ssl.rs` around lines 2110 - 2126, Refactor
certificate_error_details to derive its code-to-message entries from the
existing certificate_verify_message table, making certificate_verify_message the
single source of truth. Preserve the current fallback and exception-detail
behavior while removing the duplicated mappings so
SSLCertVerificationError.verify_message and exception text remain consistent.
| Err(error) | ||
| if error.kind() == std::io::ErrorKind::Other | ||
| && error.to_string() == "received plaintext buffer full" => | ||
| { | ||
| return Ok(()); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
rustls 0.23 read_tls "received plaintext buffer full" error detect backpressure typed
💡 Result:
In rustls 0.23, receiving a "received plaintext buffer full" error during a call to read_tls is an intentional mechanism to signal backpressure [1][2]. This error indicates that the application has not consumed enough decrypted plaintext data from the rustls connection, causing the internal plaintext buffer to reach its capacity [1][3]. To detect and handle this backpressure, you should ensure your application adheres to the following flow: 1. Handling the Error: When read_tls returns an io::Error with ErrorKind::Other and the message "received plaintext buffer full", it signifies that the application must drain the decrypted plaintext data [1][2]. 2. Draining Plaintext: Call connection.reader to consume the buffered plaintext data [1][3]. 3. Driving the Connection: After consuming plaintext, you should consistently call process_new_packets [1][3]. This function is responsible for processing any TLS records that were read from the network into the internal deframer buffer, potentially decrypting them into the plaintext buffer, and handling handshake or alert messages [3][2]. By design, rustls encourages a pattern where these methods are called repeatedly to keep buffers clear [1]. Specifically, you should call process_new_packets each time a call to read_tls succeeds, and empty the incoming plaintext buffer through the reader after the call to process_new_packets [1][3]. If your application respects this mechanism, rustls will typically only buffer one full TLS message [3][2]. Note that if process_new_packets returns an error related to the TLS protocol, the connection is considered fatal, and you should stop calling read_tls [2]. Otherwise, as long as the buffers have space, read_tls will continue to accept data from the underlying io::Read implementation [1][2].
Citations:
- 1: https://docs.rs/crate/rustls/latest/source/src/conn.rs
- 2: https://rustls.dev/src/rustls/conn/mod.rs.html
- 3: https://docs.rs/rustls/latest/rustls/struct.ConnectionCommon.html
🏁 Script executed:
set -eu
printf '%s\n' '--- rustls declarations and call site ---'
rg -n -C 8 'received plaintext buffer full|process_received_tls|read_tls|rustls' pyre/pyre-native/src/ssl.rs pyre/pyre-native/Cargo.toml Cargo.toml Cargo.lock 2>/dev/null || true
printf '%s\n' '--- repository files related to rustls ---'
git ls-files | rg '(^|/)(Cargo.toml|Cargo.lock|ssl\.rs)$|rustls'Repository: youknowone/pyre
Length of output: 1916
🌐 Web query:
rustls 0.23 source "received plaintext buffer full" typed error read_tls
💡 Result:
In rustls 0.23, the error "received plaintext buffer full" occurs when the internal buffer holding decrypted (plaintext) data is already full [1][2]. The read_tls method returns this error (as io::ErrorKind::Other) as a form of backpressure to prevent reading more encrypted data until the application has consumed the existing plaintext [3][2]. To resolve or avoid this error, ensure that your application follows the required processing loop after calling read_tls [3][4]: 1. After a successful call to read_tls, you must call process_new_packets() to parse the TLS messages [3][5]. 2. After process_new_packets() has been called, you must drain the decrypted plaintext data by reading from the connection's reader (e.g., using connection.reader()) [3][2]. By draining the plaintext reader, you free up space in the received_plaintext buffer, allowing subsequent calls to read_tls to succeed [3]. The library provides the wants_read() method to help manage this; if wants_read() returns false due to pending plaintext, the application should prioritize reading that plaintext rather than calling read_tls [3][6].
Citations:
- 1: https://rustls.dev/src/rustls/conn/mod.rs.html
- 2: https://docs.rs/crate/rustls/latest/source/src/conn.rs
- 3: https://docs.rs/rustls/latest/rustls/struct.ConnectionCommon.html
- 4: https://docs.rs/crate/rustls/0.20.8
- 5: https://docs.rs/rustls
- 6: https://docs.rs/rustls/latest/rustls/enum.Connection.html
🌐 Web query:
site:github.com/rustls/rustls "received plaintext buffer full" "wants_read" 0.23
💡 Result:
The error message "received plaintext buffer full" is an internal error emitted by the rustls library when it attempts to read and decrypt new TLS records but lacks space in its internal plaintext buffer to store the decrypted result [1]. This typically occurs when the application is not consuming decrypted plaintext data from the rustls connection fast enough [1]. Because rustls acts as a bridge between the encrypted network stream and the application-level plaintext, it holds decrypted data in an internal buffer until the application reads it [1]. If this buffer hits its capacity (which is often hardcoded, e.g., at 16KB in many versions) and the library receives more encrypted data to process, it will return this error to prevent memory exhaustion [1][2]. To resolve or prevent this error: 1. Ensure the application is actively reading plaintext from the connection using the reader (e.g., via the reader() method or appropriate Read implementation) after calling process_new_packets() [1]. 2. Avoid calling process_new_packets() if the application is not prepared to immediately drain the resulting plaintext from the connection [1]. 3. For advanced use cases where default buffer sizes are insufficient, some versions of the library allow configuring the plaintext buffer limit (e.g., via Connection::set_plaintext_buffer_limit) to accommodate larger chunks of data without immediate overflow [2]. The phrase "wants_read" mentioned in your query is a related part of the rustls API; it is a boolean method (wants_read()) that indicates whether the connection needs more data from the underlying network transport to make progress [3][4]. It is frequently used in event loops to determine if the socket should be polled for incoming TLS data [4]. If "received plaintext buffer full" occurs, the application logic responsible for reading plaintext must be prioritized to clear the buffer before further network reads are performed [1].
Citations:
- 1: https://github.com/rustls/rustls/blob/v/0.22.0/rustls/src/conn.rs
- 2: Allow changing the limit on plaintext buffer size rustls/rustls#2382
- 3: Using rustls synchronously rustls/rustls#12
- 4: https://github.com/rustls/rustls/blob/main/rustls/src/lib.rs
🌐 Web query:
github youknowone/pyre pyre-native/src/ssl.rs process_received_tls
💡 Result:
The repository youknowone/pyre is a project aimed at creating a Python implementation in Rust with a meta-tracing JIT compiler, designed with architecture parity to PyPy [1][2]. Based on the available information, the specific file path pyre-native/src/ssl.rs and the function process_received_tls do not appear to exist in the current public structure of the youknowone/pyre repository [1][2]. The project repository contains directories such as pyre-interpreter, pyre-jit, and pyrex, but does not contain a pyre-native subdirectory [1]. It is possible that the query refers to code from a different project, such as RustPython [3][4], which also uses Rust to implement Python and contains modules for SSL/TLS support (e.g., crates/stdlib/src/ssl.rs and crates/stdlib/src/ssl/compat.rs) [3][4]. In those contexts, functions like process_received_packets are commonly used to handle TLS state machines [4]. If you are looking for specific TLS processing logic in an open-source Python-in-Rust implementation, you may wish to verify the repository name or check if the code belongs to a different project like RustPython [3][4].
Citations:
- 1: https://github.com/youknowone/pyre
- 2: https://pyre-lang.org/
- 3: https://github.com/RustPython/RustPython/blob/885cf5c2/crates/stdlib/src/ssl.rs
- 4: https://github.com/RustPython/RustPython/blob/885cf5c2/crates/stdlib/src/ssl/compat.rs
🏁 Script executed:
set -eu
python3 - <<'PY'
from urllib.request import urlopen
from re import search
urls = [
"https://docs.rs/crate/rustls/0.23.31/source/src/conn/mod.rs",
"https://docs.rs/crate/rustls/0.23.31/source/src/conn.rs",
]
for url in urls:
try:
text = urlopen(url, timeout=15).read().decode()
except Exception as exc:
print(f"{url}: {exc}")
continue
print(f"--- {url} ---")
for needle in ("received plaintext buffer full", "pub fn wants_read", "fn wants_read"):
pos = text.find(needle)
print(f"{needle!r}: {'found' if pos >= 0 else 'absent'}")
if pos >= 0:
print(text[max(0, pos - 500):pos + 700])
PYRepository: youknowone/pyre
Length of output: 1916
Use wants_read() instead of matching the rustls error text. Rustls exposes plaintext backpressure through wants_read(). Gate read_tls with this method and add a test for a full plaintext buffer; error strings are not stable API.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-native/src/ssl.rs` around lines 2211 - 2216, Replace the `read_tls`
error-text match in the surrounding TLS read flow with a `wants_read()` check on
the rustls connection, using it to detect plaintext backpressure and return
successfully without relying on `ErrorKind::Other` or the unstable `"received
plaintext buffer full"` text. Add a test covering a full plaintext buffer and
verifying the read path handles it successfully.
| if session.context_identity != context as *const Context as usize { | ||
| return Err((0, "Session refers to a different SSLContext.".to_string())); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not use a Context address as a stable context identity.
context_identity is the *const Context value cast to usize, captured at lines 2328 and 2363. The allocator reuses an address after context_free. If a program drops one SSLContext and creates another, the new Context can land at the same address, and this check then accepts a session that belongs to the freed context.
The consequence is not a dangling read, because NativeSession holds an Arc<ClientConfig>. The consequence is a wrong policy decision: line 2334 clones the old context's ClientConfig, so the connection runs under the freed context's verification settings while Python reports the new SSLContext. CPython raises ValueError for this case instead.
Assign each Context a monotonic id from a process-wide AtomicU64 and compare that id.
🔒️ Proposed fix
+static NEXT_CONTEXT_ID: AtomicUsize = AtomicUsize::new(1);
+
pub struct Context {
protocol: i32,
+ identity: usize,Set identity: NEXT_CONTEXT_ID.fetch_add(1, Ordering::Relaxed) in Context::new, then replace both context as *const Context as usize expressions with context.identity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-native/src/ssl.rs` around lines 2328 - 2330, Replace address-based
context identity checks in the relevant session creation and reuse paths with a
process-wide monotonic ID: add an AtomicU64 counter, assign its fetch_add result
to Context::identity in Context::new, and compare context.identity instead of
casting Context pointers to usize. Update both context_identity capture sites
while preserving the existing mismatch error behavior.
| pub unsafe fn connection_tls_unique(connection: *const TlsConnection) -> Option<Vec<u8>> { | ||
| let inner = unsafe { (&*connection).inner.as_ref() }?; | ||
| if inner.is_handshaking() || inner.protocol_version()? != rustls::ProtocolVersion::TLSv1_2 { | ||
| return None; | ||
| } | ||
| let mut output = vec![0u8; 12]; | ||
| inner | ||
| .export_keying_material(&mut output, b"EXPORTER-pyre-tls-unique", None) | ||
| .ok()?; | ||
| Some(output) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🌐 Web query:
rustls 0.23 expose TLS 1.2 Finished verify_data tls-unique channel binding RFC 5929
💡 Result:
Rustls does not expose or support the tls-unique channel binding defined in RFC 5929, nor does it provide an API to extract the Finished message verify_data required for its calculation [1][2]. The Rustls project has explicitly stated that it does not intend to support tls-unique [1][2]. This decision is based on several factors: 1. Security Concerns: The tls-unique channel binding is susceptible to the "triple handshake" vulnerability unless specific mitigations (like requiring the Extended Master Secret extension) are strictly enforced, which the maintainers have indicated may limit its utility [1]. 2. Architectural Philosophy: The project maintains a focus on secure defaults and does not aim to implement all IETF-standardized extensions, particularly those viewed as obsolete or burdensome to maintain [3][2]. 3. Alternatives: For TLS 1.3, tls-exporter (RFC 9266) is the recommended standard [1][2]. Rustls already supports RFC 5705 exporters, which allows users to implement tls-exporter channel bindings using existing APIs [1][2]. Because Rustls does not provide access to the handshake's Finished messages, it is not possible to implement tls-unique using the crate's public interface [1][4]. [1][3][2]
Citations:
- 1: Allow extracting information for
tls-uniqueChannel Binding rustls/rustls#995 - 2: RFC 9266: Channel Bindings for TLS 1.3 support rustls/rustls#1089
- 3: https://docs.rs/rustls/latest/rustls/manual/_04_features/index.html
- 4: peer handshake violates TLS transparency XRPLF/rippled#2413
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- rustls dependency ---'
rg -n -A3 -B3 'rustls' --glob 'Cargo.toml' --glob 'Cargo.lock' .
printf '%s\n' '--- function and related bindings ---'
rg -n -A35 -B20 'connection_tls_unique|tls_unique|tls-exporter|channel.binding|EXPORTER-pyre-tls-unique' pyre/pyre-native/src pyre --glob '*.rs' --glob '*.py'
printf '%s\n' '--- source outline ---'
ast-grep outline pyre/pyre-native/src/ssl.rs --match connection_tls_unique --view expandedRepository: youknowone/pyre
Length of output: 1916
🌐 Web query:
site:docs.rs/rustls/0.23 ConnectionCommon export_keying_material peer_finished verify_data
💡 Result:
The requested terms—ConnectionCommon, export_keying_material, peer_finished, and verify_data—do not appear together in the provided documentation. While the documentation describes a CryptoProvider for the rustls library, which handles cryptographic primitives for TLS, it does not explicitly define or link these specific terms in the provided search results [1]. In general TLS terminology, export_keying_material typically refers to a mechanism defined in RFC 5705 to derive additional keys from an existing TLS master secret. The terms peer_finished and verify_data are standard elements of the TLS handshake, where the Finished message contains a verify_data field used to authenticate the entire handshake transcript. There is no evidence in the provided search results linking these to a specific ConnectionCommon object.
Citations:
🌐 Web query:
CPython ssl SSLSocket get_channel_binding tls-unique unavailable error TLS 1.3
💡 Result:
The "tls-unique" channel binding is unavailable in TLS 1.3 because it was not defined for that protocol version [1][2]. Specifically, "tls-unique" (as defined in RFC 5929) was intentionally left undefined for TLS 1.3 due to security concerns and structural changes in the TLS 1.3 handshake [1][2]. If you are encountering errors when attempting to use get_channel_binding('tls-unique') while your connection is using TLS 1.3, it is because this method is incompatible with the protocol's design [3][4]. To support channel binding in TLS 1.3, you should use the "tls-exporter" channel binding, which is defined in RFC 9266 [1][2]. This standard replaces "tls-unique" for TLS 1.3 and is based on exported keying material (EKM) [1][2]. As of the latest CPython developments, there has been ongoing work to implement RFC 9266 and the "tls-exporter" binding to address this limitation in the ssl module [3][5][6]. If your application requires channel binding with TLS 1.3, you should ensure you are using a version of Python that supports "tls-exporter" or monitor the relevant CPython issues for the integration status of these features [5][6].
Citations:
- 1: https://datatracker.ietf.org/doc/html/rfc9266
- 2: https://www.rfc-editor.org/rfc/rfc9266.html
- 3: ssl module incorrectly supports tls-unique channel binding for TLS 1.3 python/cpython#95341
- 4: FR: Add API to get tls-unique channel binding data google/conscrypt#542
- 5: gh-95341: Implement tls-exporter channel bindings and export key materials python/cpython#95366
- 6: RFC 9266: Channel Bindings for TLS 1.3 support python/cpython#115193
🌐 Web query:
rustls rustls 0.23 issue 995 tls-unique Finished verify_data
💡 Result:
Rustls does not implement the tls-unique channel binding, and the project has explicitly declined requests to add it [1][2]. The issue 995 you referenced was a request to allow the extraction of the Finished message necessary for implementing tls-unique (defined in RFC 5929) [1]. The maintainers of rustls have maintained a consistent stance that implementing this API is not a goal for the library [1]. Key points regarding this situation: 1. Lack of Support: Rustls does not provide an API to extract the Finished message (the verify_data) used for tls-unique channel binding [1]. 2. Rationale: The maintainers have expressed that they do not wish to burden the project with implementing APIs for deprecated or problematic channel binding mechanisms like tls-unique [1][2]. 3. TLS 1.3: tls-unique is not defined for TLS 1.3 [1]. For TLS 1.3, the recommended approach is to use tls-exporter (defined in RFC 9266), which is already achievable using existing rustls APIs [1][2]. 4. Security Context: RFC 5929 channel bindings can be insecure if not properly restricted to connections using the Extended Master Secret extension (RFC 7627) [1]. If you have a requirement for channel binding, the current recommendation for modern TLS (including TLS 1.3) is to utilize the exporter mechanism (RFC 9266), which is supported by rustls via its existing exporter APIs [1][2][3].
Citations:
- 1: Allow extracting information for
tls-uniqueChannel Binding rustls/rustls#995 - 2: RFC 9266: Channel Bindings for TLS 1.3 support rustls/rustls#1089
- 3: https://docs.rs/rustls/0.23.37/rustls/manual/_04_features/index.html
🌐 Web query:
site:github.com/rust-lang/rustls/issues/995 tls-unique rustls
💡 Result:
Rustls does not provide native support for the tls-unique channel binding mechanism defined in RFC 5929 [1][2]. The project maintainers have indicated they do not intend to implement this feature [1]. The primary reasons and context regarding channel binding support in rustls are as follows: 1. Protocol Support: tls-unique is not defined for TLS 1.3 [1]. For TLS 1.3, the relevant standard is RFC 9266 (tls-exporter) [1][2]. 2. Availability: Rustls provides an API for exporting keying material, which allows users to implement the tls-exporter mechanism (RFC 9266) themselves if needed [2]. 3. Security Considerations: Implementing tls-unique for TLS 1.2 would require restricting connections to those that successfully negotiate the Extended Master Secret (EMS) extension (RFC 7627) to avoid the triple-handshake vulnerability [1]. Rustls recently added functionality to check whether EMS was negotiated, which helps users safely implement channel binding logic built on top of exported keying material [3]. In summary, you cannot extract tls-unique directly from rustls, and there are no plans to add this specific capability to the library [1].
Citations:
- 1: Allow extracting information for
tls-uniqueChannel Binding rustls/rustls#995 - 2: RFC 9266: Channel Bindings for TLS 1.3 support rustls/rustls#1089
- 3: rustls/rustls@95410c3
🌐 Web query:
site:github.com/python/cpython Lib/ssl.py get_channel_binding tls-unique ValueError
💡 Result:
In Python's ssl module, the get_channel_binding(cb_type="tls-unique") method raises a ValueError when the requested channel binding type is not supported by the underlying implementation [1][2]. The ssl.SSLSocket.get_channel_binding method is designed to be extensible [3][2]. When called, it checks the requested cb_type against the list of supported types defined in the ssl.CHANNEL_BINDING_TYPES variable [1][2]. If the provided type is not in this list, the method raises a ValueError stating that the channel binding type is not implemented [1][4]. Common causes for this error include: 1. Unsupported Type: You are requesting a channel binding type (e.g., tls-server-end-point or tls-exporter) that is not currently implemented in the CPython ssl module [5][6]. As of now, only 'tls-unique' is officially supported [2]. 2. Environment Limitations: Even if 'tls-unique' is requested, the internal implementation may fail if the underlying OpenSSL library or the specific TLS connection state (e.g., pre-handshake) does not support it [1][2]. 3. TLS 1.3 Compatibility: There are ongoing discussions and issues regarding the use of 'tls-unique' with TLS 1.3 [5][6]. Since 'tls-unique' was left undefined in the TLS 1.3 specification due to security concerns, using it in a TLS 1.3 context may lead to issues or unexpected behavior, though the ValueError is primarily triggered by the library's internal check against the supported list [1][5]. To avoid this error, ensure that you only pass types present in ssl.CHANNEL_BINDING_TYPES and verify that the connection has completed its handshake, as get_channel_binding returns None if called before the handshake is finished [1][2].
Citations:
- 1: https://github.com/python/cpython/blob/main/Lib/ssl.py
- 2: https://github.com/python/cpython/blob/36e4ffc1/Doc/library/ssl.rst
- 3: Provide data for TLS channel binding python/cpython#56760
- 4: https://github.com/python/cpython/blob/3.13/Lib/ssl.py
- 5: ssl module incorrectly supports tls-unique channel binding for TLS 1.3 python/cpython#95341
- 6: RFC 9266: Channel Bindings for TLS 1.3 support python/cpython#115193
Do not return a private exporter for tls-unique. rustls 0.23 does not expose TLS 1.2 Finished verify_data, so this value cannot implement RFC 5929 or interoperate with OpenSSL, Java, or Go peers. Return the unsupported-binding error used by the CPython-compatible API. Expose a standards-based RFC 9266 exporter separately if required.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-native/src/ssl.rs` around lines 2741 - 2751, The
connection_tls_unique function must not derive or return an exporter as
tls-unique. Replace its successful TLS 1.2 exporter path with the
CPython-compatible unsupported-binding error behavior, and keep any RFC 9266
exporter functionality separate under its own API if required.
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
pyre-native/src/ssl.rs: - server_config builds its client verifier from capath certificates as well as eagerly loaded roots, applies the context's CRLs, and errors instead of dropping client authentication when no root is configured - VERIFY_CRL_CHECK_LEAF selects end-entity-only revocation checking; full-chain checking stays with VERIFY_CRL_CHECK_CHAIN - name the SSL_OP_* and X509_V_FLAG_* bits that DEFAULT_OPTIONS, enabled_versions and the verifiers decode - map cipher suites to OpenSSL names through an explicit table rather than rustls Debug output - give Context a monotonic identity so a reused address cannot match a session to a freed context - cache capath certificates against the directory list and their mtimes - detect an encrypted private key from the parsed container and report that a password is required, replacing the caller's header-text scan - gate read_tls on wants_read() instead of matching rustls' error text - derive certificate_error_details from certificate_verify_message - add default_verify_paths, context_set_num_tickets, context_cipher_enabled and context_identity - drop connection_tls_unique: rustls does not expose the TLS 1.2 Finished verify_data that RFC 5929 defines the binding as pyre-interpreter/src/module/_ssl/mod.rs: - set_default_verify_paths reads SSL_CERT_DIR, and SSL_CERT_FILE no longer suppresses the directory source - num_tickets reaches the backend; get_ciphers reports the context's filter - _set_alpn_protocols releases the buffer before propagating a parse error - get_channel_binding raises for tls-unique once a handshake has completed - get_default_verify_paths reports the paths this platform carries - extract split_library_reason; tls_error no longer builds an exception it discards - name the shadow-stack slot offsets - record why load_dh_params validates without configuring pyre-interpreter/src/module/_socket/interp_socket.rs: - socket_wait_readable computes one deadline so EINTR cannot extend it - settimeout stores a float, so an integer timeout is honored - sendto, recvfrom, recv_into, recvfrom_into, recvmsg, recvmsg_into and sendmsg use the timeout-aware error conversion Assisted-by: Claude
Summary
_sslmodule on rustls 0.23 with the AWS-LC crypto provider, without OpenSSLssl.pyCompatibility
The existing
lib-python/3/ssl.pyis used unchanged. The remaining 42 skips are capability or platform exclusions, including APIs rustls does not expose (such as post-handshake authentication, PSK, and legacy TLS protocols).Validation
MAJIT_STATS=1 target/release/pyre-dynasm -m test -v test_ssl: 196 run, 0 failures, 42 skips; 9 loops compiled, 0 aborts, 0 internal compile panicscargo check --features dynasmcargo test --features dynasm: 5 unit tests and 3 gate-triage tests passedcargo fmt --all -- --checkgit diff --checkpython3 pyre/check.py target/release/pyre-dynasm --backend dynasm --no-synthetic --no-cpython-suite: dynasm 17/17Summary by CodeRabbit