Commit 633df2896fe for nodejs

commit 633df2896fef9c5ddbfd01fb90db12fad6568f98
Author: Tim Perry <pimterry@gmail.com>
Date:   Mon Oct 26 12:51:19 2026 +0100

    tls: fix remotely reachable TLS server crash

    Signed-off-by: Tim Perry <pimterry@gmail.com>
    PR-URL: https://github.com/nodejs-private/node-private/pull/1018
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
    Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
    CVE-ID: CVE-2026-126051

diff --git a/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js
index 7b64712544d..4e295d9f8ca 100644
--- a/lib/internal/tls/wrap.js
+++ b/lib/internal/tls/wrap.js
@@ -253,6 +253,10 @@ function loadSession(hello) {
         'ticket?', hello.tlsTicket,
   );
   const owner = this[owner_symbol];
+  if (owner.destroyed) {
+    debug('server onclienthello after destroy, ignoring');
+    return;
+  }

   let once = false;
   function onSession(err, session) {
@@ -295,6 +299,10 @@ function loadSession(hello) {

 function loadSNI(info) {
   const owner = this[owner_symbol];
+  if (owner.destroyed) {
+    debug('server oncertcb after destroy, ignoring');
+    return;
+  }
   const servername = info.servername;
   if (!servername || !owner._SNICallback)
     return requestOCSP(owner, info);
diff --git a/test/parallel/test-tls-server-destroyed-before-deferred-callbacks.js b/test/parallel/test-tls-server-destroyed-before-deferred-callbacks.js
new file mode 100644
index 00000000000..05aba7b5e6d
--- /dev/null
+++ b/test/parallel/test-tls-server-destroyed-before-deferred-callbacks.js
@@ -0,0 +1,65 @@
+'use strict';
+
+// The server's 'onclienthello' and 'oncertcb' callbacks run from an immediate,
+// so a socket can be destroyed after its ClientHello is processed but before
+// they run. Destroyed sockets here must be ignored, not crash.
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+const tls = require('tls');
+const { once } = require('events');
+const { Duplex } = require('stream');
+
+const key = fixtures.readKey('agent1-key.pem');
+const cert = fixtures.readKey('agent1-cert.pem');
+
+// A ClientHello with no SNI that requests OCSP stapling.
+async function getClientHello() {
+  const { promise, resolve } = Promise.withResolvers();
+  const client = tls.connect({
+    host: '127.0.0.1',
+    requestOCSP: true,
+    socket: new Duplex({ read() {}, write: resolve }),
+  });
+  const hello = await promise;
+  client.destroy();
+  return hello;
+}
+
+// Delivers the hello and EOF in the same tick, so the server is destroyed
+// before its deferred callbacks run. Like a net.Socket, the connection is not
+// half-open and is closed asynchronously rather than auto-destroyed.
+async function sendHelloThenClose(server, hello) {
+  server.on('secureConnection', common.mustNotCall());
+  const connection = new Duplex({
+    allowHalfOpen: false,
+    autoDestroy: false,
+    read() {},
+    write(chunk, encoding, callback) { callback(); },
+  });
+  server.emit('connection', connection);
+  connection.push(hello);
+  connection.push(null);
+
+  const [err] = await once(server, 'tlsClientError');
+  assert.strictEqual(err.code, 'ECONNRESET');
+}
+
+(async () => {
+  const hello = await getClientHello();
+
+  // Repro via oncertcb reaching requestOCSP():
+  await sendHelloThenClose(tls.createServer({
+    key, cert,
+    SNICallback: common.mustNotCall(),
+  }), hello);
+
+  // Repro via 'onclienthello', when session callbacks are enabled:
+  const sessionServer = tls.createServer({ key, cert });
+  sessionServer.on('newSession', common.mustNotCall());
+  await sendHelloThenClose(sessionServer, hello);
+})().then(common.mustCall());