node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks
Affected:node:vfs MemoryProvider (experimental)
Node version: v27.0.0-nightly20260625c5635b82c9
Flag required:--experimental-vfs
Summary
MemoryProvider#readdirSync with recursive: true crashes the Node.js process with a stack overflow (RangeError: Maximum call stack size exceeded) when the virtual filesystem contains circular symlinks. Both sync and async variants are affected. The process exits with code 1 when the error is uncaught.
Root Cause
The internal walk() function inside #readdirRecursive (memory.js ~line 643) follows symlinks to directories recursively but does not track visited paths or entries. This completely bypasses the kMaxSymlinkDepth = 40 guard that exists in #lookupEntry, causing unbounded recursion until the call stack is exhausted.
// walk() calls itself with no cycle detection:if(resolvedChild.isDirectory()){constchildPath=pathPosix.join(currentPath,name);walk(resolvedChild,childPath,childRelative);// infinite if circular symlink}PoC 1 — Self-referential symlink
constvfs=require('node:vfs');constmem=newvfs.MemoryProvider();constsandbox=vfs.create(mem);sandbox.mkdirSync('/dir');sandbox.symlinkSync('/dir','/dir/loop');// /dir/loop -> /dir// Crashes: RangeError: Maximum call stack size exceededsandbox.readdirSync('/',{recursive: true});PoC 2 — Mutual circular chain (a -> b -> a)
constvfs=require('node:vfs');constmem=newvfs.MemoryProvider();constsandbox=vfs.create(mem);sandbox.mkdirSync('/a');sandbox.mkdirSync('/b');sandbox.symlinkSync('/a','/b/link_to_a');sandbox.symlinkSync('/b','/a/link_to_b');// Also crashessandbox.readdirSync('/',{recursive: true});PoC 3 — Async variant also affected
constvfs=require('node:vfs');constmem=newvfs.MemoryProvider();constsandbox=vfs.create(mem);sandbox.mkdirSync('/dir');sandbox.symlinkSync('/dir','/dir/loop');// Also crashessandbox.promises.readdir('/',{recursive: true}).catch(e=>console.log(e.message));// Maximum call stack size exceededStack trace (uncaught)
node:path:1332
join(...args) {
^
RangeError: Maximum call stack size exceeded
at Object.join (node:path:1332:7)
at #lookupEntry (node:internal/vfs/providers/memory:315:31)
at walk (node:internal/vfs/providers/memory:643:43)
at walk (node:internal/vfs/providers/memory:650:11)
at walk (node:internal/vfs/providers/memory:650:11)
at walk (node:internal/vfs/providers/memory:650:11)
Suggested Fix
Track visited entries inside walk() to detect cycles:
constvisited=newSet();constwalk=(entry,currentPath,relativePath)=>{if(visited.has(entry))return;// break cyclevisited.add(entry);// ... rest of existing walk logic};Impact
Any application calling readdirSync or promises.readdir with recursive: true on a MemoryProvider that contains circular symlinks will crash the Node.js process. In server-side contexts where the VFS is user-influenced, this enables a Denial of Service. The existing kMaxSymlinkDepth guard does not protect against this code path.
Note: symlinkSync in MemoryProvider does not validate circular targets, making this easy to trigger.


node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks
Affected:
node:vfsMemoryProvider (experimental)Node version: v27.0.0-nightly20260625c5635b82c9
Flag required:
--experimental-vfsSummary
MemoryProvider#readdirSyncwithrecursive: truecrashes the Node.js process with a stack overflow (RangeError: Maximum call stack size exceeded) when the virtual filesystem contains circular symlinks. Both sync and async variants are affected. The process exits with code 1 when the error is uncaught.Root Cause
The internal
walk()function inside#readdirRecursive(memory.js ~line 643) follows symlinks to directories recursively but does not track visited paths or entries. This completely bypasses thekMaxSymlinkDepth = 40guard that exists in#lookupEntry, causing unbounded recursion until the call stack is exhausted.PoC 1 — Self-referential symlink
PoC 2 — Mutual circular chain (a -> b -> a)
PoC 3 — Async variant also affected
Stack trace (uncaught)
node:path:1332
join(...args) {
^
RangeError: Maximum call stack size exceeded
at Object.join (node:path:1332:7)
at #lookupEntry (node:internal/vfs/providers/memory:315:31)
at walk (node:internal/vfs/providers/memory:643:43)
at walk (node:internal/vfs/providers/memory:650:11)
at walk (node:internal/vfs/providers/memory:650:11)
at walk (node:internal/vfs/providers/memory:650:11)
Suggested Fix
Track visited entries inside
walk()to detect cycles:Impact
Any application calling
readdirSyncorpromises.readdirwithrecursive: trueon aMemoryProviderthat contains circular symlinks will crash the Node.js process. In server-side contexts where the VFS is user-influenced, this enables a Denial of Service. The existingkMaxSymlinkDepthguard does not protect against this code path.Note:
symlinkSyncinMemoryProviderdoes not validate circular targets, making this easy to trigger.