Skip to content

Implement rustls-backed _ssl module - #1167

Merged
youknowone merged 3 commits into
mainfrom
agent/rustls-ssl-module
Aug 12, 2026
Merged

Implement rustls-backed _ssl module#1167
youknowone merged 3 commits into
mainfrom
agent/rustls-ssl-module

Conversation

@youknowone

@youknowone youknowone commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • implement pyre's low-level _ssl module on rustls 0.23 with the AWS-LC crypto provider, without OpenSSL
  • add SSL contexts, socket/BIO transports, certificate and trust-store handling, SNI, ALPN, sessions, channel binding, message callbacks, CRLs, cipher and curve configuration
  • integrate the new objects with GC/type registration and fix socket/TLS buffering, timeout, and native callback boundaries needed by ssl.py

Compatibility

The existing lib-python/3/ssl.py is 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 panics
  • cargo check --features dynasm
  • cargo test --features dynasm: 5 unit tests and 3 gate-triage tests passed
  • cargo fmt --all -- --check
  • git diff --check
  • python3 pyre/check.py target/release/pyre-dynasm --backend dynasm --no-synthetic --no-cpython-suite: dynasm 17/17
  • refreshed Charon LLBC artifacts and rebuilt the rtyper prepass against the current interpreter sources

Summary by CodeRabbit

  • New Features
    • Added native TLS/SSL support through a Rustls-backed implementation.
    • Added certificate loading, verification, protocol and cipher configuration, ALPN, sessions, memory BIOs, and secure socket communication.
    • Added compatible SSL context, socket, certificate, and session functionality.
  • Bug Fixes
    • Improved socket timeout handling, connection acceptance, resolver safety, and cleanup of unclosed sockets.
    • Added support for empty-host IPv4 and IPv6 binding.
  • Compatibility
    • SSL support is available on supported native builds and excluded from WebAssembly and sandboxed environments.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a native Rustls-backed _ssl module with certificate, session, BIO, context, and socket support. It integrates the new types with interpreter GC and subclass ranges. It also updates socket resolver safety, timeout handling, wildcard binds, and descriptor cleanup.

Changes

Native TLS implementation

