From 0427f19de2b0cc21bc98259fdcaf81095abdf0b8 Mon Sep 17 00:00:00 2001 From: Sho Miyamoto Date: Fri, 8 Dec 2023 00:35:20 +0900 Subject: [PATCH] fix: resolving relative paths in symlinks (#224) * fix: resolving relative paths in symlinks * remove unused variable * fix memory leak in tests * fix mistaken assertions for windows in tests --- src/path_resolver.c | 118 ++++++++++++++++++++++++++++-------- test/test-path-resolution.c | 96 +++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 28 deletions(-) diff --git a/src/path_resolver.c b/src/path_resolver.c index af13c15..ec8946b 100644 --- a/src/path_resolver.c +++ b/src/path_resolver.c @@ -31,6 +31,42 @@ static char* uvwasi__strchr_slash(const char* s) { return NULL; } +static uvwasi_errno_t uvwasi__combine_paths(const uvwasi_t* uvwasi, + const char* path1, + uvwasi_size_t path1_len, + const char* path2, + uvwasi_size_t path2_len, + char** combined_path, + uvwasi_size_t* combined_len) { + /* This function joins two paths with '/'. */ + uvwasi_errno_t err; + char* combined; + int combined_size; + int r; + + *combined_path = NULL; + *combined_len = 0; + + /* The max combined size is the path1 length + the path2 length + + 2 for a terminating NULL and a possible path separator. */ + combined_size = path1_len + path2_len + 2; + combined = uvwasi__malloc(uvwasi, combined_size); + if (combined == NULL) return UVWASI_ENOMEM; + + r = snprintf(combined, combined_size, "%s/%s", path1, path2); + if (r <= 0) { + err = uvwasi__translate_uv_error(uv_translate_sys_error(errno)); + goto exit; + } + + err = UVWASI_ESUCCESS; + *combined_path = combined; + *combined_len = strlen(combined); + +exit: + if (err != UVWASI_ESUCCESS) uvwasi__free(uvwasi, combined); + return err; +} uvwasi_errno_t uvwasi__normalize_path(const char* path, uvwasi_size_t path_len, @@ -234,39 +270,35 @@ static uvwasi_errno_t uvwasi__normalize_relative_path( uvwasi_errno_t err; char* combined; char* normalized; - int combined_size; - int fd_path_len; - int norm_len; - int r; + uvwasi_size_t combined_len; + uvwasi_size_t fd_path_len; + uvwasi_size_t norm_len; *normalized_path = NULL; *normalized_len = 0; - /* The max combined size is the path length + the file descriptor's path - length + 2 for a terminating NULL and a possible path separator. */ fd_path_len = strlen(fd->normalized_path); - combined_size = path_len + fd_path_len + 2; - combined = uvwasi__malloc(uvwasi, combined_size); - if (combined == NULL) - return UVWASI_ENOMEM; - normalized = uvwasi__malloc(uvwasi, combined_size); + err = uvwasi__combine_paths(uvwasi, + fd->normalized_path, + fd_path_len, + path, + path_len, + &combined, + &combined_len); + if (err != UVWASI_ESUCCESS) goto exit; + + normalized = uvwasi__malloc(uvwasi, combined_len + 1); if (normalized == NULL) { err = UVWASI_ENOMEM; goto exit; } - r = snprintf(combined, combined_size, "%s/%s", fd->normalized_path, path); - if (r <= 0) { - err = uvwasi__translate_uv_error(uv_translate_sys_error(errno)); - goto exit; - } - /* Normalize the input path. */ err = uvwasi__normalize_path(combined, - combined_size - 1, + combined_len, normalized, - combined_size - 1); + combined_len); if (err != UVWASI_ESUCCESS) goto exit; @@ -374,9 +406,14 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, char* host_path; char* normalized_path; char* link_target; + char* normalized_parent; + char* resolved_link_target; uvwasi_size_t input_len; uvwasi_size_t host_path_len; uvwasi_size_t normalized_len; + uvwasi_size_t link_target_len; + uvwasi_size_t normalized_parent_len; + uvwasi_size_t resolved_link_target_len; int follow_count; int r; @@ -385,6 +422,8 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, link_target = NULL; follow_count = 0; host_path = NULL; + normalized_parent = NULL; + resolved_link_target = NULL; start: normalized_path = NULL; @@ -458,19 +497,47 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, goto exit; } - input_len = strlen(req.ptr); + link_target_len = strlen(req.ptr); uvwasi__free(uvwasi, link_target); - link_target = uvwasi__malloc(uvwasi, input_len + 1); + link_target = uvwasi__malloc(uvwasi, link_target_len + 1); if (link_target == NULL) { uv_fs_req_cleanup(&req); err = UVWASI_ENOMEM; goto exit; } - memcpy(link_target, req.ptr, input_len + 1); - input = link_target; - uvwasi__free(uvwasi, normalized_path); + memcpy(link_target, req.ptr, link_target_len + 1); uv_fs_req_cleanup(&req); + + if (1 == uvwasi__is_absolute_path(link_target, link_target_len)) { + input = link_target; + input_len = link_target_len; + } else { + uvwasi__free(uvwasi, normalized_parent); + uvwasi__free(uvwasi, resolved_link_target); + + err = uvwasi__combine_paths(uvwasi, + normalized_path, + normalized_len, + "..", + 2, + &normalized_parent, + &normalized_parent_len); + if (err != UVWASI_ESUCCESS) goto exit; + err = uvwasi__combine_paths(uvwasi, + normalized_parent, + normalized_parent_len, + link_target, + link_target_len, + &resolved_link_target, + &resolved_link_target_len); + if (err != UVWASI_ESUCCESS) goto exit; + + input = resolved_link_target; + input_len = resolved_link_target_len; + } + + uvwasi__free(uvwasi, normalized_path); goto start; } @@ -484,5 +551,8 @@ uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, uvwasi__free(uvwasi, link_target); uvwasi__free(uvwasi, normalized_path); + uvwasi__free(uvwasi, normalized_parent); + uvwasi__free(uvwasi, resolved_link_target); + return err; } diff --git a/test/test-path-resolution.c b/test/test-path-resolution.c index 9491547..50510b5 100644 --- a/test/test-path-resolution.c +++ b/test/test-path-resolution.c @@ -8,6 +8,7 @@ #include "test-common.h" #define BUFFER_SIZE 1024 +#define TEST_TMP_DIR "./out/tmp" static uvwasi_t uvwasi; static uvwasi_options_t init_options; @@ -25,7 +26,7 @@ static void check_normalize(char* path, char* expected) { assert(0 == strcmp(buffer, expected)); } -static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res) { +static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res, uvwasi_lookupflags_t flags) { struct uvwasi_fd_wrap_t fd; uvwasi_errno_t err; uvwasi_size_t len; @@ -48,15 +49,45 @@ static uvwasi_errno_t check(char* fd_mp, char* fd_rp, char* path, char** res) { strlen(fd_mp)); if (err != UVWASI_ESUCCESS) return err; - return uvwasi__resolve_path(&uvwasi, &fd, path, len, res, 0); + return uvwasi__resolve_path(&uvwasi, &fd, path, len, res, flags); } static void pass(char* mp, char* rp, char* path, char* expected) { char* resolved; + char* resolved_follow; size_t res_len; size_t i; - assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved)); + assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved, 0)); + res_len = strlen(resolved); + assert(res_len == strlen(expected)); + + assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved_follow, UVWASI_LOOKUP_SYMLINK_FOLLOW)); + assert(strlen(resolved_follow) == res_len); + + for (i = 0; i < res_len + 1; i++) { +#ifdef _WIN32 + if (resolved[i] == '\\') { + assert(resolved_follow[i] == '\\'); + assert(expected[i] == '/'); + continue; + } +#endif /* _WIN32 */ + + assert(resolved[i] == resolved_follow[i]); + assert(resolved[i] == expected[i]); + } + + free(resolved); + free(resolved_follow); +} + +static void pass_follow(char* mp, char* rp, char* path, char* expected) { + char *resolved; + size_t res_len; + size_t i; + + assert(UVWASI_ESUCCESS == check(mp, rp, path, &resolved, UVWASI_LOOKUP_SYMLINK_FOLLOW)); res_len = strlen(resolved); assert(res_len == strlen(expected)); @@ -76,9 +107,38 @@ static void pass(char* mp, char* rp, char* path, char* expected) { static void fail(char* mp, char* rp, char* path, uvwasi_errno_t expected) { char* resolved; + char* resolved_follow; - assert(expected == check(mp, rp, path, &resolved)); + assert(expected == check(mp, rp, path, &resolved, 0)); assert(resolved == NULL); + + assert(expected == check(mp, rp, path, &resolved_follow, UVWASI_LOOKUP_SYMLINK_FOLLOW)); + assert(resolved_follow == NULL); +} + +static void fail_follow(char *mp, char *rp, char *path, uvwasi_errno_t expected) +{ + char *resolved; + + assert(expected == check(mp, rp, path, &resolved, UVWASI_LOOKUP_SYMLINK_FOLLOW)); + assert(resolved == NULL); +} + +static void create_symlink(char* src, char* real_dst) { + uv_fs_t req; + int r; + + r = uv_fs_mkdir(NULL, &req, TEST_TMP_DIR, 0777, NULL); + uv_fs_req_cleanup(&req); + assert(r == 0 || r == UV_EEXIST); + + r = uv_fs_mkdir(NULL, &req, TEST_TMP_DIR "/dir", 0777, NULL); + uv_fs_req_cleanup(&req); + assert(r == 0 || r == UV_EEXIST); + + r = uv_fs_symlink(NULL, &req, src, real_dst, 0, NULL); + uv_fs_req_cleanup(&req); + assert(r == 0 || r == UV_EEXIST); } int main(void) { @@ -149,6 +209,34 @@ int main(void) { fail("foo", "/baz", "../../foo/test_path", UVWASI_ENOTCAPABLE); fail("../baz", "/foo", "../bak/test_path", UVWASI_ENOTCAPABLE); + /* Arguments: source path, destination real path */ + create_symlink("foo", TEST_TMP_DIR "/bar"); + create_symlink("./foo", TEST_TMP_DIR "/bar2"); + create_symlink("/foo", TEST_TMP_DIR "/bar3"); + create_symlink("../foo", TEST_TMP_DIR "/bar4"); + create_symlink("/../foo", TEST_TMP_DIR "/bar5"); + create_symlink("bar", TEST_TMP_DIR "/baz"); + create_symlink("./bar", TEST_TMP_DIR "/baz2"); + create_symlink("/bar", TEST_TMP_DIR "/baz3"); + create_symlink("../foo", TEST_TMP_DIR "/dir/qux"); + create_symlink("./qux", TEST_TMP_DIR "/dir/quux"); + + /* Arguments: fd mapped path, fd real path, path to resolve, expected path */ + pass_follow("/", TEST_TMP_DIR, "/bar", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/bar2", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/bar3", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/bar4", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/bar5", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/baz", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/baz2", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/baz3", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/dir/qux", TEST_TMP_DIR "/foo"); + pass_follow("/", TEST_TMP_DIR, "/dir/quux", TEST_TMP_DIR "/foo"); + + /* Arguments: fd mapped path, fd real path, path to resolve, expected error */ + fail_follow("/dir", TEST_TMP_DIR "/dir", "/dir/qux", UVWASI_ENOTCAPABLE); + fail_follow("/dir", TEST_TMP_DIR "/dir", "/dir/quux", UVWASI_ENOTCAPABLE); + uvwasi_destroy(&uvwasi); return 0; }