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

    Fields

    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