镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit 6c85b8e

Browse files
pimterryaduh95
authored andcommitted
tls: fix remotely reachable TLS server crash
Signed-off-by: Tim Perry <pimterry@gmail.com> PR-URL: nodejs-private/node-private#1018 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> CVE-ID: CVE-2026-126051
1 parent 94b7367 commit 6c85b8e

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

‎lib/internal/tls/wrap.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ function loadSession(hello) {
248248
'ticket?', hello.tlsTicket,
249249
);
250250
const owner = this[owner_symbol];
251+
if (owner.destroyed) {
252+
debug('server onclienthello after destroy, ignoring');
253+
return;
254+
}
251255

252256
let once = false;
253257
function onSession(err, session) {
@@ -290,6 +294,10 @@ function loadSession(hello) {
290294

291295
function loadSNI(info) {
292296
const owner = this[owner_symbol];
297+
if (owner.destroyed) {
298+
debug('server oncertcb after destroy, ignoring');
299+
return;
300+
}
293301
const servername = info.servername;
294302
if (!servername || !owner._SNICallback)
295303
return requestOCSP(owner, info);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
3+
// The server's 'onclienthello' and 'oncertcb' callbacks run from an immediate,
4+
// so a socket can be destroyed after its ClientHello is processed but before
5+
// they run. Destroyed sockets here must be ignored, not crash.
6+
7+
const common = require('../common');
8+
if (!common.hasCrypto)
9+
common.skip('missing crypto');
10+
11+
const fixtures = require('../common/fixtures');
12+
const assert = require('assert');
13+
const tls = require('tls');
14+
const { once } = require('events');
15+
const { Duplex } = require('stream');
16+
17+
const key = fixtures.readKey('agent1-key.pem');
18+
const cert = fixtures.readKey('agent1-cert.pem');
19+
20+
// A ClientHello with no SNI that requests OCSP stapling.
21+
async function getClientHello() {
22+
const { promise, resolve } = Promise.withResolvers();
23+
const client = tls.connect({
24+
host: '127.0.0.1',
25+
requestOCSP: true,
26+
socket: new Duplex({ read() {}, write: resolve }),
27+
});
28+
const hello = await promise;
29+
client.destroy();
30+
return hello;
31+
}
32+
33+
// Delivers the hello and EOF in the same tick, so the server is destroyed
34+
// before its deferred callbacks run. Like a net.Socket, the connection is not
35+
// half-open and is closed asynchronously rather than auto-destroyed.
36+
async function sendHelloThenClose(server, hello) {
37+
server.on('secureConnection', common.mustNotCall());
38+
const connection = new Duplex({
39+
allowHalfOpen: false,
40+
autoDestroy: false,
41+
read() {},
42+
write(chunk, encoding, callback) { callback(); },
43+
});
44+
server.emit('connection', connection);
45+
connection.push(hello);
46+
connection.push(null);
47+
48+
const [err] = await once(server, 'tlsClientError');
49+
assert.strictEqual(err.code, 'ECONNRESET');
50+
}
51+
52+
(async () => {
53+
const hello = await getClientHello();
54+
55+
// Repro via oncertcb reaching requestOCSP():
56+
await sendHelloThenClose(tls.createServer({
57+
key, cert,
58+
SNICallback: common.mustNotCall(),
59+
}), hello);
60+
61+
// Repro via 'onclienthello', when session callbacks are enabled:
62+
const sessionServer = tls.createServer({ key, cert });
63+
sessionServer.on('newSession', common.mustNotCall());
64+
await sendHelloThenClose(sessionServer, hello);
65+
})().then(common.mustCall());

0 commit comments

Comments
 (0)