From 2914f1e1bea975ee2c2ca164e29b767d404dc989 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Mon, 15 Dec 2025 22:49:34 +0800 Subject: [PATCH] lib: fix infinite loop in fs.realpathSync Get the link target head realpath. Then resolve realpath and link target tail. Resolve tail after resolving the link head. Fixes: https://github.com/nodejs/node/issues/60295 --- lib/fs.js | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index ace5c464b73b14..41cc8c87521032 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -2730,9 +2730,21 @@ function realpathSync(p, options) { // Walk down the path, swapping out linked path parts for their real // values // NB: p.length changes. - while (pos < p.length) { - // find the next part - const result = nextPart(p, pos); + let unresolvedTail = ''; + while (true) { + if (pos >= p.length) { + if (unresolvedTail === '') { + break; + } + + p = pathModule.resolve(p + unresolvedTail); + unresolvedTail = ''; + current = base = splitRoot(p); + pos = current.length; + continue; + } + + const result = nextPart(p + unresolvedTail, pos); previous = current; if (result === -1) { const last = StringPrototypeSlice(p, pos); @@ -2740,7 +2752,7 @@ function realpathSync(p, options) { base = previous + last; pos = p.length; } else { - current += StringPrototypeSlice(p, pos, result + 1); + current += StringPrototypeSlice(p + unresolvedTail, pos, result + 1); base = previous + StringPrototypeSlice(p, pos, result); pos = result + 1; } @@ -2761,7 +2773,6 @@ function realpathSync(p, options) { } else { // Use stats array directly to avoid creating an fs.Stats instance just // for our internal use. - const stats = binding.lstat(base, true, undefined, true /* throwIfNoEntry */); if (stats === undefined) { return; @@ -2770,6 +2781,11 @@ function realpathSync(p, options) { if (!isFileType(stats, S_IFLNK)) { knownHard.add(base); cache?.set(base, base); + if (unresolvedTail !== '') { + p = pathModule.resolve(p + unresolvedTail); + unresolvedTail = ''; + } + continue; } @@ -2786,11 +2802,18 @@ function realpathSync(p, options) { } } if (linkTarget === null) { + debugger; binding.stat(base, false, undefined, true); linkTarget = binding.readlink(base, undefined); } - resolvedLink = pathModule.resolve(previous, linkTarget); + const nextPathDelimiterIndex = nextPart(linkTarget, 0); + if (nextPathDelimiterIndex >= 1) { + unresolvedTail = StringPrototypeSlice(linkTarget, nextPathDelimiterIndex) + unresolvedTail; + linkTarget = StringPrototypeSlice(linkTarget, 0, nextPathDelimiterIndex); + } + + resolvedLink = pathModule.resolve(previous, linkTarget); cache?.set(base, resolvedLink); if (!isWindows) seenLinks.set(id, linkTarget); }