fix: prevent one-sided auth when stream teardown races connection drop#6
fix: prevent one-sided auth when stream teardown races connection drop#6gusinacio wants to merge 1 commit into
Conversation
`end_of_auth` awaited `send.stopped()` and propagated any error up the call stack. In practice this raced with the peer dropping its `Connection` as soon as *it* finished auth, so the slower side frequently observed `connection lost` from `stopped().await` and returned an error *before* calling `add_authenticated`. The faster side ended up authenticated, the slower side did not, and the real application connection timed out in `after_handshake` waiting for the watcher to tick. Two changes fix this: 1. `end_of_auth` becomes best-effort. All SPAKE2 tokens and HKDF-derived keys are already written and ACKed at the QUIC layer by the time we reach this function; the stream FIN is cosmetic and should not be able to fail auth. 2. `add_authenticated` is moved *before* `end_of_auth` in both `auth_accept` and `auth_open`. Even if teardown is ever made non-best-effort again, the authenticated set is committed first. The error branches in both paths also call `end_of_auth` best-effort so the stream is still finalized on mismatch without masking the real `...FailedAndBlock` error. All existing tests continue to pass.
|
Hi @gusinacio, thank you for the PR! The issue in I suggest that after I push #5 to main we rebase this branch and keep your ordering changes and if required adopt the the infallible My solution was to use Lines 317 to 331 in 08e8930 I will write you here once I have updated main. Do you want to rebase it after or should I? (don't want to mess around with your fork if you don't agree ^^). Let me know what you think! |
|
Feel free to rebase it. |
end_of_authawaitedsend.stopped()and propagated any error up the call stack. In practice this raced with the peer dropping itsConnectionas soon as it finished auth, so the slower side frequently observedconnection lostfromstopped().awaitand returned an error before callingadd_authenticated. The faster side ended up authenticated, the slower side did not, and the real application connection timed out inafter_handshakewaiting for the watcher to tick.Two changes fix this:
end_of_authbecomes best-effort. All SPAKE2 tokens and HKDF-derived keys are already written and ACKed at the QUIC layer by the time we reach this function; the stream FIN is cosmetic and should not be able to fail auth.add_authenticatedis moved beforeend_of_authin bothauth_acceptandauth_open. Even if teardown is ever made non-best-effort again, the authenticated set is committed first.The error branches in both paths also call
end_of_authbest-effort so the stream is still finalized on mismatch without masking the real...FailedAndBlockerror.All existing tests continue to pass.