bryancall commented on PR #13548:
URL: https://github.com/apache/trafficserver/pull/13548#issuecomment-5638768164

   Reviewed at `8e08afa4d8`. I verified each of the five blocking items against 
the code rather than taking the summary's word for it, and they are all 
genuinely fixed:
   
   - `SSL_get_verify_mode(ssl)` at `SSLUtils.cc:535`, with no 
`SSL_CTX_get_verify_mode` left anywhere, and the `setClientCertLevel()` branch 
removed rather than patched.
   - The inbound callback no longer assumes an `SSLNetVConnection`, and the 
server name comes from `TLSSNISupport`.
   - `X509_STORE_CTX_set_default()`, `X509_VERIFY_PARAM_set1()` and 
`X509_STORE_CTX_set_ex_data()` in both directions, `&&`-chained so the returns 
are checked. Dropping the explicit `X509_STORE_CTX_set_depth()` is correct: 
BoringSSL's `SSL_set_verify_depth()` writes into `config->param` and 
`SSL_get0_param()` returns that same param, so depth still travels.
   - Origin session reuse is scoped to the name the connection authenticated 
for.
   - The smaller items all check out, and the fail-closed change on a 
configured-but-missing CA store is better than what I asked for.
   
   Two of these are fixed more carefully than the report I gave you, so thank 
you for that.
   
   I am still requesting changes, because this round introduced a new defect in 
the code that fixes one of them.
   
   ## Blocking: use-after-free in the new verify-store stash
   
   `SSLUtils.cc:1386-1394` writes the stash, `:318-331` reads it, 
`SNIActionPerformer.cc:312` and `SSLUtils.cc:518` are the two call sites.
   
   `9b9b95fdd1` replaced the per-handshake rebuild with a non-owning pointer in 
