Version
v24.18.0
Platform
Linux scatha 7.0.11-76070011-generic #202606011647~1780583630~22.04~70ad774 SMP PREEMPT_DYNAMIC Thu J x86_64 x86_64 x86_64 GNU/Linux
Subsystem
lib/internal/tls/wrap.js
What steps will reproduce the bug?
Very minimal (and artificial):
const net = require('net');
const s = new net.Socket();
s._parent = undefined; // a chain link left `undefined` (see note on the trigger)
s._unrefTimer();
More representative, though the real case involved some library setting the handle to undefined whereas this does it explicitly:
const fs = require('fs');
const tls = require('tls');
// self-signed cert: openssl req -x509 -newkey rsa:2048 -nodes \
// -keyout key.pem -out cert.pem -days 2 -subj "/CN=localhost"
const opts = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') };
const server = tls.createServer(opts, (conn) => {
setTimeout(() => conn.write('x'), 50); // inbound record after the chain is corrupted
});
server.listen(0, () => {
const client = tls.connect(
{ port: server.address().port, rejectUnauthorized: false },
() => { client._parent = undefined; }, // invariant violation (stands in for teardown race)
);
client.on('error', () => {});
});
How often does it reproduce? Is there a required condition?
It seems to happen very sporadically; we're seeing a few instances a day in a system that handles significant volume.
What is the expected behavior? Why is that the expected behavior?
The object should be unref-ed, and with no parent, the parent traversal should be a no-op.
What do you see instead?
TypeError: Cannot read properties of undefined (reading 'Symbol(timeout)')
at TLSSocket._unrefTimer (node:net:529:10)
at TLSWrap.onStreamRead (node:internal/stream_base_commons:172:23)
Additional information
In lib/net.js, the parent chain is traversed with an exact equality check s !== null which fails if some library happens to null out the parent ref with undefined, instead. If this were a lax (s != null) check, the error ought to go away.
const { kTimeout } = require('internal/timers'); // kTimeout = Symbol('timeout')
Socket.prototype._unrefTimer = function _unrefTimer() {
for (let s = this; s !== null; s = s._parent) {
if (s[kTimeout])
s[kTimeout].refresh();
}
};
Version
v24.18.0
Platform
Subsystem
lib/internal/tls/wrap.js
What steps will reproduce the bug?
Very minimal (and artificial):
More representative, though the real case involved some library setting the handle to
undefinedwhereas this does it explicitly:How often does it reproduce? Is there a required condition?
It seems to happen very sporadically; we're seeing a few instances a day in a system that handles significant volume.
What is the expected behavior? Why is that the expected behavior?
The object should be unref-ed, and with no parent, the parent traversal should be a no-op.
What do you see instead?
Additional information
In
lib/net.js, the parent chain is traversed with an exact equality checks !== nullwhich fails if some library happens to null out the parent ref withundefined, instead. If this were a lax (s != null) check, the error ought to go away.