test_runner: parent runner still crashes on child stdout bytes that mimic an event frame (survives the #64706 fix)
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 68/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- javascript
- Bereich
- testing-qa
Rechercherichtung
Start in lib/internal/test_runner/runner.js, tracing FileTest.parseMessage into #processRawBuffer, and run the deterministic poison.test.mjs reproduction with node --test. The fix is done when arbitrary child stdout, including bytes resembling a frame, no longer crashes the parent and subsequent test frames are still processed; verify the control and reproduction cases.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
- Version: v24.21.0 (this release includes the
>>> 0fix from #64706); also verified on v26.4.0 and v22.23.2 - Platform: macOS 26.6.2 (25G83), arm64
- Subsystem: test_runner
Current behavior
node --test crashes the parent runner (exit code 1; the failing component is the parent's stdout frame parser, not any test assertion) with:
Error: Unable to deserialize cloned data due to invalid or unsupported version.
at #processRawBuffer (node:internal/test_runner/runner:497:20)
at FileTest.parseMessage (node:internal/test_runner/runner:404:29)
at Socket.<anonymous> (node:internal/test_runner/runner:552:15)
How often: deterministic — 10/10 runs on every version below. Control (same file minus the stdout write): 10/10 green on every version.
| node | has #64706 >>> 0 fix |
repro | control |
|---|---|---|---|
| v24.21.0 | yes (verified in shipped runner.js source) |
10/10 crash | 10/10 pass |
| v26.4.0 | no (compiled-in source still has the signed read) | 10/10 crash | 10/10 pass |
| v22.23.2 | no (v22 backport tracked in #65934) | 10/10 crash | 10/10 pass |
Expected behavior
Anything a test (or code it imports) writes to stdout — text, banners, progress bars, QR codes, binary — should not be able to take down the parent runner. At worst it should degrade to test:stdout output or a per-file diagnostic, not abort the whole run with an error pointing at runner internals.
Steps to reproduce
Save as poison.test.mjs, then run node --test poison.test.mjs:
import { test } from 'node:test'
test('writes bytes that mimic a frame header', () => {
const poison = Buffer.from([
0xFF, 0x0F, // frame magic (v8 serdes header tag)
0x00, 0x00, 0x00, 0x08, // fake payload size = 8
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // "ABCDEFGH" payload
])
process.stdout.write(poison)
})
test('subsequent test whose real frames must also be parsed', () => {
// trivial
})
Removing the process.stdout.write(poison) line makes it 10/10 green on all three versions.
Analysis
In lib/internal/test_runner/runner.js:
runTestFilespawns each test file withstdio: ['pipe', 'pipe', 'pipe']and pipes the child's raw stdout intoFileTest.parseMessage → #processRawBuffer.- The child's stdout therefore carries two unrelated producers on one pipe: the runner's own length-prefixed frames (
FF 0F | uint32 BE size | v8 payload) and arbitrary application output. There is no isolation or escape mechanism for user bytes. - The first loop in
#processRawBufferskips bytes up to the nextFF 0Fmagic and reports them astest:stdout. But after consuming a frame, the second loop continues without re-checking the magic — it trusts the next 4 bytes as a size and hands the slice straight toreadHeader()/readValue(), with no try/catch anywhere on this path (the escaping stack above is the proof). - #64706 hardened the size read itself (
>>> 0), which fixes the negative/garbage-size case. What remains — and what the repro above hits on v24.21.0 — is the rest of the path: any stdout bytes that begin withFF 0Fplus a plausible small size pass every guard,readHeader()throws on the payload, and the exception takes down the entire run. - Binary output containing
FF 0Fat a buffer head reaches exactly this state; plain text mostly takes the negative-size branch instead, which pre-#64706 silently drops buffered frames (see #65934) rather than crashing.
Real-world impact: one test's stdout can abort the whole node --test run with an internals-only error, which is hard for users to act on. We hit this as CI flakiness: a test printed a large terminal-QR block graphic and CI failed intermittently with the identical error; removing that output made the failures disappear.
Possible directions (not exhaustive): re-validate the FF 0F magic in the second loop before trusting the size; treat readHeader() / readValue() failure as lost/stray-output diagnostics for that file instead of a fatal exception; or an escaping mechanism so user stdout is never frame-parsed.
Related
- #64061 — signed size-read crash (same error/stack); fixed by #64706. This issue is about what that fix does not cover.
- #65934 — pending v22.x backport of #64706.
Secondary repro (plain text, intermittent)
The deterministic file above is a minimized stand-in. The organic trigger is chunk-boundary dependent — text landing in the same parse buffer as the end of a frame gets its bytes read as a size. This file matches our real-world case (QR-style block text) and crashes only intermittently:
import { test } from 'node:test'
const tick = () => new Promise((r) => setTimeout(r, 0))
test('passes some ordinary assertions', async () => { await tick() })
test('prints a large block of text like a QR code / ASCII banner', async () => {
for (let i = 0; i < 54; i++) {
console.log('█▀▄ '.repeat(25))
await tick() // let runner event frames flush between text writes
}
})
test('keeps running tests after the noisy one', async () => { await tick() })
- Vorherrschende Sprache
- JavaScript
- Sterne
- 122k
- Forks
- 38.4k
- Ø Merge
- 4 T. 3 Std.
- Gemergte PRs (30 T.)
- 275
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus nodejs/node
-
build / doc: missing platform and toolchain info for `linux-x64-musl`Evtl. vergeben Ein verknüpfter Pull Request ist offen oder bereits gemergt. Offenalpine build doc
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Maintainer antworten meist innerhalb von 1 Tag
-
[Docs] `process.loadEnvFile()` does not document behaviour when variables already existEvtl. vergeben @Sepandard hat das vor 8 Tagen übernommen. Offendoc
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
Maintainer antworten meist innerhalb von 1 Tag
-
Stream.prototype.forEach will block in first promise in queue before read more chunkEvtl. vergeben @mmustafasenoglu hat das vor 9 Tagen übernommen. Offendoc
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
Maintainer antworten meist innerhalb von 1 Tag
-
build
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
nodejs/node#66076 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
`TextEncoder.encodeInto()` underfills the destination for some non-ASCII textEvtl. vergeben @XadillaX hat das vor 23 Tagen übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
nodejs/node#65994 · 2 Kommentare · 2 Reaktionen ·
Maintainer antworten meist innerhalb von 1 Tag
Ähnliche Issues
-
NoCode.vue, Task.vue: replace explicit `any` with real typesEvtl. vergeben @prayas-bit hat das heute übernommen. Offenarea/frontend good first issue kind/cooldown
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
kestra-io/kestra#20347 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
ubuntu/gnome-shell-extension-appindicator#676 · 1 Kommentar ·
-
first-Agent inherits Docker TLS settings for its recorded Unix socketEvtl. vergeben @ericcaiwx-star hat das heute übernommen. Offenclawsweeper:bulk-filed clawsweeper:linked-pr-open clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:ux-friction issue-rating: 🦞 diamond lobster P2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
openclaw/openclaw-enterprise#1337 · 1 Kommentar · 1 Reaktion ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
Maintainer antworten meist innerhalb von 1 Tag
-
good first issue quality
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 86/100
StudentSuite/awesome-student-resources#554 ·
Maintainer antworten meist innerhalb von 1 Tag