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());