Skip to content

quic: crash when incoming unidirectional stream has no onstream handlerΒ #64030

Description

@efekrskl

When a QUIC server accepts a session without .onstream, and the client connects with a unidirectional stream with a body, Node crashes with SIGSEGV

If I use a bidirectional stream instead, it works. If I create the unidirectional stream without a body, it also does not crash.

Expected behaviour would be to safely destroy or at least not crash.

Repro:

import {readFileSync} from 'node:fs';
import {createPrivateKey} from 'node:crypto';
import {listen, connect} from 'node:quic';

const key = createPrivateKey(readFileSync(new URL('./key.pem', import.meta.url)));
const cert = readFileSync(new URL('./cert.pem', import.meta.url));

const serverEndpoint = await listen(async (session) => {
  await session.opened;
  console.log('Server session opened');

  // Intentionally do not install .onstream
}, {
  sni: {'*': {keys: [key], certs: [cert]}},
  alpn: ['repro'],
});

const clientSession = await connect(serverEndpoint.address, {
  servername: 'localhost',
  alpn: 'repro',
  ca: cert,
});

await clientSession.opened;
console.log('Client session opened');

await clientSession.createUnidirectionalStream({
  body: 'something something darkside'
});

console.log('Unidirectional stream created');

Outputs

(node:99156) ExperimentalWarning: quic is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Client session opened
Unidirectional stream created
Server session opened
(node:99156) Warning: A new stream was received but no onstream callback was provided
(node:99156) Warning: A new stream was received but no onstream callback was provided
(node:99156) Warning: A new stream was received but no onstream callback was provided
fish: Job 1, './out/Release/node --experiment…' terminated by signal SIGSEGV (Address boundary error)

Activity

  1. added
    quicIssues and PRs related to the QUIC transport implementation.
    on Jun 20, 2026
  2. martenrichter commented on Jun 21, 2026

    @martenrichter
    Contributor

    I turned it into a test:

    // Flags: --experimental-quic  --no-warnings
    // Test: client-initiated unidirectional stream with no onstream
    // The client creates a uni stream with no onstream.
    // Verify, that this causes no crash
    
    import { hasQuic, skip, mustCall } from '../common/index.mjs';
    import {readFileSync} from 'node:fs';
    import {createPrivateKey} from 'node:crypto';
    import * as fixtures from '../common/fixtures.mjs';
    
    const { readKey } = fixtures;
    
    if (!hasQuic) {
      skip('QUIC is not enabled');
    }
    
    const { listen, connect } = await import('../common/quic.mjs');
    
    
    const serverKey = createPrivateKey(readKey('agent1-key.pem'));
    const serverCert = readKey('agent1-cert.pem');
    
    const serverEndpoint = await listen(mustCall(async (session) => {
      await session.opened;
      console.log('Server session opened');
    
      // Intentionally do not install .onstream
    }), {
      sni: {'*': {keys: [serverKey], certs: [serverCert]}},
      alpn: ['repro'],
    });
    
    const clientSession = await connect(serverEndpoint.address, {
      servername: 'localhost',
      alpn: 'repro',
      ca: serverCert,
    });
    
    await clientSession.opened;
    console.log('Client session opened');
    
    await clientSession.createUnidirectionalStream({
      body: 'something something darkside'
    });
    
    console.log('Unidirectional stream created');
    

    and tried to reproduce it on windows, but no luck. So I expect that it is a race condition or uninitialized memory, which may be system depent. However, my local main is still on martenrichter@a17d2b8 from yesterday, so may be it is a very recent commit. I try to update and test again.

  3. martenrichter commented on Jun 21, 2026

    @martenrichter
    Contributor

    Does not crash either... on current main.

  4. martenrichter commented on Jun 21, 2026

    @martenrichter
    Contributor

    My best bet is that something in Stream::Destroy:

    if (stats()->destroyed_at != 0) return;

    crashes, where something is not protected against a unidirectional stream that has no write side.
    So as you can reproduce it locally, add some printf in this function to narrow down the crash. As it does not crash on my install I can not help with the debugging part.

  5. efekrskl commented on Jun 26, 2026

    @efekrskl
    MemberAuthor

    My best bet is that something in Stream::Destroy:
    ...
    So as you can reproduce it locally, add some printf in this function to narrow down the crash. As it does not crash on my install I can not help with the debugging part.

    Thanks for the input!

    I've been debugging this over the last few days, as far as I see this is caused by JS synchronously destroying the stream and native code trying to access it. Weird you couldn't reproduce it though, it is 100% reproducable for me.

  6. martenrichter commented on Jun 27, 2026

    @martenrichter
    Contributor

    Thanks for the input!

    I've been debugging this over the last few days, as far as I see this is caused by JS synchronously destroying the stream and native code trying to access it. Weird you couldn't reproduce it though, it is 100% reproducable for me.

    So that means something on the native side is not holding a reference to JS long enough.
    (You may want to look at my old PR #60237, the code basis change a bit since then, what I had fixed there many bugs like this, so if one of the fix is close to the parts you are debugging, it may be a match).

    And where are the crashes happening?

    And it is not so weird, that I can not repoduce it. These type of race condition triggered crashes depend on the timing, and also the timing of network packets. And Windows uses a different compiler, so memory is differently initialized. So it is often machine and OS dependent.

    [Edit: I see you already made a PR, was not clear to me when writing the above]

  7. efekrskl commented on Jun 30, 2026

    @efekrskl
    MemberAuthor

    [Edit: I see you already made a PR, was not clear to me when writing the above]

    Yeah, I have a PR open, but I appreciate the input. I'm trying to get familiar with QUIC and it's implementation in Node.js, so any extra guidance helps :)

  8. efekrskl commented on Jul 11, 2026

    @efekrskl
    MemberAuthor

    Fixed #64158

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    quicIssues and PRs related to the QUIC transport implementation.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions