nodejs / nodejs/node

TLSSocket does not gracefully handle peer protocol violation during TLS teardown

Ouverte
#61,714 4 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

tls
Langage dominant
JavaScript
Étoiles
122k
Forks
37.3k
Merge moyen
4 j 2 h
PR mergées (30 j)
283

Description

Version

24.13.0

Platform
Have tried on these:
> Linux [hostname] 6.6.87.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
> Linux [hostname] 6.12.64-87.122.amzn2023.aarch64 #1 SMP Mon Jan 12 22:17:48 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux
Subsystem

tls

What steps will reproduce the bug?

This is reproducible against mail.smtp2go.com on port 465.

Save the following as test.js and run with node test.js. After socket.end() is called, the process terminates due to an unhandled 'error' event emitted by TLSSocket during TLS teardown.

const tls = require('tls');

const socket = tls.connect(465, 'mail.smtp2go.com', () => {
  console.log('TLS connected');

  socket.on('data', d => console.log('DATA:', d.toString()));
  socket.on('end', () => console.log('end'));
  socket.on('finish', () => console.log('finish'));

  setTimeout(() => {
    socket.end();
    console.log('socket.end() called');
  }, 3000);
});

setInterval(() => console.log('alive'), 1000);
How often does it reproduce? Is there a required condition?

Every time, when the peer sends invalid (non‑TLS) after (or during?) TLS teardown.

What is the expected behavior? Why is that the expected behavior?

After tls.connect() completes and socket.end() is called, I expected the process to continue running. Instead, the process exited with an "Unhandled 'error' event".

I am not certain whether this behavior is intentional, especially in light of the recent Node 24.13.0 security release, or an edge case in the TLS socket lifecycle.

What do you see instead?
$ node test.js
TLS connected
DATA: 220 mail.smtp2go.com ESMTP Exim 4.99.1-S2G Sat, 07 Feb 2026 05:41:32 +0000

alive
alive
alive
socket.end() called
finish
node:events:486
      throw er; // Unhandled 'error' event
      ^

Error: C0686C2E10700000:error:0A0001BB:SSL routines:tls13_validate_record_header:bad record type:../deps/openssl/openssl/ssl/record/methods/tls13_meth.c:258:

Emitted 'error' event on TLSSocket instance at:
    at TLSSocket._emitTLSError (node:_tls_wrap:1050:10)
    at TLSWrap.onerror (node:_tls_wrap:491:11) {
  library: 'SSL routines',
  reason: 'bad record type',
  code: 'ERR_SSL_BAD_RECORD_TYPE'
}

Node.js v24.13.0
Additional information

To better understand the peer behavior involved, I verified the server interaction using OpenSSL.

Running the command openssl s_client -connect mail.smtp2go.com:465 -quiet followed by "QUIT" produced:

...
220 mail.smtp2go.com ESMTP Exim 4.99.1-S2G Sat, 07 Feb 2026 04:38:41 +0000
QUIT
221 mail.smtp2go.com closing connection

I also tested against a different SMTP server on port 465, which did not exhibit this behavior and it did not trigger the unhandled 'error' event in Node.

For additional context, I captured debug output using:
NODE_DEBUG=tls,net,stream node test.js

output
$ NODE_DEBUG=tls,net,stream node test.js
TLS 252547: client _init handle? true
NET 252547: pipe false undefined
NET 252547: connect: find host mail.smtp2go.com
NET 252547: connect: dns options { family: undefined, hints: 32 }
NET 252547: connect: autodetecting
TLS 252547: client initRead handle? true buffered? false
STREAM 252547: read 0
STREAM 252547: need readable false
STREAM 252547: length less than watermark true
STREAM 252547: do read
NET 252547: _read - n 65536 isConnecting? true hasHandle? true
NET 252547: _read wait for connection
NET 252547: connect/multiple: will try the following addresses [
...
]
NET 252547: connect/multiple: attempting to connect to 173.255.233.87:465 (addressType: 4)
NET 252547: connect/multiple: setting the attempt timeout to 250 ms
NET 252547: connect/multiple: connection attempt to 173.255.233.87:465 completed with status 0
NET 252547: afterConnect
TLS 252547: client _start handle? true connecting? false requestOCSP? false
NET 252547: _read - n 65536 isConnecting? false hasHandle? true
NET 252547: Socket._handle.readStart
STREAM 252547: read 0
STREAM 252547: need readable true
STREAM 252547: length less than watermark true
STREAM 252547: reading, ended or constructing false
TLS 252547: client onhandshakedone
TLS 252547: client _finishInit handle? true alpn false servername false
TLS 252547: client emit secureConnect. authorized: true
TLS connected
STREAM 252547: resume
STREAM 252547: resume true
STREAM 252547: flow
STREAM 252547: read undefined
STREAM 252547: need readable true
STREAM 252547: length less than watermark true
STREAM 252547: reading, ended or constructing false
STREAM 252547: push <Buffer ...>
DATA: 220 mail.smtp2go.com ESMTP Exim 4.99.1-S2G Sat, 07 Feb 2026 05:54:18 +0000

STREAM 252547: maybeReadMore read 0
STREAM 252547: read 0
STREAM 252547: need readable true
STREAM 252547: length less than watermark true
STREAM 252547: do read
NET 252547: _read - n 65536 isConnecting? false hasHandle? true
alive
alive
alive
NET 252547: _final: not ended, call shutdown()
socket.end() called
NET 252547: afterShutdown destroyed=false
finish
TLS 252547: client onerror [Error: C068302354700000:error:0A0001BB:SSL routines:tls13_validate_record_header:bad record type:../deps/openssl/openssl/ssl/record/methods/tls13_meth.c:258:
] {
  library: 'SSL routines',
  reason: 'bad record type',
  code: 'ERR_SSL_BAD_RECORD_TYPE'
} had? false
node:events:486
      throw er; // Unhandled 'error' event
      ^

Error: C068302354700000:error:0A0001BB:SSL routines:tls13_validate_record_header:bad record type:../deps/openssl/openssl/ssl/record/methods/tls13_meth.c:258:

Emitted 'error' event on TLSSocket instance at:
    at TLSSocket._emitTLSError (node:_tls_wrap:1050:10)
    at TLSWrap.onerror (node:_tls_wrap:491:11) {
  library: 'SSL routines',
  reason: 'bad record type',
  code: 'ERR_SSL_BAD_RECORD_TYPE'
}

Node.js v24.13.0

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par le reproducteur test.js fourni et la trace de teardown TLS, puis examinez les emplacements de node:_tls_wrap indiqués dans la pile et la sortie de débogage. Déterminez comment l’enregistrement invalide du pair est géré après socket.end() et vérifiez le comportement avec la reproduction de mail.smtp2go.com. Le travail est terminé lorsque le teardown ne provoque plus d’erreur TLSSocket non gérée et que le scénario signalé reste couvert par un test de régression.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
javascript, nodejs
Domaine
networking, security
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
50/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.