Skip to content

quic: guard against null impl_ in UpdateDataStats#62126

Open
zerone0x wants to merge 3 commits intonodejs:mainfrom
zerone0x:fix/quic-null-deref-updatedatastats
Open

quic: guard against null impl_ in UpdateDataStats#62126
zerone0x wants to merge 3 commits intonodejs:mainfrom
zerone0x:fix/quic-null-deref-updatedatastats

Conversation

@zerone0x
Copy link

@zerone0x zerone0x commented Mar 6, 2026

When a QUIC session's handshake fails or the connection is
terminated early, `UpdateDataStats()` can be called before `impl_`
has been initialized, leading to a null pointer dereference
and SIGSEGV.

Add an early return when `impl_` is nullptr to prevent the crash.

Fixes: #62057

When a QUIC session's handshake fails or the connection is
terminated early, UpdateDataStats() can be called before impl_
has been initialized, leading to a null pointer dereference
and SIGSEGV.

Add an early return when impl_ is nullptr to prevent the crash.

Fixes: nodejs#62057

Co-Authored-By: Claude <noreply@anthropic.com>
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. quic Issues and PRs related to the QUIC implementation / HTTP/3. labels Mar 6, 2026
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a regression test?

Exercises the crash path in Session::SendDataStats() where on_exit fires
UpdateDataStats() after Destroy() has already reset impl_ to nullptr.

Refs: nodejs#62126
@zerone0x
Copy link
Author

zerone0x commented Mar 6, 2026

added a regression test in test/parallel/test-quic-session-update-data-stats-after-close.mjs — creates a session with datagrams enabled, sends a datagram and immediately closes on both sides to race the on_exit UpdateDataStats() call against the destroy, which is the path that was crashing before the fix

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't crash on main for me, it just gets killed for a non-settling promise. With NODE_DEBUG=quic:

QUIC 662671: endpoint created
QUIC 662671: endpoint listening as a server
QUIC 662671: endpoint created
QUIC 662671: endpoint connecting as a client
QUIC 662671: session created
QUIC 662671: new server session callback QuicEndpoint { ... } Session { ... }
QUIC 662671: session created
QUIC 662671: session handshake callback localhost h3 TLS_AES_128_GCM_SHA256 TLSv1.3 unable to get local issuer certificate UNABLE_TO_GET_ISSUER_CERT_LOCALLY
QUIC 662671: gracefully closing the session
Warning: Detected unsettled top-level await at file:///tmp/node/test/parallel/test-quic-session-update-data-stats-after-close.mjs:52
await clientSession.closed;
^

Two fixes:

1. Pass ca: certs to connect() so TLS validation succeeds against the
   self-signed agent1-cert.pem. Without it the client rejects the server cert
   with UNABLE_TO_GET_ISSUER_CERT_LOCALLY, the session never fully opens, and
   'await clientSession.closed' never settles.

2. Track serverSession.closed via a separate Promise.withResolvers() so the
   test waits for both sides to cleanly tear down before exiting.

Also clarifies in the comment that the exact C++ crash path (packet-allocation
failure inside SendDatagram) cannot be triggered from JS; the test exercises
the closest reachable scenario and validates the null-impl_ guard introduced
in session.cc.

Co-Authored-By: Claude <noreply@anthropic.com>
@zerone0x
Copy link
Author

zerone0x commented Mar 8, 2026

good catch on both — fixed in the latest commit:

  • added ca: certs to the client connect() call so TLS validation passes against the self-signed agent1-cert.pem. without it the client rejects the server cert, the session never opens cleanly, and await clientSession.closed hangs
  • track serverSession.closed separately so the test waits for both sides to tear down before exiting

on the crash reproduction point: you are right that this does not trigger the actual crash path. the packet-allocation failure inside SendDatagram that originally caused the null deref cannot be induced from the JS side (the JS sendDatagram() has a #isClosedOrClosing guard that prevents reaching the C++ path once close is called). the test exercises the closest reachable scenario — datagrams fired immediately before close() on both sides — and validates that the if (!impl_) return guard does not regress. the comment in the test now explains this limitation explicitly.

if a cctest that exercises UpdateDataStats() directly after Destroy() would be preferred over the JS-level test, happy to add one instead.

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

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. quic Issues and PRs related to the QUIC implementation / HTTP/3.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

quic: NULL dereference in Session::UpdateDataStats when impl_ is null

4 participants