Skip to content

External client can trigger quic_bug_10586_3 in quiche based servers #147

Description

@martenrichter

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:

void QuicSimpleServerStream::SendResponse() {
void QuicSimpleServerStream::SendErrorResponse(int resp_code) {
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: fails-components/webtransport@ccc15b5 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 nodejs/node#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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions