The two stacks disagree about what counts as a usable identity, and QUIC is the permissive one.
TlsSession.ExtractCommonName refuses a CN containing control characters, and says why:
a CN carrying CR or LF forges log lines, and one carrying it into a header splits the response. PeerSubject escapes these on its way through X509_NAME_oneline, which left the accessor sold as the safe one as the only raw one. Refusing beats sanitising: a name that had to be rewritten to be safe is not the name that was presented.
The QUIC shim applies only three of those rules. iq_record_subject (src/protocols/ioxide.ngtcp2/native/ioxide_ngtcp2_shim.c) keeps the CN when it is non-empty, shorter than peer_cn, and free of an embedded NUL:
if (cn_len > 0 && (size_t)cn_len < sizeof(c->peer_cn) && memchr(cn, 0, (size_t)cn_len) == NULL) {
memcpy(c->peer_cn, cn, (size_t)cn_len);
There is no control-character check at all. So the same client certificate produces an authorizable name on QUIC and none on TCP, and QuicEngineConnection.PeerCommonName — the value documented as the one to authorize on — can carry CR/LF into whatever the handler logs next.
Needs a trusted CA willing to sign such a CN, so this is hardening rather than a live hole, but it is a divergence in the direction where the newer stack is weaker. The fix belongs in the shim next to the NUL check.
The two stacks disagree about what counts as a usable identity, and QUIC is the permissive one.
TlsSession.ExtractCommonNamerefuses a CN containing control characters, and says why:The QUIC shim applies only three of those rules.
iq_record_subject(src/protocols/ioxide.ngtcp2/native/ioxide_ngtcp2_shim.c) keeps the CN when it is non-empty, shorter thanpeer_cn, and free of an embedded NUL:There is no control-character check at all. So the same client certificate produces an authorizable name on QUIC and none on TCP, and
QuicEngineConnection.PeerCommonName— the value documented as the one to authorize on — can carry CR/LF into whatever the handler logs next.Needs a trusted CA willing to sign such a CN, so this is hardening rather than a live hole, but it is a divergence in the direction where the newer stack is weaker. The fix belongs in the shim next to the NUL check.