Skip to content

Commit cf039e2

Browse files
committed
tls: route event listener exceptions through error handlers
Signed-off-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: #63822 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent dfefe03 commit cf039e2

2 files changed

Lines changed: 189 additions & 6 deletions

File tree

lib/internal/tls/wrap.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,14 @@ function loadSession(hello) {
270270
if (hello.sessionId.length <= 0 ||
271271
hello.tlsTicket ||
272272
(owner.server &&
273-
!owner.server.emit('resumeSession', hello.sessionId, onSession))) {
273+
!(() => {
274+
try {
275+
return owner.server.emit('resumeSession', hello.sessionId, onSession);
276+
} catch (err) {
277+
onSession(err);
278+
return true;
279+
}
280+
})())) {
274281
// Sessions without identifiers can't be resumed.
275282
// Sessions with tickets can be resumed directly from the ticket, no server
276283
// session storage is necessary.
@@ -399,10 +406,14 @@ function requestOCSP(socket, info) {
399406
};
400407

401408
debug('server oncertcb emit OCSPRequest');
402-
socket.server.emit('OCSPRequest',
403-
ctx.getCertificate(),
404-
ctx.getIssuer(),
405-
onOCSP);
409+
try {
410+
socket.server.emit('OCSPRequest',
411+
ctx.getCertificate(),
412+
ctx.getIssuer(),
413+
onOCSP);
414+
} catch (err) {
415+
socket.destroy(err);
416+
}
406417
}
407418

408419
function requestOCSPDone(socket) {
@@ -455,7 +466,14 @@ function onnewsession(sessionId, session) {
455466
};
456467

457468
owner._newSessionPending = true;
458-
if (!owner.server.emit('newSession', sessionId, session, done))
469+
470+
let hasEmitted = false;
471+
try {
472+
hasEmitted = owner.server.emit('newSession', sessionId, session, done);
473+
} catch (err) {
474+
owner.server.emit('error', err);
475+
}
476+
if (!hasEmitted)
459477
done();
460478
}
461479

