Skip to content

Error on Socket unref: Cannot read properties of undefined (reading 'Symbol(timeout)') #64490

Description

@haggholm

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions