Skip to content

Commit b632812

Browse files
authored
[nodefs] Don't try to resolve absolute symlinks outside of the VFS (#21805)
This PR reverts the behaviour that was added in #3277 and suggested in #3222.
1 parent 3cd6861 commit b632812

File tree

6 files changed

+209
-72
lines changed

6 files changed

+209
-72
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ to browse the changes between the tags.
1818

1919
See docs/process.md for more on how version tagging works.
2020

21+
3.1.61 - 05/08/24
22+
-----------------
23+
- Under nodefs, symbolic links to files outside of mount locations no longer work.
24+
This reverts the previous behaviour added in #3277. (#21805)
25+
2126
3.1.60 (in development)
2227
-----------------------
2328
- The `EXPORTED_FUNCTIONS` list can now include JS library symbols even if they

src/library_nodefs.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,7 @@ addToLibrary({
234234
readlink(node) {
235235
var path = NODEFS.realPath(node);
236236
try {
237-
path = fs.readlinkSync(path);
238-
path = nodePath.relative(nodePath.resolve(node.mount.opts.root), path);
239-
return path;
237+
return fs.readlinkSync(path);
240238
} catch (e) {
241239
if (!e.code) throw e;
242240
// node under windows can return code 'UNKNOWN' here:

test/unistd/links.c

Lines changed: 141 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,70 @@
1717
#include <emscripten.h>
1818
#endif
1919

20-
void setup() {
21-
int rtn = mkdir("working", 0777);
22-
assert(rtn == 0);
23-
#if defined(__EMSCRIPTEN__) && defined(NODEFS)
24-
EM_ASM(FS.mount(NODEFS, { root: '.' }, 'working'));
25-
#endif
26-
rtn = chdir("working");
20+
void makedir(const char *dir) {
21+
int rtn = mkdir(dir, 0777);
2722
assert(rtn == 0);
28-
symlink("../test/../there!", "link");
29-
int fd = open("file", O_RDWR | O_CREAT, 0777);
23+
}
24+
25+
void makefile(const char *file, const char *content) {
26+
int fd = open(file, O_RDWR | O_CREAT, 0777);
3027
assert(fd >= 0);
31-
rtn = write(fd, "test", 5);
32-
assert(rtn == 5);
28+
int rtn = write(fd, content, strlen(content));
29+
assert(rtn == strlen(content));
3330
close(fd);
34-
rtn = mkdir("folder", 0777);
31+
}
32+
33+
void makelink(const char *link, const char *path) {
34+
int rtn = symlink(link, path);
3535
assert(rtn == 0);
3636
}
3737

38-
int main() {
39-
setup();
38+
void changedir(const char *dir) {
39+
int rtn = chdir(dir);
40+
assert(rtn == 0);
41+
}
4042

41-
char* files[] = {"link", "file", "folder"};
42-
char buffer[256] = {0};
43-
int ret;
43+
void setup() {
44+
makedir("working");
45+
#if defined(__EMSCRIPTEN__) && defined(NODEFS)
46+
EM_ASM(FS.mount(NODEFS, { root: '.' }, 'working'));
47+
#endif
48+
changedir("working");
49+
makelink("../test/../there!", "link");
50+
makefile("file", "test");
51+
makedir("directory");
52+
makedir("directory/subdirectory");
53+
makefile("directory/subdirectory/file", "Subdirectory");
54+
55+
makedir("relative");
56+
makedir("relative/subrelative");
57+
makefile("relative/file", "Relative");
58+
makefile("relative/subrelative/file", "Subrelative");
59+
makelink("../relative/file", "directory/relative");
60+
makelink("../../relative/subrelative/file", "directory/subdirectory/subrelative");
61+
makelink("directory/subdirectory/file", "subdirectoryrelative");
62+
63+
makedir("absolute");
64+
makedir("absolute/subabsolute");
65+
makefile("absolute/file", "Absolute");
66+
makefile("absolute/subabsolute/file", "Subabsolute");
67+
makelink("/working/absolute/file", "/working/directory/absolute");
68+
makelink("/working/absolute/subabsolute/file", "/working/directory/subdirectory/subabsolute");
69+
makelink("/working/directory/subdirectory/file", "/working/subdirectoryabsolute");
70+
}
71+
72+
void test_reading_existing_symlinks() {
73+
char* files[] = {"link", "file", "directory"};
4474

4575
for (int i = 0; i < sizeof files / sizeof files[0]; i++) {
46-
printf("readlink(%s)\n", files[i]);
47-
ret = readlink(files[i], buffer, 256);
48-
printf("errno: %s\n", strerror(errno));
49-
if (ret < 0) {
50-
printf("not a link\n\n");
76+
char buffer[256] = {0};
77+
int rtn = readlink(files[i], buffer, 256);
78+
printf("readlink: '%s'\n", files[i]);
79+
printf("errno: %s\n\n", strerror(errno));
80+
if (rtn < 0) {
5181
continue;
5282
}
83+
5384
// WASMFS behaves the same as Linux (and as best as I can tell, the spec),
5485
// seeing the symlink as a string. The old JS FS instead normalizes it and
5586
// returns something modified.
@@ -59,56 +90,125 @@ int main() {
5990
#else
6091
assert(strcmp(buffer, "/there!") == 0);
6192
#endif
62-
assert(strlen(buffer) == ret);
93+
assert(strlen(buffer) == rtn);
6394
errno = 0;
64-
printf("\n");
6595
}
96+
}
6697

67-
printf("symlink/overwrite\n");
68-
ret = symlink("new-nonexistent-path", "link");
69-
assert(ret == -1);
98+
void test_overwriting_symlink() {
99+
int rtn = symlink("new-nonexistent-path", "link");
100+
assert(rtn == -1);
70101
assert(errno == EEXIST);
71102
errno = 0;
103+
}
72104

73-
printf("\nsymlink/normal\n");
74-
ret = symlink("new-nonexistent-path", "folder/link");
75-
assert(ret == 0);
105+
void test_creating_symlink() {
106+
int rtn = symlink("new-nonexistent-path", "directory/link");
107+
assert(rtn == 0);
76108
assert(errno == 0);
77109
errno = 0;
78110

79-
printf("\nreadlink(created link)\n");
80-
ret = readlink("folder/link", buffer, 256);
111+
char buffer[256] = {0};
112+
rtn = readlink("directory/link", buffer, 256);
81113
assert(errno == 0);
82114
#if !defined(__EMSCRIPTEN__) || defined(WASMFS)
83115
assert(strcmp(buffer, "new-nonexistent-path") == 0);
84116
#else
85-
assert(strcmp(buffer, "/working/folder/new-nonexistent-path") == 0);
117+
assert(strcmp(buffer, "/working/directory/new-nonexistent-path") == 0);
86118
#endif
87-
assert(strlen(buffer) == ret);
119+
assert(strlen(buffer) == rtn);
88120
errno = 0;
121+
}
89122

123+
void test_reading_shortened_symlink() {
124+
char buffer[256] = {0};
125+
readlink("directory/link", buffer, 256);
90126
buffer[0] = buffer[1] = buffer[2] = buffer[3] = buffer[4] = buffer[5] = '*';
91-
printf("\nreadlink(short buffer)\n");
92-
ret = readlink("link", buffer, 4);
127+
int rtn = readlink("link", buffer, 4);
93128
assert(errno == 0);
94-
assert(ret == 4);
129+
assert(rtn == 4);
95130
#if !defined(__EMSCRIPTEN__) || defined(WASMFS)
96131
assert(strcmp(buffer, "../t**nexistent-path") == 0);
97132
#else
98-
assert(strcmp(buffer, "/the**ng/folder/new-nonexistent-path") == 0);
133+
assert(strcmp(buffer, "/the**ng/directory/new-nonexistent-path") == 0);
99134
#endif
100135
errno = 0;
136+
}
101137

138+
void test_noticing_loop_in_symlink() {
102139
// FS.lookupPath should notice the symlink loop and return ELOOP, not go into
103140
// an infinite recurse.
104141
//
105142
// This test doesn't work in wasmfs -- probably because access sees the
106143
// symlink and returns true without bothering to chase the symlink
107-
symlink("./linkX/inside", "./linkX");
108-
ret = access("linkX", F_OK);
109-
assert(ret == -1);
144+
symlink("./loop-link/inside", "./loop-link");
145+
int rtn = access("loop-link", F_OK);
146+
assert(rtn == -1);
110147
assert(errno == ELOOP);
111148
errno = 0;
149+
}
150+
151+
152+
void test_relative_path_symlinks() {
153+
char* parents[] = {
154+
"/working/directory/",
155+
"/working/directory/subdirectory/",
156+
"/working/"
157+
};
112158

159+
char* links[] = {
160+
"relative",
161+
"subrelative",
162+
"subdirectoryrelative",
163+
};
164+
165+
for (int i = 0; i < sizeof links / sizeof links[0]; i++) {
166+
int rtn = chdir(parents[i]);
167+
assert(rtn == 0);
168+
char symlink[256] = {0};
169+
strcat(strcpy(symlink, parents[i]), links[i]);
170+
printf("symlink: '%s'\n", symlink);
171+
char buf[256] = {0};
172+
rtn = readlink(links[i], buf, 256);
173+
FILE *fd = fopen(buf, "r");
174+
assert(fd);
175+
char buffer[13] = {0};
176+
rtn = fread(buffer, 1, 13, fd);
177+
assert(rtn <= 13);
178+
printf("buffer: '%s'\n\n", buffer);
179+
fclose(fd);
180+
}
181+
}
182+
183+
void test_absolute_path_symlinks() {
184+
char* links[] = {
185+
"/working/directory/absolute",
186+
"/working/directory/subdirectory/subabsolute",
187+
"/working/subdirectoryabsolute"
188+
};
189+
190+
for (int i = 0; i < sizeof links / sizeof links[0]; i++) {
191+
printf("symlink: '%s'\n", links[i]);
192+
char buf[256] = {0};
193+
readlink(links[i], buf, 256);
194+
FILE *fd = fopen(buf, "r");
195+
assert(fd);
196+
char buffer[13] = {0};
197+
int rtn = fread(buffer, 1, 13, fd);
198+
assert(rtn <= 13);
199+
printf("buffer: '%s'\n\n", buffer);
200+
fclose(fd);
201+
}
202+
}
203+
204+
int main() {
205+
setup();
206+
test_reading_existing_symlinks();
207+
test_overwriting_symlink();
208+
test_creating_symlink();
209+
test_reading_shortened_symlink();
210+
test_noticing_loop_in_symlink();
211+
test_relative_path_symlinks();
212+
test_absolute_path_symlinks();
113213
return 0;
114214
}

test/unistd/links.out

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
readlink(link)
1+
readlink: 'link'
22
errno: No error information
33

4-
readlink(file)
4+
readlink: 'file'
55
errno: Invalid argument
6-
not a link
76

8-
readlink(folder)
7+
readlink: 'directory'
98
errno: Invalid argument
10-
not a link
119

12-
symlink/overwrite
10+
symlink: '/working/directory/relative'
11+
buffer: 'Relative'
1312

14-
symlink/normal
13+
symlink: '/working/directory/subdirectory/subrelative'
14+
buffer: 'Subrelative'
1515

16-
readlink(created link)
16+
symlink: '/working/subdirectoryrelative'
17+
buffer: 'Subdirectory'
1718

18-
readlink(short buffer)
19+
symlink: '/working/directory/absolute'
20+
buffer: 'Absolute'
21+
22+
symlink: '/working/directory/subdirectory/subabsolute'
23+
buffer: 'Subabsolute'
24+
25+
symlink: '/working/subdirectoryabsolute'
26+
buffer: 'Subdirectory'

test/unistd/symlink_on_nodefs.c

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,60 @@
1717

1818
void setup() {
1919
EM_ASM(
20-
fs.mkdirSync('new-directory', '0777');
21-
fs.writeFileSync('new-directory/test', 'Link it');
22-
fs.symlinkSync(fs.realpathSync('new-directory'), 'symlink');
20+
fs.mkdirSync('directory', '0777');
21+
fs.writeFileSync('directory/test', 'Link it');
22+
fs.symlinkSync('/working/directory', 'inside-symlink');
23+
fs.symlinkSync(fs.realpathSync('directory'), 'outside-symlink');
2324

2425
FS.mkdir('working');
2526
FS.mount(NODEFS, { root: '.' }, 'working');
2627

27-
FS.mkdir('direct-link');
28-
FS.mount(NODEFS, { root: 'symlink' }, 'direct-link');
28+
FS.mkdir('mount-link');
29+
FS.mount(NODEFS, { root: 'inside-symlink' }, 'mount-link');
2930
);
3031
}
3132

32-
int main() {
33-
setup();
34-
35-
char buf[1024];
36-
readlink("/working/symlink", buf, 1024);
37-
printf("readlink: %s\n", buf);
38-
39-
FILE* fd = fopen("/working/symlink/test", "r");
33+
void test_inside_symlink()
34+
{
35+
char buf[256] = {0};
36+
readlink("/working/inside-symlink", buf, 256);
37+
printf("readlink: '%s'\n", buf);
38+
FILE* fd = fopen("/working/inside-symlink/test", "r");
4039
assert(fd);
4140
char buffer[8] = {0};
4241
int rtn = fread(buffer, 1, 7, fd);
4342
assert(rtn == 7);
44-
printf("buffer is '%s'\n", buffer);
43+
printf("buffer: '%s'\n", buffer);
4544
fclose(fd);
45+
}
4646

47-
// This should fail, since it resolves to ../new-directory which is not
48-
// mounted
49-
fd = fopen("/direct-link/test", "r");
47+
void test_outside_symlink()
48+
{
49+
// outside-symlink is link to an absolute path which is not part of the emscripten VFS
50+
// and so we should be able to open it.
51+
FILE* fd = fopen("/working/outside-symlink/test", "r");
5052
assert(fd == NULL);
53+
assert(errno == ENOENT);
54+
}
5155

56+
void test_mount_link()
57+
{
58+
char buf[256] = {0};
59+
readlink("/mount-link", buf, 256);
60+
printf("\nreadlink: '%s'\n", buf);
61+
FILE* fd = fopen("/mount-link/test", "r");
62+
assert(fd);
63+
char buffer[8] = {0};
64+
int rtn = fread(buffer, 1, 7, fd);
65+
assert(rtn == 7);
66+
printf("buffer: '%s'\n", buffer);
67+
fclose(fd);
68+
}
69+
70+
int main() {
71+
setup();
72+
test_inside_symlink();
73+
test_outside_symlink();
74+
test_mount_link();
5275
return 0;
5376
}

test/unistd/symlink_on_nodefs.out

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
readlink: /working/new-directory
2-
buffer is 'Link it'
1+
readlink: '/working/directory'
2+
buffer: 'Link it'
3+
4+
readlink: '/working/directory'
5+
buffer: 'Link it'

0 commit comments

Comments
 (0)