Skip to content

Commit 89b6941

Browse files
committed
fix: resolving relative paths in symlinks
1 parent 2c14ae9 commit 89b6941

File tree

2 files changed

+187
-28
lines changed

2 files changed

+187
-28
lines changed

src/path_resolver.c

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,42 @@ static char* uvwasi__strchr_slash(const char* s) {
3131
return NULL;
3232
}
3333

34+
static uvwasi_errno_t uvwasi__combine_paths(const uvwasi_t* uvwasi,
35+
const char* path1,
36+
uvwasi_size_t path1_len,
37+
const char* path2,
38+
uvwasi_size_t path2_len,
39+
char** combined_path,
40+
uvwasi_size_t* combined_len) {
41+
/* This function joins two paths with '/'. */
42+
uvwasi_errno_t err;
43+
char* combined;
44+
int combined_size;
45+
int r;
46+
47+
*combined_path = NULL;
48+
*combined_len = 0;
49+
50+
/* The max combined size is the path1 length + the path2 length
51+
+ 2 for a terminating NULL and a possible path separator. */
52+
combined_size = path1_len + path2_len + 2;
53+
combined = uvwasi__malloc(uvwasi, combined_size);
54+
if (combined == NULL) return UVWASI_ENOMEM;
55+
56+
r = snprintf(combined, combined_size, "%s/%s", path1, path2);
57+
if (r <= 0) {
58+
err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
59+
goto exit;
60+
}
61+
62+
err = UVWASI_ESUCCESS;
63+
*combined_path = combined;
64+
*combined_len = strlen(combined);
65+
66+
exit:
67+
if (err != UVWASI_ESUCCESS) uvwasi__free(uvwasi, combined);
68+
return err;
69+
}
3470

3571
uvwasi_errno_t uvwasi__normalize_path(const char* path,
3672
uvwasi_size_t path_len,
@@ -234,39 +270,35 @@ static uvwasi_errno_t uvwasi__normalize_relative_path(
234270
uvwasi_errno_t err;
235271
char* combined;
236272
char* normalized;
237-
int combined_size;
238-
int fd_path_len;
239-
int norm_len;
240-
int r;
273+
uvwasi_size_t combined_len;
274+
uvwasi_size_t fd_path_len;
275+
uvwasi_size_t norm_len;
241276

242277
*normalized_path = NULL;
243278
*normalized_len = 0;
244279

245-
/* The max combined size is the path length + the file descriptor's path
246-
length + 2 for a terminating NULL and a possible path separator. */
247280
fd_path_len = strlen(fd->normalized_path);
248-
combined_size = path_len + fd_path_len + 2;
249-
combined = uvwasi__malloc(uvwasi, combined_size);
250-
if (combined == NULL)
251-
return UVWASI_ENOMEM;
252281

253-
normalized = uvwasi__malloc(uvwasi, combined_size);
282+
err = uvwasi__combine_paths(uvwasi,
283+
fd->normalized_path,
284+
fd_path_len,
285+
path,
286+
path_len,
287+
&combined,
288+
&combined_len);
289+
if (err != UVWASI_ESUCCESS) goto exit;
290+
291+
normalized = uvwasi__malloc(uvwasi, combined_len + 1);
254292
if (normalized == NULL) {
255293
err = UVWASI_ENOMEM;
256294
goto exit;
257295
}
258296

259-
r = snprintf(combined, combined_size, "%s/%s", fd->normalized_path, path);
260-
if (r <= 0) {
261-
err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
262-
goto exit;
263-
}
264-
265297
/* Normalize the input path. */
266298
err = uvwasi__normalize_path(combined,
267-
combined_size - 1,
299+
combined_len,
268300
normalized,
269-
combined_size - 1);
301+
combined_len);
270302
if (err != UVWASI_ESUCCESS)
271303
goto exit;
272304

@@ -374,9 +406,14 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
374406
char* host_path;
375407
char* normalized_path;
376408
char* link_target;
409+
char* normalized_parent;
410+
char* resolved_link_target;
377411
uvwasi_size_t input_len;
378412
uvwasi_size_t host_path_len;
379413
uvwasi_size_t normalized_len;
414+
uvwasi_size_t link_target_len;
415+
uvwasi_size_t normalized_parent_len;
416+
uvwasi_size_t resolved_link_target_len;
380417
int follow_count;
381418
int r;
382419

@@ -385,6 +422,8 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
385422
link_target = NULL;
386423
follow_count = 0;
387424
host_path = NULL;
425+
normalized_parent = NULL;
426+
resolved_link_target = NULL;
388427

389428
start:
390429
normalized_path = NULL;
@@ -458,19 +497,47 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
458497
goto exit;
459498
}
460499

