[darwin] pty_posix_spawn leaks the pty master+slave when posix_spawn fails, and one ptmx fd on every successful spawn
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 68/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- cpp, macos
調査の方向性
src/unix/pty.cc の pty_posix_spawn から始め、その low-fd プロローグ、エラーリターン、done: パス、PtyFork の失敗パスを調べます。提供された oversized-argv スクリプトで失敗する spawn と成功する spawn を再現し、その後 lsof で master、slave、low_fds のすべてのディスクリプタが閉じられていること、および既存の spawn の動作が引き続き機能することを確認します。
索引モデルが issue の本文から書いたものです。
説明
Version: node-pty 1.1.0, macOS (arm64, also reproduced on macOS 26), Node 22 / Electron 42.
On darwin, pty_posix_spawn (src/unix/pty.cc) allocates a pty before spawning and never releases it on any error path — and its low-fd prologue leaks one fd even when everything succeeds.
Leak 1 — failed spawn leaks the master and the slave (2 pty devices per failure)
*master = posix_openpt(O_RDWR)(pty.cc:707) andslave = open(slave_pty_name, …)(pty.cc:725) have no matching parent-sideclose()anywhere: every earlyreturn(L709, 714, 722, 727, 733, 740) and thedone:path (L777) leave both fds open. Theposix_spawn_file_actions_addclosecalls at L749-750 apply only to the child.PtyForkthen throws (pty.cc:372-374,"posix_spawnp failed.") without cleanup, and since the JS ctor never receivesterm.fd, the caller cannot close anything either.
Leak 2 — successful spawn leaks one ptmx fd (off-by-one)
The low-fd prologue (L694-701) opens ptys until one lands at fd >= STDERR_FILENO, then breaks. Cleanup is:
for (; count > 0; count--) close(low_fds[count]);
In the common case the loop breaks with count == 0, so the body never runs and low_fds[0] leaks on every spawn. It also skips index 0 whenever count > 0, and reads low_fds[3] out of bounds if the prologue loop completes without breaking.
Reproduction
Forcing a real posix_spawn failure needs more than a nonexistent binary (on macOS argv[0] is the spawn-helper, which exists — the failure then happens in the child). E2BIG via an oversized argv works:
const pty = require('node-pty')
const huge = 'x'.repeat(3 * 1024 * 1024)
for (let i = 0; i < 25; i++) {
try { pty.spawn('/bin/echo', [huge], { cwd: '/tmp' }) } catch (e) { /* posix_spawnp failed. */ }
}
// lsof -p <pid>: 50 /dev/ptmx + 25 /dev/ttysNNN still open
Measured on macOS: 25 failed spawns → +50 /dev/ptmx fds, +25 /dev/ttys* fds, system-wide device count 84 → 134 (2 devices per failure). 25 successful spawns, each fully exited and destroy()ed, still left 25 /dev/ptmx fds open (1 device each).
Impact
macOS caps pty devices system-wide at kern.tty.ptmx_max (default 511). Because failures leak two devices each, exhaustion is self-amplifying: at the ceiling every spawn fails, every failure consumes two more devices, and the process never recovers. An Electron app of ours accumulated 479 open masters in a 31-minute session against 28 real terminals, after which every spawn on the machine failed until the process exited.
Suggested fix
- In
pty_posix_spawn, close*master(andslaveonce opened) on every error path, setting*master = -1; closeslavein the parent unconditionally afterposix_spawn, and close*mastertoo when*err != 0. - Track how many
low_fdswere actually opened and close all of them, e.g.size_t opened = count < 3 ? count + 1 : 3; for (size_t i = 0; i < opened; i++) if (low_fds[i] >= 0) close(low_fds[i]); - Ideally include the
posix_spawnerrno in the thrown message —"posix_spawnp failed."currently discards the one datum (EMFILE vs EAGAIN vs ENOENT) that would let applications diagnose this.
- 主要言語
- TypeScript
- スター
- 2k
- フォーク
- 337
- 平均マージ
- 13時間 17分
- マージ済み PR(30日)
- 5
環境構築
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/node-pty のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
microsoft/node-pty#951 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
microsoft/node-pty の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
siyuan-note/siyuan#20040 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
RunestoneInteractive/rs#1574 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
remotion-dev/remotion#11901 ·
メンテナーはふだん 1 日以内に返信
-
Poll constructor throws for an uncached channel while resolving a message context-menu interactionオープンbug need repro packages:discord.js
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
discordjs/discord.js#11645 ·
メンテナーはふだん 3 日以内に返信
-
🐞 bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Sitecore/content-sdk#641 ·
メンテナーはふだん 2 日以内に返信