Layer / File(s) Summary
Native TLS state and configuration
Cargo.toml, pyre/pyre-native/...
Adds Rustls dependencies and native TLS support for BIOs, contexts, certificates, trust stores, ciphers, verification policies, and sessions.
Native TLS connection flow
pyre/pyre-native/src/ssl.rs
Adds connection construction, handshakes, TLS record processing, encrypted and plaintext I/O, shutdown, session export, channel binding, and negotiated metadata.
Interpreter _ssl wrappers
pyre/pyre-interpreter/src/module/_ssl/*, pyre/pyre-interpreter/src/importing.rs, pyre/pyre-interpreter/src/module/mod.rs
Exposes native TLS operations through Python-compatible _ssl objects, callbacks, exceptions, constants, and module registration.
Runtime type and hierarchy integration
pyre/pyre-object/src/pyobject.rs, pyre/pyre-interpreter/src/lib.rs, pyre/pyre-interpreter/src/typedef.rs, pyre/pyre-interpreter/src/objspace/std/mapdict.rs, pyre/pyre-jit/*, pyre/pyrex/Cargo.toml
Registers native _ssl types with mapdict and GC support. Subclass-range computation now uses the active hierarchy, including sandbox handling.
Socket resolver and timeout behavior
pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
Adds resolver locking, unaligned hostent access, owned resolver data, timeout-aware I/O and accept polling, wildcard binds, and socket finalization.

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
Loading

Poem

A rabbit watched the TLS streams flow,
Through BIO burrows down below.
Certificates rested, neat and bright,
Sockets closed before the night.
“Rustls hops!” the rabbit cried,
As secure connections sprang alive.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's primary change: implementing a rustls-backed _ssl module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/rustls-ssl-module

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@youknowone
youknowone force-pushed the agent/rustls-ssl-module branch from 47c948e to be37310 Compare August 12, 2026 02:19
@youknowone
youknowone force-pushed the agent/rustls-ssl-module branch from 5cbcf42 to 4462a84 Compare August 12, 2026 10:51
@youknowone
youknowone marked this pull request as ready for review August 12, 2026 12:51
@github-actions

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 4462a84).
Updated: 2026-08-12T12:55:47.975Z

Files in the reviewed diff
Cargo.lock
Cargo.toml
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/lib.rs
pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
pyre/pyre-interpreter/src/module/_ssl/mod.rs
pyre/pyre-interpreter/src/module/mod.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-jit/Cargo.toml
pyre/pyre-jit/src/eval.rs
pyre/pyre-native/Cargo.toml
pyre/pyre-native/src/lib.rs
pyre/pyre-native/src/ssl.rs
pyre/pyre-object/src/pyobject.rs
pyre/pyrex/Cargo.toml

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:949 ↔ lib-python/3/test/test_ssl.py:1011SSLContext.get_ciphers() always enumerates the static provider list, rather than the active list selected by set_ciphers(). After set_ciphers("AESGCM"), unrelated enabled suites remain reported.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:699 ↔ lib-python/3/test/test_ssl.py:1814num_tickets is stored only on W_SSLContext; pyre/pyre-native/src/ssl.rs:1878 builds ServerConfig without reading it. Setting num_tickets = 0 or 1 therefore does not alter emitted TLS 1.3 session tickets.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:689 ↔ lib-python/3/test/test_ssl.py:4825post_handshake_auth is only a wrapper field. Neither client nor server configuration consumes it, so enabling it cannot negotiate or perform post-handshake client authentication.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:28 ↔ lib-python/3/ssl.py:777keylog_filename is retained as an unused Python reference: there is no descriptor implementation and no rustls key-log sink. ssl.create_default_context() can assign SSLKEYLOGFILE, but no key material is written.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:994 ↔ lib-python/3/test/test_ssl.py:4308load_dh_params() merely checks for a PEM header and discards the parsed parameters. It reports success although subsequent server handshakes cannot use the requested finite-field DH parameters.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:671 ↔ lib-python/3/ssl.py:757_host_flags is stored but never reaches the certificate verifier. Changing hostname-verification flags therefore has no effect on a connection.

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 hostent storage match rpython/rlib/rsocket.py:1620 and rpython/rlib/rsocket.py:1589.

4. Structural adaptations

  • pyre/pyre-native/src/ssl.rs:176 ↔ lib-python/3/ssl.py:430 — the new _ssl layer uses rustls rather than PyPy’s unavailable local _ssl implementation/OpenSSL backend. rustls supports TLS 1.2/1.3 only; legacy protocol constants remain exposed but Context::new() rejects TLSv1/TLSv1.1.

  • pyre/pyre-native/src/ssl.rs:2732 ↔ lib-python/3/test/test_ssl.py:4194tls-unique is emulated with a private exporter label because rustls does not expose TLS Finished verify-data. It preserves peer equality and length, but is not RFC 5929 tls-unique.

  • pyre/pyre-native/src/ssl.rs:2711 ↔ lib-python/3/ssl.py:835SSLSession snapshots are available only for TLS 1.2 because rustls does not expose a transferable TLS 1.3 ticket/session value. A completed TLS 1.3 client connection returns None for .session.

  • pyre/pyre-interpreter/src/module/_ssl/mod.rs:1220 ↔ lib-python/3/ssl.py:475 — NPN is deliberately unsupported by rustls; _set_npn_protocols() raises instead of configuring the deprecated OpenSSL extension.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

https://github.com/youknowone/pyre/blob/4462a84f3dc6ee9ac7ec9a026a1620025a99693a/pyre-native/src/ssl.rs#L1888-L1889
P1 Badge 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
P1 Badge 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
P2 Badge 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
P2 Badge 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
P2 Badge 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
P2 Badge 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".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 that socket_get_attr_i64(obj, "_fd") returns a negative value when _fd is absent. Otherwise finalization can close file descriptor 0 or fail during cleanup.


1019-1030: 🎯 Functional Correctness

Check 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 & Availability

Verify the blocking state before accept.

Both paths call socket_wait_readable(obj, fd)? before libc::accept(...). Confirm that a positive timeout cannot leave fd blocking 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.11 and der 0.8 must agree on the same der major version, because ssl.rs passes a der::SecretDocument byte slice into pkcs8::EncryptedPrivateKeyInfoRef. A mismatched pair fails to compile or silently pulls two der copies. Also confirm that x509-parser 0.18 and pem-rfc7468 1 exist 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 _ssl constants the interpreter exports.

This match accepts 2, 16, 17, 5, and 6. CPython defines PROTOCOL_TLSv1 == 3 and PROTOCOL_TLSv1_1 == 4, and both are rejected here, so ssl.SSLContext(ssl.PROTOCOL_TLSv1) raises "invalid or unsupported protocol version". CPython also defines no protocol constant with value 6, yet 6 maps to a TLS 1.3-only context.

Confirm that 5 and 6 match the values the interpreter _ssl module publishes, and confirm that rejecting 3 and 4 is 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_events is always drained.

Two accumulation paths have no bound.

message_events grows on every observed record and is emptied only by connection_take_message_events. If the interpreter calls that function only when a Python _msg_callback is installed, then every connection without a callback retains one TlsMessageEvent per record, each holding a Vec<u8> copy of the payload, for the whole connection lifetime.

self.handshakes accepts a 24-bit message_len, so a peer can declare a 16 MiB handshake message and drip bytes. observe runs 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_context is an unowned raw pointer that is dereferenced after the handshake.

connection_accept_server stores the caller's *const Context at line 2469, and note_server_resumption dereferences it at line 2237 on every process_received_tls call. TlsConnection holds no reference count on the Context. Every other native value in this file is owned through Box::into_raw and freed explicitly, so nothing in the native layer keeps this Context alive.

If the Python SSLContext is collected while the SSLSocket still exists, this dereference reads freed memory. connection_new at line 2298 also dereferences a borrowed Context, but that deref completes before the function returns; this one outlives the call.

Confirm that the interpreter W_SSLSocket holds a strong reference to its W_SSLContext for the socket's whole lifetime, including after connection_accept_server. If it does not, store the resumption counter behind an Arc shared with the Context instead 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 _ssl aliases at lines 1130-1139 are compiled out when the target is wasm32 or the sandbox feature is on. The truncation here applies only when the target is not wasm32 and sandbox is on. A wasm32 build therefore drops the five aliases but keeps the full hierarchy, including the five trailing _ssl slots.

The doc comment states that pyre-object must not learn the sandbox feature, so SUBCLASS_RANGE_HIERARCHY always contains those slots. On wasm32 the 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 _ssl slots now do.

SSL_HIERARCHY_SLOTS is also a hardcoded 5 that 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 wasm32 requirement before you apply this. If wasm32 also 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_version maps -1 to 0x304 but forwards -2 unchanged. set_maximum_version maps -2 to 0x303 but forwards -1 unchanged. Each setter normalizes one sentinel and passes the other raw into pyre_native::ssl.

Confirm that context_set_minimum_version and context_set_maximum_version interpret the raw sentinel correctly, and that a later minimum_version / maximum_version read returns the sentinel CPython returns.


1258-1316: LGTM!


1778-1810: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

⚠️ Unverified finding
Sandbox verification was unavailable.

WritableBuffer is 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. writable goes out of scope at the end of read without one. Every other buffer acquisition in this module releases explicitly, including W_MemoryBIO::write at Line 1299 and W_SSLSocket::write at Line 1751. ssl.SSLSocket.recv_into drives this path on every read, so a missing release leaks one export per call and permanently locks the target bytearray or memoryview against resize.

Second, the acquisition is live across the loop at Lines 1797-1806. receive_transport calls the Python socket.recv method, which can run arbitrary code and trigger a moving collection. Line 1808 then calls buffer.as_mut_slice() and writes through it. If WritableBuffer caches 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::WritableBuffer before 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 _ssl subclass-range census and the _ssl GC registrations use different cfg gates. SUBCLASS_RANGE_HIERARCHY declares ids 170-174 under not(target_arch = "wasm32") alone, while the GC registrations that must match those ids are additionally gated on not(feature = "sandbox"). On a native --features sandbox build the two sets disagree unless active_subclass_range_hierarchy() and all_subclass_range_aliases() both truncate the _ssl tail. The assertion at pyre/pyre-jit/src/eval.rs Lines 3971-3975 and the alias assertion inside compute_subclass_ranges_from_hierarchy both 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 under sandbox, as the comment claims; if it does not, gate the entries on not(feature = "sandbox") as well.
  • pyre/pyre-jit/src/eval.rs#L3623-L3719: confirm the sandbox gate on this block leaves actual_hierarchy ending at id 169 and that Line 3973 receives a matching truncated slice.
  • pyre/pyre-interpreter/src/typedef.rs#L277-L280: confirm crate::all_subclass_range_aliases() omits the _ssl aliases under sandbox, so the witnesses[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

📥 Commits

Reviewing files that changed from the base of the PR and between 56924a3 and 4462a84.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • Cargo.toml
  • pyre/pyre-interpreter/src/importing.rs
  • pyre/pyre-interpreter/src/lib.rs
  • pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
  • pyre/pyre-interpreter/src/module/_ssl/mod.rs
  • pyre/pyre-interpreter/src/module/mod.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit/Cargo.toml
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-native/Cargo.toml
  • pyre/pyre-native/src/lib.rs
  • pyre/pyre-native/src/ssl.rs
  • pyre/pyre-object/src/pyobject.rs
  • pyre/pyrex/Cargo.toml

Comment on lines +2025 to +2037
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");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +2061 to +2081
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()?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment on lines +3103 to +3106
return Err(socket_io_err_for_operation(
obj,
std::io::Error::from_raw_os_error(errno),
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +146 to +198
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Comment on lines +426 to +448
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);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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.

Comment on lines +1888 to +1901
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)
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Suggested change
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.

Comment on lines +2110 to +2126
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",
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment on lines +2211 to +2216
Err(error)
if error.kind() == std::io::ErrorKind::Other
&& error.to_string() == "received plaintext buffer full" =>
{
return Ok(());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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:


🏁 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:


🌐 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:


🌐 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:


🏁 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])
PY

Repository: 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.

Comment on lines +2328 to +2330
if session.context_identity != context as *const Context as usize {
return Err((0, "Session refers to a different SSLContext.".to_string()));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Comment on lines +2741 to +2751
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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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:


🏁 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 expanded

Repository: 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:


🌐 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:


🌐 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:


🌐 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:


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.

@youknowone
youknowone merged commit 7122afd into main Aug 12, 2026
18 checks passed
@youknowone
youknowone deleted the agent/rustls-ssl-module branch August 12, 2026 13:31
youknowone added a commit that referenced this pull request Aug 13, 2026
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
youknowone added a commit that referenced this pull request Aug 13, 2026
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
youknowone added a commit that referenced this pull request Aug 13, 2026
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
youknowone added a commit that referenced this pull request Aug 13, 2026
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
youknowone added a commit that referenced this pull request Aug 14, 2026
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
youknowone added a commit that referenced this pull request Aug 14, 2026
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
youknowone added a commit that referenced this pull request Aug 14, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant