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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions