Repository navigation
fs: resolve symlinks before parent components - #65457
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #65457 +/- ##
==========================================
- Coverage 90.69% 90.68% -0.02%
==========================================
Files 797 797
Lines 279611 279630 +19
Branches 53787 53800 +13
==========================================
- Hits 253603 253584 -19
- Misses 16295 16335 +40
+ Partials 9713 9711 -2
🚀 New features to boost your workflow:
|
|
Hi @nodejs/fs, would appreciate any reviews |
There was a problem hiding this comment.
I think this change may introduce a permission-model regression in the JS fs.realpathSync() walk.
Repro (current main, run with --permission --allow-fs-read=):
mkdir -p jail secret && echo x > secret/marker && ln -s "$PWD/secret" jail/link
node --permission --allow-fs-read="$PWD/jail" -p "fs.realpathSync('$PWD/jail/link/..')"
On main this returns $PWD/jail, which is inside the grant. With this PR it returns $PWD, which is outside the grant, and does not throw.
toAbsolutePathPreservingDots() keeps .. in the path so that it is applied after the symlink is resolved. The walk then leaves the granted tree, and the lstat/stat/readlink calls made during the walk are not checked against the grant. The permission check on the input string sees the lexical path, which is inside the grant, so it passes.
Expected behaviour: ERR_ACCESS_DENIED, which is what the async fs.realpath() returns on the current main.
Suggestion: check each base against the fs.read grant before it is lstat'd, stat'd, or readlink'd during the walk, and add a regression test under test/parallel/ that covers link/.. with --permission.
|
@jakecastelli Thanks for the review! I can see there are 2 concerns here
For 1, I think this is a known imperfection. On the current main line, with As for 2.
The js version,
So Hope that makes sense. |
jakecastelli
left a comment
There was a problem hiding this comment.
yes, my bad, it should be "$PWD", I made a mistake
The JavaScript implementation of `fs.realpath()` and `fs.realpathSync()` joined a symlink target with the rest of the path through `path.resolve()`, which collapses `..` lexically before the preceding components have been resolved. A link such as `d -> c/../d`, where `c` is itself a symlink, was therefore rewritten back to `d` and the walk never terminated. Keep `.` and `..` in the path while walking it, and apply `..` to the already resolved parent, matching `realpath(3)` and `fs.realpathSync.native()`. Only normalize the final result, and skip caching link targets containing `..`, whose resolution depends on the path walked before them. Thsi is a POSIX only fix. Windows is unaffected. Signed-off-by: jazelly <xzha4350@gmail.com>
This comment was marked as outdated.
This comment was marked as outdated.
|
Landed in 40aad9e |
fs.realpath()andfs.realpathSync()resolved symlink targets withpath.resolve(), which collapses
..before the preceding symlink isresolved. A link like
d -> c/../dwas turned back intod, causingan infinite loop.
Fixed this by resolving
..against the already resolved parent instead.This matches how native implements realpath:
..is applied to the already-resolved prefix, and symlink targets arespliced into the remaining path without lexical normalization.
Fixes: #60295
Signed-off-by: jazelly xzha4350@gmail.com