461-
input_len = strlen(req.ptr);
500+
link_target_len = strlen(req.ptr);
462501
uvwasi__free(uvwasi, link_target);
463-
link_target = uvwasi__malloc(uvwasi, input_len + 1);
502+
link_target = uvwasi__malloc(uvwasi, link_target_len + 1);
464503
if (link_target == NULL) {
465504
uv_fs_req_cleanup(&req);
466505
err = UVWASI_ENOMEM;
467506
goto exit;
468507
}
469508

470-
memcpy(link_target, req.ptr, input_len + 1);
471-
input = link_target;
472-
uvwasi__free(uvwasi, normalized_path);
509+
memcpy(link_target, req.ptr, link_target_len + 1);
473510
uv_fs_req_cleanup(&req);
511+
512+
if (1 == uvwasi__is_absolute_path(link_target, link_target_len)) {
513+
input = link_target;
514+
input_len = link_target_len;
515+
} else {
516+
uvwasi__free(uvwasi, normalized_parent);
517+
uvwasi__free(uvwasi, resolved_link_target);
518+
519+
err = uvwasi__combine_paths(uvwasi,
520+
normalized_path,
521+
normalized_len,
522+
"..",
523+
2,
524+
&normalized_parent,
525+
&normalized_parent_len);
526+
if (err != UVWASI_ESUCCESS) goto exit;
527+
err = uvwasi__combine_paths(uvwasi,
528+
normalized_parent,
529+
normalized_parent_len,
530+
link_target,
531+
link_target_len,
532+
&resolved_link_target,
533+
&resolved_link_target_len);
534+
if (err != UVWASI_ESUCCESS) goto exit;
535+
536+
input = resolved_link_target;
537+
input_len = resolved_link_target_len;
538+
}
539+
540+
uvwasi__free(uvwasi, normalized_path);
474541
goto start;
475542
}
476543

@@ -484,5 +551,8 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
484551

485552
uvwasi__free(uvwasi, link_target);
486553
uvwasi__free(uvwasi, normalized_path);
554+
uvwasi__free(uvwasi, normalized_parent);
555+
uvwasi__free(uvwasi, resolved_link_target);
556+
487557
return err;
488558
}

test/test-path-resolution.c

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "test-common.h"
99

1010
#define BUFFER_SIZE 1024
11+
#define TEST_TMP_DIR "./out/tmp"
1112

1213
static uvwasi_t uvwasi;
1314
static uvwasi_options_t init_options;
@@ -25,7 +26,7 @@ static void check_normalize(char* path, char* expected) {
2526
assert(0 == strcmp(buffer, expected));
2627
}
2728

28-
static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res) {
29+
static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res, uvwasi_lookupflags_t flags) {
2930
struct uvwasi_fd_wrap_t fd;
3031
uvwasi_errno_t err;
3132
uvwasi_size_t len;
@@ -48,21 +49,51 @@ static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res) {
4849
strlen(fd_mp));
4950
if (err != UVWASI_ESUCCESS)
5051
return err;
51-
return uvwasi__resolve_path(&uvwasi, &fd, path, len, res, 0);
52+
return uvwasi__resolve_path(&uvwasi, &fd, path, len, res, flags);
5253
}
5354

5455
static void pass(char* mp, char* rp, char* path, char* expected) {
5556
char* resolved;
57+
char* resolved_follow;
5658
size_t res_len;
5759
size_t i;
5860

59-
assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved));
61+
assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved, 0));
6062
res_len = strlen(resolved);
6163
assert(res_len == strlen(expected));
6264

