External client can trigger quic_bug_10586_3 in quiche based servers
- Dominant language
- C++
- Stars
- 897
- Forks
- 177
- PR merge metrics
- No merged PRs in 30d
Description
I was currently testing my quiche based node.js webtransport plugin, against node.js quic implementation.
During the test I saw: F0517 05:33:10.985518 17324 quic_stream.cc:818] quic_bug_10586_3: Fin already buffered, or RESET_STREAM_AT sent
I hunted down the cause and I can mitigate my own code. But the pattern is probably an issue also for other quiche-based implementations,
It works as follows: 1.) quiche is running the server 2.) node.js is the quic is the client.
3.) I during my frist test I passed aheader when creating the bidrectional stream for the session, which automatically sends a fin in the control stream and so WebTransportHttp3::OnConnectStreamFinReceived is called. This immediately sends a fin on the control stream.
4.) The parsing of the initial body is inflight and does not know about fin, so that calls to size_t QuicSpdyStream::WriteHeaders or similar calls of QuicSpdyStream cause quic_bug_10586_3.
My lib is derived partially from libquiche test, though the issue is present in: https://github.com/google/quiche/blob/ce53d3090da59a80e2f08c39a5fefe3b6db7d44f/quiche/quic/tools/quic_simple_server_stream.cc#L164 https://github.com/google/quiche/blob/ce53d3090da59a80e2f08c39a5fefe3b6db7d44f/quiche/quic/tools/quic_simple_server_stream.cc#L417 etc. and may be in other quiche based code. (I do not know if envoy may be affected).
Please look at my repo at the commit: https://github.com/fails-components/webtransport/commit/ccc15b57c0e8413f8bb91d3c3f3f9ca8d7bfff5c it mitigates the issue, and I would suggest that the examples in the quiche repo are updated.
Steps to reproduce: 1.) Build current (as of mid may, but should still work) node.js main (I did it on windows and use the PR quic-issue-63216 https://github.com/nodejs/node/pull/63230, but I do not think it matters) 2.) Connect to a webtransport endpoint and path on a quiche based server, e.g. with something like:
import { connect } from 'node:quic'
import { isIP } from 'node:net'
function(host, port, path) {
if (typeof port === 'undefined') port = 443
/** @type {import('../session.js').HttpClient} */
const quicOptions = {
alpn: 'h3', // it is the default,
application: {
enableConnectProtocol: true, // needed to start a webtransport session
enableDatagrams: true,
maxStreamWindow: 6 * 1024 * 1024,
maxWindow: 15 * 1024 * 1024
// I am wondering, how certificates are verified.
},
transportParams: {
initialMaxStreamDataBidiLocal: 100,
initialMaxStreamDataBidiRemote: 100,
initialMaxStreamDataUni: 100,
initialMaxData: 15 * 1024 * 1024, // or something else
initialMaxStreamsBidi: 100,
initialMaxStreamsUni: 100,
maxDatagramFrameSize: 1200 // required to start a webtransport session
}
}
if (!isIP(host)) {
quicOptions.servername = host
}
const clientInt = connect(host + ':' + port, quicOptions)
console.log('tried to connect to', host + ':' + port)
let connected = false
clientInt
.then(async (session) => {
console.log('http3 client created')
session.onsessionticket = (ticket) => {
console.log('ticket', ticket)
}
await session.opened
console.log('http3 client connected')
let headersReceivedResolve
const headersReceivedProm = new Promise((resolve, reject) => {
headersReceivedResolve = resolve
})
session
.createBidirectionalStream({
headers: { /* this is important as it immediately sends a fin */
':method': 'CONNECT',
':scheme': 'https',
// this one depends on draft, draft14 says "webtransport", draft15 says "webtransport-h3"
':protocol': 'webtransport',
':path': path,
':authority': host + ':' + port
},
onheaders(headers) {
headersReceivedResolve(headers)
}
})
.then((stream) => {
console.log('session established')
})
.catch((error) => {
console.log('Error creating sessionstream')
throw error
})
})
.catch((error) => {
connected = false
console.log('http3 connection fail', error)
})
}
Note that this is untested code I distilled from my code inside a bigger framework, though you should be able to build code to trigger the event. The path should point to a webtransport endpoint.
If this is applied to an implementation that did not mitigate the issue, you can trigger the quic bug and crash the server. (Note I have this issue before using another way, and the team said I should file it here so that it may be fixed.)
Contributor guide
Research direction
Start with quiche/quic/tools/quic_simple_server_stream.cc at the referenced lines 164 and 417, then trace WebTransportHttp3::OnConnectStreamFinReceived and QuicSpdyStream::WriteHeaders. Reproduce the WebTransport connection using the supplied Node.js example and verify that the server no longer crashes with quic_bug_10586_3; update the quiche examples if appropriate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, javascript, node.js
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100