From 2f80c4d10a0d2ceeefd8b0e62b17f7a7c226c184 Mon Sep 17 00:00:00 2001 From: Damian Meden Date: Thu, 3 Sep 2026 14:02:26 +0200 Subject: [PATCH 1/2] HTTP/3: compare stream ids at full width in HQSession lookup HQSession::get_transaction() matched with t->get_transaction_id() == static_cast(id). get_transaction_id() returns int and QUICStreamId is a 62-bit value, so both sides narrow and two live transactions whose ids share the low 32 bits alias each other. Add get_quic_stream_id(), returning the QUICStreamId already cached in _stream_id by the constructor, and compare against that. Reaching through _info.adapter.stream() instead would reintroduce the use-after-free fixed by 13213. --- include/proxy/http3/Http3Transaction.h | 11 +++++++++++ src/proxy/http3/Http3Session.cc | 2 +- src/proxy/http3/Http3Transaction.cc | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/proxy/http3/Http3Transaction.h b/include/proxy/http3/Http3Transaction.h index abb7dfd6359..3edca21c4f6 100644 --- a/include/proxy/http3/Http3Transaction.h +++ b/include/proxy/http3/Http3Transaction.h @@ -61,6 +61,17 @@ class HQTransaction : public ProxyTransaction void increment_transactions_stat() override; void decrement_transactions_stat() override; + /** Full-width QUIC stream identifier for this transaction. + * + * @c get_transaction_id returns @c int and exists primarily for compact log + * fields and probe arguments; QUIC stream IDs are 62-bit values, so callers + * that route or compare by stream ID must use this accessor to avoid + * truncation. + * + * @return The QUIC stream ID owned by this transaction. + */ + QUICStreamId get_quic_stream_id() const; + // VConnection interface virtual VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; virtual VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, diff --git a/src/proxy/http3/Http3Session.cc b/src/proxy/http3/Http3Session.cc index 4c899f2e37f..1ec5a09d161 100644 --- a/src/proxy/http3/Http3Session.cc +++ b/src/proxy/http3/Http3Session.cc @@ -97,7 +97,7 @@ HQTransaction * HQSession::get_transaction(QUICStreamId id) { for (HQTransaction *t = this->_transaction_list.head; t; t = static_cast(t->link.next)) { - if (t->get_transaction_id() == static_cast(id)) { + if (t->get_quic_stream_id() == id) { return t; } } diff --git a/src/proxy/http3/Http3Transaction.cc b/src/proxy/http3/Http3Transaction.cc index 44b2c828ef7..b2a818029f8 100644 --- a/src/proxy/http3/Http3Transaction.cc +++ b/src/proxy/http3/Http3Transaction.cc @@ -225,6 +225,13 @@ HQTransaction::transaction_done() int HQTransaction::get_transaction_id() const +{ + // Narrowing is intentional here; see get_quic_stream_id() for the full-width value. + return static_cast(this->_stream_id); +} + +QUICStreamId +HQTransaction::get_quic_stream_id() const { return this->_stream_id; } From d77195af66e0909ae843008bbc74df1df81c2a79 Mon Sep 17 00:00:00 2001 From: Damian Meden Date: Tue, 8 Sep 2026 10:31:14 +0200 Subject: [PATCH 2/2] HTTP/3: move get_quic_stream_id() to the HQTransaction block The declaration sat under "Implement ProxyClienTransaction interface", but get_quic_stream_id() is not a ProxyTransaction override. Move it next to direction(), after it so the alignment group on state_stream_open/state_stream_closed/direction is untouched. Declaration placement only; no behavior change. --- include/proxy/http3/Http3Transaction.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/include/proxy/http3/Http3Transaction.h b/include/proxy/http3/Http3Transaction.h index 3edca21c4f6..d911967ff17 100644 --- a/include/proxy/http3/Http3Transaction.h +++ b/include/proxy/http3/Http3Transaction.h @@ -61,17 +61,6 @@ class HQTransaction : public ProxyTransaction void increment_transactions_stat() override; void decrement_transactions_stat() override; - /** Full-width QUIC stream identifier for this transaction. - * - * @c get_transaction_id returns @c int and exists primarily for compact log - * fields and probe arguments; QUIC stream IDs are 62-bit values, so callers - * that route or compare by stream ID must use this accessor to avoid - * truncation. - * - * @return The QUIC stream ID owned by this transaction. - */ - QUICStreamId get_quic_stream_id() const; - // VConnection interface virtual VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; virtual VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, @@ -85,6 +74,17 @@ class HQTransaction : public ProxyTransaction virtual int state_stream_closed(int event, Event *data) = 0; NetVConnectionContext_t direction() const; + /** Full-width QUIC stream identifier for this transaction. + * + * @c get_transaction_id returns @c int and exists primarily for compact log + * fields and probe arguments; QUIC stream IDs are 62-bit values, so callers + * that route or compare by stream ID must use this accessor to avoid + * truncation. + * + * @return The QUIC stream ID owned by this transaction. + */ + QUICStreamId get_quic_stream_id() const; + // For Queue from tscore/Link.h LINK(HQTransaction, link);