65+
assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved_follow, UVWASI_LOOKUP_SYMLINK_FOLLOW));
66+
assert(strlen(resolved_follow) == res_len);
67+
6368
for (i = 0; i < res_len + 1; i++) {
6469
#ifdef _WIN32
6570
if (resolved[i] == '\\') {
71+
assert(resolved_follow[i] == '\\');
72+
assert(expected[i] == '/');
73+
continue;
74+
}
75+
#endif /* _WIN32 */
76+
77+
assert(resolved[i] == resolved_follow[i]);
78+
assert(resolved[i] == expected[i]);
79+
}
80+
81+
free(resolved);
82+
}
83+
84+
static void pass_follow(char* mp, char* rp, char* path, char* expected) {
85+
char *resolved;
86+
size_t res_len;
87+
size_t i;
88+
89+
assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved, UVWASI_LOOKUP_SYMLINK_FOLLOW));
90+
res_len = strlen(resolved);
91+
assert(res_len == strlen(expected));
92+
93+
for (i = 0; i < res_len + 1; i++) {
94+
#ifdef _WIN32
95+
if (resolved[i] == '\\') {
96+
assert(resolved_follow[i] == '\\');
6697
assert(expected[i] == '/');
6798
continue;
6899
}
@@ -76,9 +107,39 @@ static void pass(char* mp, char* rp, char* path, char* expected) {
76107

77108
static void fail(char* mp, char* rp, char* path, uvwasi_errno_t expected) {
78109
char* resolved;
110+
char* resolved_follow;
79111

80-
assert(expected == check(mp, rp, path, &resolved));
112+
assert(expected == check(mp, rp, path, &resolved, 0));
81113
assert(resolved == NULL);
114+
115+
assert(expected == check(mp, rp, path, &resolved_follow, UVWASI_LOOKUP_SYMLINK_FOLLOW));
116+
assert(resolved_follow == NULL);
117+
}
118+
119+
static void fail_follow(char *mp, char *rp, char *path, uvwasi_errno_t expected)
120+
{
121+
uvwasi_errno_t err;
122+
char *resolved;
123+
124+
assert(expected == check(mp, rp, path, &resolved, UVWASI_LOOKUP_SYMLINK_FOLLOW));
125+
assert(resolved == NULL);
126+
}
127+
128+
static void create_symlink(char* src, char* real_dst) {
129+
uv_fs_t req;
130+
int r;
131+
132+
r = uv_fs_mkdir(NULL, &req, TEST_TMP_DIR, 0777, NULL);
133+
uv_fs_req_cleanup(&req);
134+
assert(r == 0 || r == UV_EEXIST);
135+
136+
r = uv_fs_mkdir(NULL, &req, TEST_TMP_DIR "/dir", 0777, NULL);
137+
uv_fs_req_cleanup(&req);
138+
assert(r == 0 || r == UV_EEXIST);
139+
140+
r = uv_fs_symlink(NULL, &req, src, real_dst, 0, NULL);
141+
uv_fs_req_cleanup(&req);
142+
assert(r == 0 || r == UV_EEXIST);
82143
}
83144

84145
int main(void) {
@@ -149,6 +210,34 @@ int main(void) {
149210
fail("foo", "/baz", "../../foo/test_path", UVWASI_ENOTCAPABLE);
150211
fail("../baz", "/foo", "../bak/test_path", UVWASI_ENOTCAPABLE);
151212

213+
/* Arguments: source path, destination real path */
214+
create_symlink("foo", TEST_TMP_DIR "/bar");
215+
create_symlink("./foo", TEST_TMP_DIR "/bar2");
216+
create_symlink("/foo", TEST_TMP_DIR "/bar3");
217+
create_symlink("../foo", TEST_TMP_DIR "/bar4");
218+
create_symlink("/../foo", TEST_TMP_DIR "/bar5");
219+
create_symlink("bar", TEST_TMP_DIR "/baz");
220+
create_symlink("./bar", TEST_TMP_DIR "/baz2");
221+
create_symlink("/bar", TEST_TMP_DIR "/baz3");
222+
create_symlink("../foo", TEST_TMP_DIR "/dir/qux");
223+
create_symlink("./qux", TEST_TMP_DIR "/dir/quux");
224+
225+
/* Arguments: fd mapped path, fd real path, path to resolve, expected path */
226+
pass_follow("/", TEST_TMP_DIR, "/bar", TEST_TMP_DIR "/foo");
227+
pass_follow("/", TEST_TMP_DIR, "/bar2", TEST_TMP_DIR "/foo");
228+
pass_follow("/", TEST_TMP_DIR, "/bar3", TEST_TMP_DIR "/foo");
229+
pass_follow("/", TEST_TMP_DIR, "/bar4", TEST_TMP_DIR "/foo");
230+
pass_follow("/", TEST_TMP_DIR, "/bar5", TEST_TMP_DIR "/foo");
231+
pass_follow("/", TEST_TMP_DIR, "/baz", TEST_TMP_DIR "/foo");
232+
pass_follow("/", TEST_TMP_DIR, "/baz2", TEST_TMP_DIR "/foo");
233+
pass_follow("/", TEST_TMP_DIR, "/baz3", TEST_TMP_DIR "/foo");
234+
pass_follow("/", TEST_TMP_DIR, "/dir/qux", TEST_TMP_DIR "/foo");
235+
pass_follow("/", TEST_TMP_DIR, "/dir/quux", TEST_TMP_DIR "/foo");
236+
237+
/* Arguments: fd mapped path, fd real path, path to resolve, expected error */
238+
fail_follow("/dir", TEST_TMP_DIR "/dir", "/dir/qux", UVWASI_ENOTCAPABLE);
239+
fail_follow("/dir", TEST_TMP_DIR "/dir", "/dir/quux", UVWASI_ENOTCAPABLE);
240+
152241
uvwasi_destroy(&uvwasi);
153242
return 0;
154243
}

0 commit comments

Comments
 (0)