Skip to content

Commit e9c074a

Browse files
committed
Fix path resolution for symlinks
If `/a/link` is a symlink to directory `/b/c` then `/a/link/..` should be resolved to `/b/c/..` which is `/b`. Currently, we cancel out `link/..` to get `/a`. The problem is that we apply `PATH_FS.resolve` and `PATH_FS.normalize` to paths. These functions cancel `..` incorrectly. This at least partially handles the situation. `lookupPath` is modified to avoid calls that call `PATH_FS.normalize()`. We check that `mkdir`, `open`, and `stat` now work correctly.
1 parent 58889f9 commit e9c074a

File tree

4 files changed

+90
-7
lines changed

4 files changed

+90
-7
lines changed

src/library_fs.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,17 @@ FS.staticInit();
171171
// paths
172172
//
173173
lookupPath(path, opts = {}) {
174-
path = PATH_FS.resolve(path);
175-
176174
if (!path) return { path: '', node: null };
177175
opts.follow_mount ??= true
178176

177+
if (!PATH.isAbs(path)) {
178+
path = FS.cwd() + "/" + path;
179+
}
180+
179181
// limit max consecutive symlinks to 40 (SYMLOOP_MAX).
180182
linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) {
181183
// split the absolute path
182-
var parts = path.split('/').filter((p) => !!p);
184+
var parts = path.split('/').filter((p) => !!p && (p !== "."));
183185

184186
// start at the root
185187
var current = FS.root;
@@ -192,6 +194,12 @@ FS.staticInit();
192194
break;
193195
}
194196

197+
if (parts[i] === "..") {
198+
current_path = PATH.dirname(current_path);
199+
current = current.parent;
200+
continue;
201+
}
202+
195203
current = FS.lookupNode(current, parts[i]);
196204
current_path = PATH.join2(current_path, parts[i]);
197205

@@ -207,7 +215,10 @@ FS.staticInit();
207215
throw new FS.ErrnoError({{{ cDefs.ENOSYS }}});
208216
}
209217
var link = current.node_ops.readlink(current);
210-
path = PATH_FS.resolve(PATH.dirname(current_path), link, ...parts.slice(i + 1));
218+
if (!PATH.isAbs(link)) {
219+
link = PATH.dirname(current_path) + "/" + link;
220+
}
221+
path = link + "/" + parts.slice(i + 1).join("/");
211222
continue linkloop;
212223
}
213224
}
@@ -1033,7 +1044,6 @@ FS.staticInit();
10331044
if (typeof path == 'object') {
10341045
node = path;
10351046
} else {
1036-
path = PATH.normalize(path);
10371047
try {
10381048
var lookup = FS.lookupPath(path, {
10391049
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}})

src/library_syscall.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ var SyscallsLibrary = {
3838
}
3939
return dir;
4040
}
41-
return PATH.join2(dir, path);
41+
if (!dir.endsWith("/")) {
42+
dir += "/"
43+
}
44+
return dir + path;
4245
},
4346

4447
doStat(func, path, buf) {
@@ -826,7 +829,6 @@ var SyscallsLibrary = {
826829
path = SYSCALLS.calculateAt(dirfd, path);
827830
// remove a trailing slash, if one - /a/b/ has basename of '', but
828831
// we want to create b in the context of this function
829-
path = PATH.normalize(path);
830832
if (path[path.length-1] === '/') path = path.substr(0, path.length-1);
831833
FS.mkdir(path, mode, 0);
832834
return 0;

test/fs/test_symlink_resolution.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <unistd.h>
2+
#include <fcntl.h>
3+
#include <fcntl.h>
4+
#include <unistd.h>
5+
#include <sys/stat.h>
6+
#include <assert.h>
7+
#include <stdio.h>
8+
#include <string.h>
9+
#if defined(__EMSCRIPTEN__)
10+
#include "emscripten.h"
11+
#endif
12+
13+
void makedir(const char *dir) {
14+
int rtn = mkdir(dir, 0777);
15+
assert(rtn == 0);
16+
}
17+
18+
void changedir(const char *dir) {
19+
int rtn = chdir(dir);
20+
assert(rtn == 0);
21+
}
22+
23+
static void create_file(const char *path) {
24+
printf("creating: %s\n", path);
25+
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0777);
26+
assert(fd >= 0);
27+
28+
close(fd);
29+
}
30+
31+
void setup() {
32+
#if defined(__EMSCRIPTEN__) && defined(NODEFS)
33+
makedir("working");
34+
EM_ASM(FS.mount(NODEFS, { root: '.' }, 'working'));
35+
changedir("working");
36+
#endif
37+
makedir("a");
38+
makedir("b");
39+
makedir("b/c");
40+
symlink("../b/c", "a/link");
41+
}
42+
43+
44+
int main() {
45+
setup();
46+
create_file("a/link/../x.txt");
47+
struct stat statBuf;
48+
assert(stat("a/link/../x.txt", &statBuf) == 0);
49+
assert(stat("b/x.txt", &statBuf) == 0);
50+
makedir("a/link/../d");
51+
assert(stat("a/link/../d", &statBuf) == 0);
52+
assert(stat("b/d", &statBuf) == 0);
53+
54+
assert(truncate("a/link/../x.txt", 0) == 0);
55+
assert(chmod("a/link/../x.txt", 0777) == 0);
56+
printf("success\n");
57+
}

test/test_core.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5925,6 +5925,20 @@ def test_fs_64bit(self):
59255925
self.set_setting('FORCE_FILESYSTEM')
59265926
self.do_runf('fs/test_64bit.c', 'success')
59275927

5928+
@requires_node
5929+
@parameterized({
5930+
'': ([],),
5931+
'nodefs': (['-DNODEFS', '-lnodefs.js'],),
5932+
'noderawfs': (['-sNODERAWFS'],),
5933+
})
5934+
def test_fs_symlink_resolution(self, args):
5935+
nodefs = '-DNODEFS' in args or '-sNODERAWFS' in args
5936+
if self.get_setting('WASMFS'):
5937+
if nodefs:
5938+
self.skipTest('NODEFS in WasmFS')
5939+
self.set_setting('FORCE_FILESYSTEM')
5940+
self.do_runf('fs/test_symlink_resolution.c', 'success', emcc_args=args)
5941+
59285942
def test_sigalrm(self):
59295943
self.do_runf('test_sigalrm.c', 'Received alarm!')
59305944
self.set_setting('EXIT_RUNTIME')

0 commit comments

Comments
 (0)