`ssl_verify_store_index`. The stash is written in one place, the success path 
at `:1389`, and cleared nowhere. `SSL_set0_verify_cert_store()` takes 
ownership, and `SSL_set_SSL_CTX()` replaces `config->cert` wholesale, whose 
destructor frees `verify_store`. The codebase already knows this: the comment 
at `:517` ("After replacing the SSL_CTX, make sure the overridden ca_cert_file 
is still set") is why `setClientCertCACerts()` is called a second time there.
   
   So:
   
   1. A `verify_client` action on an entry that also sets 
`ssl_client_rpk_ca_name` builds store A, hands it to 
`SSL_set0_verify_cert_store()`, and stashes A.
   2. `selectCertificate()` calls `SSL_set_SSL_CTX()`, which frees A. The stash 
still holds A.
   3. `:518` rebuilds. If `X509_STORE_new()` returns null or 
`X509_STORE_load_locations()` fails, which is the CA file rotated or its 
permissions changed since config load, the `else` branch at `:1393` frees the 
new store and leaves the stash pointing at the freed A.
   4. A client presents an X.509 certificate, `:318` reads a non-null dangling 
A and passes it to `X509_STORE_CTX_init()`.
   
   The unfortunate part is that step 3 is exactly the condition the new 
`ca_configured` guard at `:320-327` was added to fail closed on. A stale 
non-null stash skips that guard, so a clean `SSL_AD_INTERNAL_ERROR` becomes a 
use-after-free on attacker-reachable input instead. The `SSL_set_ex_data()` 
failure path at `:1389` has the same shape: it logs and leaves the previous 
pointer in place.
   
   Clearing the slot on both failure paths covers it, and clearing it right 
after `selectCertificate()` returns would make the invalidation point explicit. 
Using `SSL_set1_verify_cert_store()` with an owning free function on the 
ex_data index would remove the category.
   
   ## Also worth addressing
   
   **`SSLUtils.cc:238` still has the unguarded dereference.** The note says 
both unguarded uses were inbound and are fixed, but only 
`ssl_custom_verify_client_callback` was. The RPK branch this PR adds to 
`ssl_verify_client_callback` still calls `netvc->options.sni_servername.get()` 
on a pin mismatch at `:238`, and again at `:241`, under the same 
"TLSBasicSupport is bound but `SSLNetVCAttach` was never called" condition. It 
needs OpenSSL 3.5 plus QUIC to reach, but it is the same one-line fix as its 
twin.
   
   **RPK origin sessions stop pooling when the outbound SNI is empty.** 
`SSLClientUtils.cc:427` compares `netvc->options.sni_servername` against the 
`hostname` argument, but those come from different sources. 
`get_outbound_sni_for_cert_verification()` falls back to the Host header and 
then to `current.server->name` (`HttpSM.cc:5510-5518`), while `sni_servername` 
is only set when the SNI is non-empty. With an empty outbound SNI the 
comparison is `""` against the Host header, so it returns false on every call 
and `validate_session_origin_cert()` never releases an H2 session from the 
pool. It fails closed, so this is a functional regression rather than a hole, 
but it is silent outside `ssl_verify` debug. Comparing against the same source 
the establishing side used would fix it. Note this branch also sits above the 
`check_name` gate, so it applies to operators who deliberately left `NAME` out 
of `verify_server_properties`.
   
   **On QUIC, `ssl_cert_callback` never runs.** 
`QUICMultiCertConfigLoader::_set_handshake_callbacks()` overrides the base and 
installs a `cert_cb` that only calls `select_quic_sni_context()`. So the 
premise behind removing the `setClientCertLevel()` branch, that 
`ssl_cert_callback` runs afterwards and is the single decision point, does not 
hold for H3. `ssl_client_rpk_ca_name` on a non-default entry stays inert there, 
which is the same bug this PR fixes for TLS. It is fail-closed, so a gap rather 
than a bypass, but the comments read as though it works everywhere. Wiring it 
into the QUIC callback or documenting it as unsupported would both be fine.
   
   ## The CI paragraph in the description is wrong
   
   It says the BoringSSL jobs pin a June 2024 revision predating 
`SSL_CREDENTIAL_new_raw_public_key`. `tools/build_boringssl_h3_tools.sh:42` 
pins `c3ffc3300a9450cf8e396c7880be7c6cadc16a4a`, which is dated 2026-04-24 and 
was last bumped on 2026-05-22 in 
[#13190](https://github.com/apache/trafficserver/pull/13190). I checked that 
revision's `include/openssl/ssl.h` directly and it declares 
`SSL_CREDENTIAL_new_raw_public_key`, `SSL_get0_peer_rpk` and 
`SSL_set1_accepted_peer_cert_types`, so `TS_USE_RPK` would be on for a 
BoringSSL build produced by the repo's own script.
   
   The error understates the coverage rather than overstating it, so nothing 
rests on it, but the docs should be right. The caveat that does hold is 
narrower: the Jenkins images are built out of the `trafficserver-ci` repo, so 
whether they actually track this pin is not visible from this tree. Worth 
saying that instead, or checking the image directly.
   
   ## Tests
   
   The four new scenarios are a real improvement, and scenario 12 closes the 
hole I cared about most, since the accept path under ENFORCED was previously 
only exercised under PERMISSIVE where the verdict was discarded. The 
`server.ocsp.pem` and `ca.ocsp.pem` pair is a clean purpose isolation: it 
chains cleanly and fails on purpose alone, which is what makes the 
`set_default()` mutation meaningful.
   
   One gap. Scenarios 9, 10 and 11 assert only `ExcludesExpression('origin 
response')`, so they establish that the connection did not work rather than 
that the fallback rejected it for the intended reason. Scenario 9 and scenario 
10 are currently indistinguishable, and scenario 9 would also pass if the edge 
sent no certificate at all, in which case the empty-chain early return fires 
and the hand-rolled verification never runs. Adding the distinguishing string 
to each would fix it, and for scenario 10 that string is free, since 
`X509_verify_cert_error_string()` prints `unsupported certificate purpose`.
   
   Separately, the new stash plumbing has no coverage at all. No scenario 
combines a `verify_client` CA override with an RPK-enabled entry, which is the 
path the use-after-free above lives on, and this delta removed the rebuild that 
used to cover it.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to