test/parallel/test-tls-psk-alpn-callback-exception-handling.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ if (process.features.openssl_is_boringssl) {
2121

2222
const assert = require('assert');
2323
const { describe, it } = require('node:test');
24+
const https = require('node:https');
2425
const tls = require('tls');
2526
const fixtures = require('../common/fixtures');
2627

@@ -117,6 +118,170 @@ describe('TLS callback exception handling', () => {
117118
await promise;
118119
});
119120

121+
it('newSession synchronous error emits error', async (t) => {
122+
const server = tls.createServer({
123+
key: fixtures.readKey('agent2-key.pem'),
124+
cert: fixtures.readKey('agent2-cert.pem'),
125+
sessionTimeout: 3600,
126+
});
127+
const agent = new https.Agent({
128+
keepAlive: false,
129+
keepAliveMsecs: 10_000,
130+
maxSockets: 5,
131+
});
132+
133+
t.after(() => {
134+
agent.destroy();
135+
server.close();
136+
});
137+
138+
const { promise, resolve } = createTestPromise();
139+
140+
const expectedError = new Error();
141+
server.on('newSession', common.mustCall(() => {
142+
setImmediate(resolve);
143+
throw expectedError;
144+
}, 2));
145+
146+
server.on('error', common.mustCall((e) => {
147+
assert.strictEqual(e, expectedError);
148+
}, 2));
149+
150+
await new Promise((res) => server.listen(0, res));
151+
152+
// We might need one or two requests to trigger the `resumeSession` event
153+
const clientOptions = {
154+
port: server.address().port,
155+
host: '127.0.0.1',
156+
rejectUnauthorized: false,
157+
};
158+
// First connection: Establish a session
159+
const socket = tls.connect(clientOptions, common.mustCall(() => {
160+
// Close immediately to allow session resumption on next connect
161+
socket.end();
162+
}));
163+
164+
socket.on('error', common.mustNotCall());
165+
166+
socket.on('close', common.mustCall());
167+
168+
await promise;
169+
});
170+
171+
it('OCSPRequest listener synchronous error emits tlsClientError', async (t) => {
172+
const server = tls.createServer({
173+
key: fixtures.readKey('agent2-key.pem'),
174+
cert: fixtures.readKey('agent2-cert.pem'),
175+
requestOCSP: true,
176+
});
177+
178+
t.after(() => server.close());
179+
180+
const { promise, resolve, reject } = createTestPromise();
181+
182+
const expectedError = new Error();
183+
server.on('OCSPRequest', (cert, issuer, cb) => {
184+
throw expectedError;
185+
});
186+
187+
server.on('tlsClientError', common.mustCall((err, socket) => {
188+
try {
189+
assert.strictEqual(err, expectedError);
190+
socket.destroy();
191+
resolve();
192+
} catch (e) {
193+
reject(e);
194+
}
195+
}));
196+
server.on('secureConnection', common.mustNotCall('secureConnection listener'));
197+
server.on('error', common.mustNotCall('server error listener'));
198+
199+
await new Promise((res) => server.listen(0, res));
200+
201+
const client = tls.connect({
202+
port: server.address().port,
203+
host: '127.0.0.1',
204+
rejectUnauthorized: false,
205+
requestOCSP: true,
206+
});
207+
208+
client.on('error', common.mustCall());
209+
210+
await promise;
211+
});
212+
213+
it('resumeSession listener synchronous error emits tlsClientError', async (t) => {
214+
const server = tls.createServer({
215+
key: fixtures.readKey('agent2-key.pem'),
216+
cert: fixtures.readKey('agent2-cert.pem'),
217+
sessionTimeout: 3600,
218+
});
219+
const agent = new https.Agent({
220+
keepAlive: true,
221+
keepAliveMsecs: 10_000,
222+
maxSockets: 5,
223+
});
224+
225+
t.after(() => {
226+
agent.destroy();
227+
server.close();
228+
});
229+
230+
const { promise, resolve, reject } = createTestPromise();
231+
232+
const expectedError = new Error();
233+
server.on('resumeSession', (id, cb) => {
234+
throw expectedError;
235+
});
236+
237+
server.on('tlsClientError', common.mustCall((err, socket) => {
238+
try {
239+
assert.strictEqual(err, expectedError);
240+
socket.destroy();
241+
resolve();
242+
} catch (e) {
243+
reject(e);
244+
}
245+
}));
246+
server.on('error', common.mustNotCall('server error listener'));
247+
248+
await new Promise((res) => server.listen(0, res));
249+
250+
const expectErrorOnResume = common.expectsError();
251+
252+
// We might need one or two requests to trigger the `resumeSession` event
253+
let triggeredOnFirstRequest = true;
254+
const reqOptions = {
255+
port: server.address().port,
256+
host: '127.0.0.1',
257+
path: '/',
258+
method: 'GET',
259+
agent,
260+
rejectUnauthorized: false,
261+
};
262+
const req = https.request(reqOptions, (res) => {
263+
triggeredOnFirstRequest = false;
264+
res.resume();
265+
res.on('end', () => {
266+
const req = https.request(reqOptions, (res) => {
267+
res.resume();
268+
});
269+
req.on('error', expectErrorOnResume);
270+
req.end();
271+
});
272+
});
273+
274+
req.on('error', expectErrorOnResume);
275+
req.end();
276+
277+
await promise;
278+
279+
if (!triggeredOnFirstRequest) {
280+
// The second request would have received the error instead.
281+
req.off('error', expectErrorOnResume);
282+
}
283+
});
284+
120285
// Test 2: PSK server callback throwing should emit tlsClientError
121286
it('pskCallback throwing emits tlsClientError', async (t) => {
122287
const server = tls.createServer({

0 commit comments

Comments
 (0)