nodejs / nodejs/node

TLSSocket does not gracefully handle peer protocol violation during TLS teardown

オープン
#61,714 コメント 4 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

tls
主要言語
JavaScript
スター
122k
フォーク
37.3k
平均マージ
4日 2時間
マージ済み PR(30日)
283

説明

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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

提供された test.js の再現コードと TLS のティアダウン トレースから始め、次にスタックとデバッグ出力に示されている node:_tls_wrap の箇所を調べます。socket.end() 後にピアの無効なレコードがどのように処理されるかを確認し、mail.smtp2go.com の再現で動作を検証します。ティアダウンによって未処理の TLSSocket エラーが発生しなくなり、報告されたシナリオが引き続き回帰テストでカバーされていれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
javascript, nodejs
領域
networking, security
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
50/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。