-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
net: fix crash due to simultaneous close/shutdown on JS Stream Sockets
A JS stream socket wraps a stream, exposing it as a socket for something on top which needs a socket specifically (e.g. an HTTP server). If the internal stream is closed in the same tick as the layer on top attempts to close this stream, the race between doShutdown and doClose results in an uncatchable exception. A similar race can happen with doClose and doWrite. It seems legitimate these can happen in parallel, so this resolves that by explicitly detecting and handling that situation: if a close is in progress, both doShutdown & doWrite allow doClose to run finishShutdown/Write for them, cancelling the operation, without trying to use this._handle (which will be null) in the meantime. PR-URL: #49400 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
- Loading branch information
Showing
2 changed files
with
91 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const fixtures = require('../common/fixtures'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
const assert = require('assert'); | ||
const net = require('net'); | ||
const tls = require('tls'); | ||
const h2 = require('http2'); | ||
|
||
// This test sets up an H2 proxy server, and tunnels a request over one of its streams | ||
// back to itself, via TLS, and then closes the TLS connection. On some Node versions | ||
// (v18 & v20 up to 20.5.1) the resulting JS Stream Socket fails to shutdown correctly | ||
// in this case, and crashes due to a null pointer in finishShutdown. | ||
|
||
const tlsOptions = { | ||
key: fixtures.readKey('agent1-key.pem'), | ||
cert: fixtures.readKey('agent1-cert.pem'), | ||
ALPNProtocols: ['h2'] | ||
}; | ||
|
||
const netServer = net.createServer((socket) => { | ||
socket.allowHalfOpen = false; | ||
// ^ This allows us to trigger this reliably, but it's not strictly required | ||
// for the bug and crash to happen, skipping this just fails elsewhere later. | ||
|
||
h2Server.emit('connection', socket); | ||
}); | ||
|
||
const h2Server = h2.createSecureServer(tlsOptions, (req, res) => { | ||
res.writeHead(200); | ||
res.end(); | ||
}); | ||
|
||
h2Server.on('connect', (req, res) => { | ||
res.writeHead(200, {}); | ||
netServer.emit('connection', res.stream); | ||
}); | ||
|
||
netServer.listen(0, common.mustCall(() => { | ||
const proxyClient = h2.connect(`https://localhost:${netServer.address().port}`, { | ||
rejectUnauthorized: false | ||
}); | ||
|
||
const proxyReq = proxyClient.request({ | ||
':method': 'CONNECT', | ||
':authority': 'example.com:443' | ||
}); | ||
|
||
proxyReq.on('response', common.mustCall((response) => { | ||
assert.strictEqual(response[':status'], 200); | ||
|
||
// Create a TLS socket within the tunnel, and start sending a request: | ||
const tlsSocket = tls.connect({ | ||
socket: proxyReq, | ||
ALPNProtocols: ['h2'], | ||
rejectUnauthorized: false | ||
}); | ||
|
||
proxyReq.on('close', common.mustCall(() => { | ||
proxyClient.close(); | ||
netServer.close(); | ||
})); | ||
|
||
// Forcibly kill the TLS socket | ||
tlsSocket.destroy(); | ||
|
||
// This results in an async error in affected Node versions, before the 'close' event | ||
})); | ||
})); |