fs: fix out-of-bounds write in mkdtemp for long prefixes - #64770
Merged
nodejs-github-bot merged 1 commit intoAug 20, 2026
Conversation
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #64770 +/- ##
==========================================
- Coverage 90.14% 90.13% -0.01%
==========================================
Files 744 744
Lines 242518 242519 +1
Branches 45685 45685
==========================================
- Hits 218611 218599 -12
- Misses 15396 15406 +10
- Partials 8511 8514 +3
π New features to boost your workflow:
|
Renegade334
requested changes
Jul 27, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
frandle331-yh
force-pushed
the
fix-mkdtemp-oob-write
branch
from
July 27, 2026 20:24
7bcec4a to
a702884
Compare
This comment has been minimized.
This comment has been minimized.
Contributor
Author
|
It looks like the macOS CI failed due to a timeout in parallel/test-debugger-break, which seems unrelated to my fs.mkdtemp changes (likely a flaky test). |
Collaborator
Contributor
Author
|
It looks like the CI failed again (node-test-commit, etc.), but I don't have permission to view the Jenkins logs. |
Collaborator
Collaborator
|
Landed in 9472421 |
aduh95
pushed a commit
that referenced
this pull request
Aug 25, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
PR-URL: #64770
Reviewed-By: RenΓ© <[email protected]>
aduh95
pushed a commit
that referenced
this pull request
Aug 25, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
PR-URL: #64770
Reviewed-By: RenΓ© <[email protected]>
aduh95
pushed a commit
that referenced
this pull request
Aug 27, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
PR-URL: #64770
Reviewed-By: RenΓ© <[email protected]>
aduh95
pushed a commit
that referenced
this pull request
Sep 7, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
PR-URL: #64770
Reviewed-By: RenΓ© <[email protected]>
aduh95
pushed a commit
that referenced
this pull request
Sep 7, 2026
Mkdtemp() allocated the template buffer as `length + strlen("XXXXXX")`,
leaving no room for the terminating NUL byte. For a single-byte prefix
long enough to force the heap allocation path (length + 6 > the
stack-buffer threshold), the terminating NUL was written one byte past
the end of the buffer -- a 1-byte heap-buffer-overflow flagged by
AddressSanitizer.
Allocate room for the terminating NUL, copy the suffix, and use
SetLengthAndZeroTerminate to set the correct length and write the
terminator, following the MaybeStackBuffer paradigm used elsewhere in
this file.
Signed-off-by: frandle331-yh <[email protected]>
PR-URL: #64770
Reviewed-By: RenΓ© <[email protected]>
Arcanada
added a commit
to Arcanada-one/muneral
that referenced
this pull request
Sep 14, 2026
β¦ESM move dropped Dependabot #103 proposes node 24.20.0 -> 26.8-alpine. Measured against nodejs.org/dist/index.json: the 24.x line is the CURRENT LTS (Krypton, v24.21.0 released 2026-09-07) and every 26.x release carries `lts: false` β 26 is Current, not LTS. Moving production off a supported line is not a dependency bump, so this takes the security patch WITHIN the LTS line instead: 24.21.0 everywhere. There is a concrete reason to take it. v24.21.0 fixes an out-of-bounds write in `mkdtemp` for long prefixes (nodejs/node#64770), and this repository calls `fs.mkdtempSync` in three places, one of them the evidence path itself (mutation-harness.js gitSupplement). Five pins move together, because regenerate-derived-artefacts.yml REFUSES a mismatch between ci.yml and .github/workflows/.node-version-for-derived: apps/api/Dockerfile (x2), ci.yml, that pin file, and the root engines field. THE DEFENCE THAT WAS LOST. Two defects, both introduced by the ESM move in #113 and both invisible until a full mutation run was attempted: 1. The harness spawns jest directly and did not pass --experimental-vm-modules, so under ESM every battery failed with `SyntaxError: Cannot use import statement outside a module` and the run stopped at `baseline: FAIL`. Measured: 20 suites / 394 tests pass with the flag, 20 suites fail with 0 tests without it. `pnpm test` sets the flag; the harness never did. It went unnoticed because the only caller of a full run is regenerate-derived-artefacts.yml, gated on author == 'dependabot[bot]' β it never ran for #113. NODE_OPTIONS is appended, not assigned, so an inherited flag survives. 2. `isolatedModules: true` on the ts-jest transform removes type-checking at transform time, and nine of the 85 mutants were KILLED_BY_TYPECHECK. Measured with the flag: 79 killed by test, 6 SURVIVED β #11, #15, #16, #50, #52, #75, each a mutant the compiler used to reject and the tests do not catch. Three of the nine (#6, #13, #21) turned out to be caught by tests after all; six were not. A mutation baseline that records 0 survivors while the tree actually has 6 is worse than no baseline. The flag is removed. Its cost, measured: the local suite goes from 26s to 58s, and the CI job from about 3 minutes to an expected 3.5-4. Six mutants of the canonical-JSON and validator paths are worth thirty seconds. Measured after, from the repository root (the invocation the evidence records): 85 sites, 76 killed by test, 9 killed by compiler, 0 survived β identical to the counts on main. `--verify-structure` prints "Structurally verified 85 source-bound mutation records" with no TOOLCHAIN_MISMATCH, because tools now read node v24.21.0. tsc --noEmit 0 under both tsconfig.json and tsconfig.test.json. Closes #103 by answering it: the upgrade it asks for is refused with a measurement and the security patch it was reaching for is taken.
Arcanada
added a commit
to Arcanada-one/muneral
that referenced
this pull request
Sep 14, 2026
β¦ESM move dropped (#114) * fix: stay on the Node LTS line, and restore the mutation defence the ESM move dropped Dependabot #103 proposes node 24.20.0 -> 26.8-alpine. Measured against nodejs.org/dist/index.json: the 24.x line is the CURRENT LTS (Krypton, v24.21.0 released 2026-09-07) and every 26.x release carries `lts: false` β 26 is Current, not LTS. Moving production off a supported line is not a dependency bump, so this takes the security patch WITHIN the LTS line instead: 24.21.0 everywhere. There is a concrete reason to take it. v24.21.0 fixes an out-of-bounds write in `mkdtemp` for long prefixes (nodejs/node#64770), and this repository calls `fs.mkdtempSync` in three places, one of them the evidence path itself (mutation-harness.js gitSupplement). Five pins move together, because regenerate-derived-artefacts.yml REFUSES a mismatch between ci.yml and .github/workflows/.node-version-for-derived: apps/api/Dockerfile (x2), ci.yml, that pin file, and the root engines field. THE DEFENCE THAT WAS LOST. Two defects, both introduced by the ESM move in #113 and both invisible until a full mutation run was attempted: 1. The harness spawns jest directly and did not pass --experimental-vm-modules, so under ESM every battery failed with `SyntaxError: Cannot use import statement outside a module` and the run stopped at `baseline: FAIL`. Measured: 20 suites / 394 tests pass with the flag, 20 suites fail with 0 tests without it. `pnpm test` sets the flag; the harness never did. It went unnoticed because the only caller of a full run is regenerate-derived-artefacts.yml, gated on author == 'dependabot[bot]' β it never ran for #113. NODE_OPTIONS is appended, not assigned, so an inherited flag survives. 2. `isolatedModules: true` on the ts-jest transform removes type-checking at transform time, and nine of the 85 mutants were KILLED_BY_TYPECHECK. Measured with the flag: 79 killed by test, 6 SURVIVED β #11, #15, #16, #50, #52, #75, each a mutant the compiler used to reject and the tests do not catch. Three of the nine (#6, #13, #21) turned out to be caught by tests after all; six were not. A mutation baseline that records 0 survivors while the tree actually has 6 is worse than no baseline. The flag is removed. Its cost, measured: the local suite goes from 26s to 58s, and the CI job from about 3 minutes to an expected 3.5-4. Six mutants of the canonical-JSON and validator paths are worth thirty seconds. Measured after, from the repository root (the invocation the evidence records): 85 sites, 76 killed by test, 9 killed by compiler, 0 survived β identical to the counts on main. `--verify-structure` prints "Structurally verified 85 source-bound mutation records" with no TOOLCHAIN_MISMATCH, because tools now read node v24.21.0. tsc --noEmit 0 under both tsconfig.json and tsconfig.test.json. Closes #103 by answering it: the upgrade it asks for is refused with a measurement and the security patch it was reaching for is taken. * receipt(#103): file the ChangeAdmissionReceipt/v1 for the Node LTS pin Draft by the bundle's own verify.py in diff mode: verdict admitted, 3 entities, all three verified, zero exemptions β nothing needed exempting, which is the first receipt in this series that can say so. One finding came out of it and was fixed rather than exempted. The first draft reported v-config-schema UNDECLARED_CONFIG_KEY NODE_OPTIONS as failed, because the harness fix read process.env.NODE_OPTIONS to append to an inherited value. The verifier was right: reading it makes it a configuration key this file consumes, and declaring a test-harness runtime flag in .env.example β the application's own configuration β would have been the worse answer. The flag is now set outright, and regenerate-derived-artefacts.yml (the only caller of a full run) sets no NODE_OPTIONS, so nothing is lost. Filed in the tree with the receipt included in its own change_set, and the evidence's trackedTreeWithoutEvidence rebound after staging it β the MUN-0049 precedent. schema_check.py: CONFORMANT.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mkdtemp() allocated the template buffer as
length + strlen("XXXXXX"),leaving no room for the terminating NUL byte that snprintf() writes. For
a single-byte prefix long enough to force the heap allocation path
(length + 6 > the stack-buffer threshold), snprintf() wrote the six 'X'
characters plus its NUL one byte past the end of the buffer -- a 1-byte
heap-buffer-overflow flagged by AddressSanitizer.
Allocate one extra byte for the NUL terminator, matching the
+ 1already used by the sibling allocations in the same file.
Signed-off-by: frandle331-yh [email protected]