Title: node:vfs MemoryProvider: readdirSync({recursive:true}) crashes via circular symlinks (stack overflow) · Issue #64148 · nodejs/node · GitHub
Open Graph Title: node:vfs MemoryProvider: readdirSync({recursive:true}) crashes via circular symlinks (stack overflow) · Issue #64148 · nodejs/node
X Title: node:vfs MemoryProvider: readdirSync({recursive:true}) crashes via circular symlinks (stack overflow) · Issue #64148 · nodejs/node
Description: 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 Memory...
Open Graph Description: node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks Affected: node:vfs MemoryProvider (experimental) Node version: v27.0.0-nightly20260625c5635b82c9 Flag req...
X Description: node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks Affected: node:vfs MemoryProvider (experimental) Node version: v27.0.0-nightly20260625c5635b82c9 Flag req...
Opengraph URL: https://github.com/nodejs/node/issues/64148
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"node:vfs MemoryProvider: readdirSync({recursive:true}) crashes via circular symlinks (stack overflow)","articleBody":"## node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks\n\n**Affected:** `node:vfs` MemoryProvider (experimental) \n**Node version:** v27.0.0-nightly20260625c5635b82c9 \n**Flag required:** `--experimental-vfs`\n\n### Summary\n\n`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.\n\n### Root Cause\n\nThe 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.\n\n```js\n// walk() calls itself with no cycle detection:\nif (resolvedChild.isDirectory()) {\n const childPath = pathPosix.join(currentPath, name);\n walk(resolvedChild, childPath, childRelative); // infinite if circular symlink\n}\n```\n\n### PoC 1 — Self-referential symlink\n\n```js\nconst vfs = require('node:vfs');\nconst mem = new vfs.MemoryProvider();\nconst sandbox = vfs.create(mem);\n\nsandbox.mkdirSync('/dir');\nsandbox.symlinkSync('/dir', '/dir/loop'); // /dir/loop -\u003e /dir\n\n// Crashes: RangeError: Maximum call stack size exceeded\nsandbox.readdirSync('/', { recursive: true });\n```\n\n### PoC 2 — Mutual circular chain (a -\u003e b -\u003e a)\n\n```js\nconst vfs = require('node:vfs');\nconst mem = new vfs.MemoryProvider();\nconst sandbox = vfs.create(mem);\n\nsandbox.mkdirSync('/a');\nsandbox.mkdirSync('/b');\nsandbox.symlinkSync('/a', '/b/link_to_a');\nsandbox.symlinkSync('/b', '/a/link_to_b');\n\n// Also crashes\nsandbox.readdirSync('/', { recursive: true });\n```\n\n### PoC 3 — Async variant also affected\n\n```js\nconst vfs = require('node:vfs');\nconst mem = new vfs.MemoryProvider();\nconst sandbox = vfs.create(mem);\n\nsandbox.mkdirSync('/dir');\nsandbox.symlinkSync('/dir', '/dir/loop');\n\n// Also crashes\nsandbox.promises.readdir('/', { recursive: true })\n .catch(e =\u003e console.log(e.message)); // Maximum call stack size exceeded\n```\n\n### Stack trace (uncaught)\nnode:path:1332\n\njoin(...args) {\n\n^\n\nRangeError: Maximum call stack size exceeded\n\nat Object.join (node:path:1332:7)\n\nat #lookupEntry (node:internal/vfs/providers/memory:315:31)\n\nat walk (node:internal/vfs/providers/memory:643:43)\n\nat walk (node:internal/vfs/providers/memory:650:11)\n\nat walk (node:internal/vfs/providers/memory:650:11)\n\nat walk (node:internal/vfs/providers/memory:650:11)\n\n### Suggested Fix\n\nTrack visited entries inside `walk()` to detect cycles:\n\n```js\nconst visited = new Set();\nconst walk = (entry, currentPath, relativePath) =\u003e {\n if (visited.has(entry)) return; // break cycle\n visited.add(entry);\n // ... rest of existing walk logic\n};\n```\n\n### Impact\n\nAny 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.\n\nNote: `symlinkSync` in `MemoryProvider` does not validate circular targets, making this easy to trigger.\n\n\u003cimg width=\"1122\" height=\"1032\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/545ab293-efe1-4a9c-8c34-f7cdd041a8a3\" /\u003e\n\n\u003cimg width=\"1007\" height=\"327\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/ddfb993b-5d78-4223-881c-08524f75057d\" /\u003e\n","author":{"url":"https://github.com/jojin1709","@type":"Person","name":"jojin1709"},"datePublished":"2026-06-26T11:06:44.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/64148/node/issues/64148"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:fe6dd4ce-fedc-440c-430a-6c7eadc29aa9 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9E1A:3ED847:671ECA:98D5DD:6A4E113E |
| html-safe-nonce | e6d28742a7c5c1545f829d2731244508598366b053b710d92abeb21eafe5f087 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5RTFBOjNFRDg0Nzo2NzFFQ0E6OThENUREOjZBNEUxMTNFIiwidmlzaXRvcl9pZCI6IjI3OTYxMTU5MzMwMTcyODA4MzAiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 453d636ab2f09e162171c9d484f7d09a775b24b85e558a0f7c4c6be59e7ac1fd |
| hovercard-subject-tag | issue:4751490333 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/64148/issue_layout |
| twitter:image | https://opengraph.githubassets.com/ddb049f7e2aa961e40842e5afcb233581576998f1e63c631cac20ac4d877ccd5/nodejs/node/issues/64148 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/ddb049f7e2aa961e40842e5afcb233581576998f1e63c631cac20ac4d877ccd5/nodejs/node/issues/64148 |
| og:image:alt | node:vfs MemoryProvider: readdirSync({recursive:true}) stack overflow via circular symlinks Affected: node:vfs MemoryProvider (experimental) Node version: v27.0.0-nightly20260625c5635b82c9 Flag req... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | jojin1709 |
| hostname | github.com |
| expected-hostname | github.com |
| None | df0492960db29b4938cb72070351d6b1d0c6c0767b27ceb8394bbf4fcc0223c6 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 51470c353b8a1f52a88d3e3cc2014b17ab8cc1ce |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width