From 8325c29e927e6db5931d8f7c6490fded45fbe575 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sun, 26 Apr 2020 10:57:44 -0400 Subject: [PATCH] deps: update to uvwasi 0.0.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This release focuses on improving the robustness of the path resolution and sandboxing, including adding support for relative preopen paths. PR-URL: https://github.com/nodejs/node/pull/33078 Reviewed-By: Gus Caplan Reviewed-By: Juan José Arboleda Reviewed-By: Anna Henningsen --- deps/uvwasi/include/fd_table.h | 1 + deps/uvwasi/include/uvwasi.h | 6 +- deps/uvwasi/include/wasi_types.h | 2 +- deps/uvwasi/src/clocks.c | 4 +- deps/uvwasi/src/fd_table.c | 19 +- deps/uvwasi/src/path_resolver.c | 492 +++++++++++++++++++++++++++++++ deps/uvwasi/src/path_resolver.h | 28 ++ deps/uvwasi/src/uvwasi.c | 330 +-------------------- deps/uvwasi/uvwasi.gyp | 1 + 9 files changed, 546 insertions(+), 337 deletions(-) create mode 100644 deps/uvwasi/src/path_resolver.c create mode 100644 deps/uvwasi/src/path_resolver.h diff --git a/deps/uvwasi/include/fd_table.h b/deps/uvwasi/include/fd_table.h index f29b1adf88d6f0..474a0231e03baf 100644 --- a/deps/uvwasi/include/fd_table.h +++ b/deps/uvwasi/include/fd_table.h @@ -13,6 +13,7 @@ struct uvwasi_fd_wrap_t { uv_file fd; char* path; char* real_path; + char* normalized_path; uvwasi_filetype_t type; uvwasi_rights_t rights_base; uvwasi_rights_t rights_inheriting; diff --git a/deps/uvwasi/include/uvwasi.h b/deps/uvwasi/include/uvwasi.h index 39ee2f0ceb6609..f2d2d1a9fd8945 100644 --- a/deps/uvwasi/include/uvwasi.h +++ b/deps/uvwasi/include/uvwasi.h @@ -11,7 +11,7 @@ extern "C" { #define UVWASI_VERSION_MAJOR 0 #define UVWASI_VERSION_MINOR 0 -#define UVWASI_VERSION_PATCH 6 +#define UVWASI_VERSION_PATCH 8 #define UVWASI_VERSION_HEX ((UVWASI_VERSION_MAJOR << 16) | \ (UVWASI_VERSION_MINOR << 8) | \ (UVWASI_VERSION_PATCH)) @@ -66,7 +66,7 @@ typedef struct uvwasi_options_s { const uvwasi_mem_t* allocator; } uvwasi_options_t; -// Embedder API. +/* Embedder API. */ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options); void uvwasi_destroy(uvwasi_t* uvwasi); uvwasi_errno_t uvwasi_embedder_remap_fd(uvwasi_t* uvwasi, @@ -75,7 +75,7 @@ uvwasi_errno_t uvwasi_embedder_remap_fd(uvwasi_t* uvwasi, const char* uvwasi_embedder_err_code_to_string(uvwasi_errno_t code); -// WASI system call API. +/* WASI system call API. */ uvwasi_errno_t uvwasi_args_get(uvwasi_t* uvwasi, char** argv, char* argv_buf); uvwasi_errno_t uvwasi_args_sizes_get(uvwasi_t* uvwasi, size_t* argc, diff --git a/deps/uvwasi/include/wasi_types.h b/deps/uvwasi/include/wasi_types.h index ec1013663f6a76..2f93b412624c06 100644 --- a/deps/uvwasi/include/wasi_types.h +++ b/deps/uvwasi/include/wasi_types.h @@ -4,7 +4,7 @@ #include #include -/* API: https://github.com/WebAssembly/WASI/blob/master/phases/unstable/docs/wasi_unstable_preview0.md */ +/* API: https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/docs.md */ typedef uint8_t uvwasi_advice_t; #define UVWASI_ADVICE_NORMAL 0 diff --git a/deps/uvwasi/src/clocks.c b/deps/uvwasi/src/clocks.c index fd42b9e50e4d8e..b59cbd6bb192c0 100644 --- a/deps/uvwasi/src/clocks.c +++ b/deps/uvwasi/src/clocks.c @@ -153,7 +153,7 @@ uvwasi_errno_t uvwasi__clock_gettime_thread_cputime(uvwasi_timestamp_t* time) { UVWASI__WIN_TIME_AND_RETURN(GetCurrentThread(), *time); #elif defined(__APPLE__) UVWASI__OSX_THREADTIME_AND_RETURN(*time); -#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__sun) +#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__sun) && !defined(__PASE__) UVWASI__CLOCK_GETTIME_AND_RETURN(CLOCK_THREAD_CPUTIME_ID, *time); #else # if defined(RUSAGE_LWP) @@ -185,7 +185,7 @@ uvwasi_errno_t uvwasi__clock_getres_thread_cputime(uvwasi_timestamp_t* time) { UVWASI__WIN_GETRES_AND_RETURN(*time); #elif defined(__APPLE__) UVWASI__SLOW_GETRES_AND_RETURN(*time); -#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__sun) +#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__sun) && !defined(__PASE__) UVWASI__CLOCK_GETTIME_AND_RETURN(CLOCK_THREAD_CPUTIME_ID, *time); #elif defined(RUSAGE_THREAD) || defined(RUSAGE_LWP) UVWASI__SLOW_GETRES_AND_RETURN(*time); diff --git a/deps/uvwasi/src/fd_table.c b/deps/uvwasi/src/fd_table.c index f6e530d9591df2..3d134e3b7e5e2e 100644 --- a/deps/uvwasi/src/fd_table.c +++ b/deps/uvwasi/src/fd_table.c @@ -8,6 +8,7 @@ #include "uv.h" #include "fd_table.h" +#include "path_resolver.h" #include "wasi_types.h" #include "wasi_rights.h" #include "uv_mapping.h" @@ -75,20 +76,33 @@ uvwasi_errno_t uvwasi_fd_table_insert(uvwasi_t* uvwasi, char* mp_copy; size_t rp_len; char* rp_copy; + char* np_copy; mp_len = strlen(mapped_path); rp_len = strlen(real_path); + /* Reserve room for the mapped path, real path, and normalized mapped path. */ entry = (struct uvwasi_fd_wrap_t*) - uvwasi__malloc(uvwasi, sizeof(*entry) + mp_len + rp_len + 2); - if (entry == NULL) return UVWASI_ENOMEM; + uvwasi__malloc(uvwasi, sizeof(*entry) + mp_len + mp_len + rp_len + 3); + if (entry == NULL) + return UVWASI_ENOMEM; mp_copy = (char*)(entry + 1); rp_copy = mp_copy + mp_len + 1; + np_copy = rp_copy + rp_len + 1; memcpy(mp_copy, mapped_path, mp_len); mp_copy[mp_len] = '\0'; memcpy(rp_copy, real_path, rp_len); rp_copy[rp_len] = '\0'; + /* Calculate the normalized version of the mapped path, as it will be used for + any path calculations on this fd. Use the length of the mapped path as an + upper bound for the normalized path length. */ + err = uvwasi__normalize_path(mp_copy, mp_len, np_copy, mp_len); + if (err) { + uvwasi__free(uvwasi, entry); + goto exit; + } + uv_rwlock_wrlock(&table->rwlock); /* Check that there is room for a new item. If there isn't, grow the table. */ @@ -138,6 +152,7 @@ uvwasi_errno_t uvwasi_fd_table_insert(uvwasi_t* uvwasi, entry->fd = fd; entry->path = mp_copy; entry->real_path = rp_copy; + entry->normalized_path = np_copy; entry->type = type; entry->rights_base = rights_base; entry->rights_inheriting = rights_inheriting; diff --git a/deps/uvwasi/src/path_resolver.c b/deps/uvwasi/src/path_resolver.c new file mode 100644 index 00000000000000..ee0e60f7e8f4ea --- /dev/null +++ b/deps/uvwasi/src/path_resolver.c @@ -0,0 +1,492 @@ +#include + +#include "uv.h" +#include "uvwasi.h" +#include "uvwasi_alloc.h" +#include "uv_mapping.h" +#include "path_resolver.h" + +#define UVWASI__MAX_SYMLINK_FOLLOWS 32 + +#ifndef _WIN32 +# define IS_SLASH(c) ((c) == '/') +#else +# define IS_SLASH(c) ((c) == '/' || (c) == '\\') +#endif /* _WIN32 */ + + +static int uvwasi__is_absolute_path(const char* path, size_t path_len) { + /* It's expected that only Unix style paths will be generated by WASI. */ + return path != NULL && path_len > 0 && path[0] == '/'; +} + + +static char* uvwasi__strchr_slash(const char* s) { + /* strchr() that identifies /, as well as \ on Windows. */ + do { + if (IS_SLASH(*s)) + return (char*) s; + } while (*s++); + + return NULL; +} + + +uvwasi_errno_t uvwasi__normalize_path(const char* path, + size_t path_len, + char* normalized_path, + size_t normalized_len) { + const char* cur; + char* ptr; + char* next; + char* last; + size_t cur_len; + int is_absolute; + + if (path_len > normalized_len) + return UVWASI_ENOBUFS; + + is_absolute = uvwasi__is_absolute_path(path, path_len); + normalized_path[0] = '\0'; + ptr = normalized_path; + for (cur = path; cur != NULL; cur = next + 1) { + next = uvwasi__strchr_slash(cur); + cur_len = (next == NULL) ? strlen(cur) : (size_t) (next - cur); + + if (cur_len == 0) { + if (ptr == normalized_path && next != NULL && is_absolute) { + *ptr = '/'; + ptr++; + } + + *ptr = '\0'; + } else if (cur_len == 1 && cur[0] == '.') { + /* No-op. Just consume the '.' */ + } else if (cur_len == 2 && cur[0] == '.' && cur[1] == '.') { + /* Identify the path segment that preceded the current one. */ + last = ptr; + while (!IS_SLASH(*last) && last != normalized_path) { + last--; + } + + /* If the result is currently empty, or the last prior path is also '..' + then output '..'. Otherwise, remove the last path segment. */ + if (ptr == normalized_path || + (last == ptr - 2 && last[0] == '.' && last[1] == '.') || + (last == ptr - 3 && last[0] == '/' && + last[1] == '.' && last[2] == '.')) { + if (ptr != normalized_path && *(ptr - 1) != '/') { + *ptr = '/'; + ptr++; + } + + *ptr = '.'; + ptr++; + *ptr = '.'; + ptr++; + } else { + /* Strip the last segment, but make sure not to strip the '/' if that + is the entire path. */ + if (last == normalized_path && *last == '/') + ptr = last + 1; + else + ptr = last; + } + + *ptr = '\0'; + } else { + if (ptr != normalized_path && *(ptr - 1) != '/') { + *ptr = '/'; + ptr++; + } + + memcpy(ptr, cur, cur_len); + ptr += cur_len; + *ptr = '\0'; + } + + if (next == NULL) + break; + } + + /* Normalized the path to the empty string. Return either '/' or '.'. */ + if (ptr == normalized_path) { + if (1 == is_absolute) + *ptr = '/'; + else + *ptr = '.'; + + ptr++; + *ptr = '\0'; + } + + return UVWASI_ESUCCESS; +} + + +static int uvwasi__is_path_sandboxed(const char* path, + size_t path_len, + const char* fd_path, + size_t fd_path_len) { + char* ptr; + int remaining_len; + + if (1 == uvwasi__is_absolute_path(fd_path, fd_path_len)) + return path == strstr(path, fd_path) ? 1 : 0; + + /* Handle relative fds that normalized to '.' */ + if (fd_path_len == 1 && fd_path[0] == '.') { + /* If the fd's path is '.', then any path does not begin with '..' is OK. */ + if ((path_len == 2 && path[0] == '.' && path[1] == '.') || + (path_len > 2 && path[0] == '.' && path[1] == '.' && path[2] == '/')) { + return 0; + } + + return 1; + } + + if (path != strstr(path, fd_path)) + return 0; + + /* Fail if the remaining path starts with '..', '../', '/..', or '/../'. */ + ptr = (char*) path + fd_path_len; + remaining_len = path_len - fd_path_len; + if (remaining_len < 2) + return 1; + + /* Strip a leading slash so the check is only for '..' and '../'. */ + if (*ptr == '/') { + ptr++; + remaining_len--; + } + + if ((remaining_len == 2 && ptr[0] == '.' && ptr[1] == '.') || + (remaining_len > 2 && ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '/')) { + return 0; + } + + return 1; +} + + +static uvwasi_errno_t uvwasi__normalize_absolute_path( + const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, + const char* path, + size_t path_len, + char** normalized_path, + size_t* normalized_len + ) { + /* This function resolves an absolute path to the provided file descriptor. + If the file descriptor's path is relative, then this operation will fail + with UVWASI_ENOTCAPABLE since it doesn't make sense to resolve an absolute + path to a relative prefix. If the file desciptor's path is also absolute, + then we just need to verify that the normalized path still starts with + the file descriptor's path. */ + uvwasi_errno_t err; + char* abs_path; + int abs_size; + + *normalized_path = NULL; + *normalized_len = 0; + abs_size = path_len + 1; + abs_path = uvwasi__malloc(uvwasi, abs_size); + if (abs_path == NULL) { + err = UVWASI_ENOMEM; + goto exit; + } + + /* Normalize the input path first. */ + err = uvwasi__normalize_path(path, path_len, abs_path, path_len); + if (err != UVWASI_ESUCCESS) + goto exit; + + /* Once the input is normalized, ensure that it is still sandboxed. */ + if (0 == uvwasi__is_path_sandboxed(abs_path, + path_len, + fd->normalized_path, + strlen(fd->normalized_path))) { + err = UVWASI_ENOTCAPABLE; + goto exit; + } + + *normalized_path = abs_path; + *normalized_len = abs_size - 1; + return UVWASI_ESUCCESS; + +exit: + uvwasi__free(uvwasi, abs_path); + return err; +} + + +static uvwasi_errno_t uvwasi__normalize_relative_path( + const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, + const char* path, + size_t path_len, + char** normalized_path, + size_t* normalized_len + ) { + /* This function resolves a relative path to the provided file descriptor. + The relative path is concatenated to the file descriptor's path, and then + normalized. */ + uvwasi_errno_t err; + char* combined; + char* normalized; + int combined_size; + int fd_path_len; + int norm_len; + int r; + + *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); + 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, + normalized, + combined_size - 1); + if (err != UVWASI_ESUCCESS) + goto exit; + + norm_len = strlen(normalized); + + /* Once the path is normalized, ensure that it is still sandboxed. */ + if (0 == uvwasi__is_path_sandboxed(normalized, + norm_len, + fd->normalized_path, + fd_path_len)) { + err = UVWASI_ENOTCAPABLE; + goto exit; + } + + err = UVWASI_ESUCCESS; + *normalized_path = normalized; + *normalized_len = norm_len; + +exit: + if (err != UVWASI_ESUCCESS) + uvwasi__free(uvwasi, normalized); + + uvwasi__free(uvwasi, combined); + return err; +} + + +static uvwasi_errno_t uvwasi__resolve_path_to_host( + const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, + const char* path, + size_t path_len, + char** resolved_path, + size_t* resolved_len + ) { + /* Return the normalized path, but resolved to the host's real path. */ + char* res_path; + char* stripped_path; + int real_path_len; + int fake_path_len; + int stripped_len; +#ifdef _WIN32 + size_t i; +#endif /* _WIN32 */ + + real_path_len = strlen(fd->real_path); + fake_path_len = strlen(fd->normalized_path); + + /* If the fake path is '.' just ignore it. */ + if (fake_path_len == 1 && fd->normalized_path[0] == '.') { + fake_path_len = 0; + } + + stripped_len = path_len - fake_path_len; + + /* The resolved path's length is calculated as: the length of the fd's real + path, + 1 for a path separator, and the length of the input path (with the + fake path stripped off). */ + *resolved_len = stripped_len + real_path_len + 1; + *resolved_path = uvwasi__malloc(uvwasi, *resolved_len + 1); + + if (*resolved_path == NULL) + return UVWASI_ENOMEM; + + res_path = *resolved_path; + stripped_path = (char*) path + fake_path_len; + memcpy(res_path, fd->real_path, real_path_len); + res_path += real_path_len; + + if (stripped_len > 1 || + (stripped_len == 1 && stripped_path[0] != '/')) { + if (stripped_path[0] != '/') { + *res_path = '/'; + res_path++; + } + + memcpy(res_path, stripped_path, stripped_len); + res_path += stripped_len; + } + + *res_path = '\0'; + +#ifdef _WIN32 + /* Replace / with \ on Windows. */ + for (i = real_path_len; i < *resolved_len; i++) { + if (res_path[i] == '/') + res_path[i] = '\\'; + } +#endif /* _WIN32 */ + + return UVWASI_ESUCCESS; +} + + +uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, + const char* path, + size_t path_len, + char* resolved_path, + uvwasi_lookupflags_t flags) { + uv_fs_t req; + uvwasi_errno_t err; + const char* input; + char* host_path; + char* normalized_path; + char* link_target; + size_t input_len; + size_t host_path_len; + size_t normalized_len; + int follow_count; + int r; + + input = path; + input_len = path_len; + link_target = NULL; + follow_count = 0; + host_path = NULL; + +start: + normalized_path = NULL; + err = UVWASI_ESUCCESS; + + if (1 == uvwasi__is_absolute_path(input, input_len)) { + err = uvwasi__normalize_absolute_path(uvwasi, + fd, + input, + input_len, + &normalized_path, + &normalized_len); + } else { + err = uvwasi__normalize_relative_path(uvwasi, + fd, + input, + input_len, + &normalized_path, + &normalized_len); + } + + if (err != UVWASI_ESUCCESS) + goto exit; + + uvwasi__free(uvwasi, host_path); + err = uvwasi__resolve_path_to_host(uvwasi, + fd, + normalized_path, + normalized_len, + &host_path, + &host_path_len); + if (err != UVWASI_ESUCCESS) + goto exit; + + /* TODO(cjihrig): Currently performing a bounds check here. The TODO is to + stop allocating resolved_path in every caller and instead return the + path allocated in this function. */ + if (host_path_len > PATH_MAX_BYTES) { + err = UVWASI_ENOBUFS; + goto exit; + } + + if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW) == UVWASI_LOOKUP_SYMLINK_FOLLOW) { + r = uv_fs_readlink(NULL, &req, host_path, NULL); + + if (r != 0) { +#ifdef _WIN32 + /* uv_fs_readlink() returns UV__UNKNOWN on Windows. Try to get a better + error using uv_fs_stat(). */ + if (r == UV__UNKNOWN) { + uv_fs_req_cleanup(&req); + r = uv_fs_stat(NULL, &req, host_path, NULL); + + if (r == 0) { + if (uvwasi__stat_to_filetype(&req.statbuf) != + UVWASI_FILETYPE_SYMBOLIC_LINK) { + r = UV_EINVAL; + } + } + + /* Fall through. */ + } +#endif /* _WIN32 */ + + /* Don't report UV_EINVAL or UV_ENOENT. They mean that either the file + does not exist, or it is not a symlink. Both are OK. */ + if (r != UV_EINVAL && r != UV_ENOENT) + err = uvwasi__translate_uv_error(r); + + uv_fs_req_cleanup(&req); + goto exit; + } + + /* Clean up memory and follow the link, unless it's time to return ELOOP. */ + follow_count++; + if (follow_count >= UVWASI__MAX_SYMLINK_FOLLOWS) { + uv_fs_req_cleanup(&req); + err = UVWASI_ELOOP; + goto exit; + } + + input_len = strlen(req.ptr); + uvwasi__free(uvwasi, link_target); + link_target = uvwasi__malloc(uvwasi, input_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); + uv_fs_req_cleanup(&req); + goto start; + } + +exit: + if (err == UVWASI_ESUCCESS) + memcpy(resolved_path, host_path, host_path_len + 1); + + uvwasi__free(uvwasi, link_target); + uvwasi__free(uvwasi, normalized_path); + uvwasi__free(uvwasi, host_path); + return err; +} diff --git a/deps/uvwasi/src/path_resolver.h b/deps/uvwasi/src/path_resolver.h new file mode 100644 index 00000000000000..d5f95eafbfbf83 --- /dev/null +++ b/deps/uvwasi/src/path_resolver.h @@ -0,0 +1,28 @@ +#ifndef __UVWASI_PATH_RESOLVER_H__ +#define __UVWASI_PATH_RESOLVER_H__ + +#include "uvwasi.h" + +/* TODO(cjihrig): PATH_MAX_BYTES shouldn't be stack allocated. On Windows, paths + can be 32k long, and this PATH_MAX_BYTES is an artificial limitation. */ +#ifdef _WIN32 +/* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ +# define PATH_MAX_BYTES (MAX_PATH * 4) +#else +# include +# define PATH_MAX_BYTES (PATH_MAX) +#endif + +uvwasi_errno_t uvwasi__normalize_path(const char* path, + size_t path_len, + char* normalized_path, + size_t normalized_len); + +uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, + const char* path, + size_t path_len, + char* resolved_path, + uvwasi_lookupflags_t flags); + +#endif /* __UVWASI_PATH_RESOLVER_H__ */ diff --git a/deps/uvwasi/src/uvwasi.c b/deps/uvwasi/src/uvwasi.c index c80fc7715c1dc0..0ee66be36a3951 100644 --- a/deps/uvwasi/src/uvwasi.c +++ b/deps/uvwasi/src/uvwasi.c @@ -7,14 +7,11 @@ # include # include # include -# define IS_SLASH(c) ((c) == '/') #else # include -# define IS_SLASH(c) ((c) == '/' || (c) == '\\') #endif /* _WIN32 */ #define UVWASI__READDIR_NUM_ENTRIES 1 -#define UVWASI__MAX_SYMLINK_FOLLOWS 32 #include "uvwasi.h" #include "uvwasi_alloc.h" @@ -22,18 +19,9 @@ #include "uv_mapping.h" #include "fd_table.h" #include "clocks.h" +#include "path_resolver.h" #include "wasi_rights.h" -/* TODO(cjihrig): PATH_MAX_BYTES shouldn't be stack allocated. On Windows, paths - can be 32k long, and this PATH_MAX_BYTES is an artificial limitation. */ -#ifdef _WIN32 -/* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ -# define PATH_MAX_BYTES (MAX_PATH * 4) -#else -# include -# define PATH_MAX_BYTES (PATH_MAX) -#endif - /* IBMi PASE does not support posix_fadvise() */ #ifdef __PASE__ # undef POSIX_FADV_NORMAL @@ -84,322 +72,6 @@ static const uvwasi_mem_t default_allocator = { }; -static int uvwasi__is_absolute_path(const char* path, size_t path_len) { - /* It's expected that only Unix style paths will be generated by WASI. */ - return path != NULL && path_len > 0 && path[0] == '/'; -} - - -static char* uvwasi__strchr_slash(const char* s) { - /* strchr() that identifies /, as well as \ on Windows. */ - do { - if (IS_SLASH(*s)) - return (char*) s; - } while (*s++); - - return NULL; -} - - -static uvwasi_errno_t uvwasi__normalize_path(const char* path, - size_t path_len, - char* normalized_path, - size_t normalized_len) { - const char* cur; - char* ptr; - char* next; - size_t cur_len; - - if (path_len > normalized_len) - return UVWASI_ENOBUFS; - - normalized_path[0] = '\0'; - ptr = normalized_path; - for (cur = path; cur != NULL; cur = next + 1) { - next = uvwasi__strchr_slash(cur); - cur_len = (next == NULL) ? strlen(cur) : (size_t) (next - cur); - - if (cur_len == 0 || (cur_len == 1 && cur[0] == '.')) - continue; - - if (cur_len == 2 && cur[0] == '.' && cur[1] == '.') { - while (!IS_SLASH(*ptr) && ptr != normalized_path) - ptr--; - *ptr = '\0'; - continue; - } - - *ptr = '/'; - ptr++; - memcpy(ptr, cur, cur_len); - ptr += cur_len; - *ptr = '\0'; - - if (next == NULL) - break; - } - - return UVWASI_ESUCCESS; -} - - -static uvwasi_errno_t uvwasi__resolve_path_to_host( - const uvwasi_t* uvwasi, - const struct uvwasi_fd_wrap_t* fd, - const char* path, - size_t path_len, - char** resolved_path, - size_t* resolved_len - ) { - /* Return the normalized path, but resolved to the host's real path. */ - int real_path_len; - int fake_path_len; -#ifdef _WIN32 - size_t i; -#endif /* _WIN32 */ - - real_path_len = strlen(fd->real_path); - fake_path_len = strlen(fd->path); - *resolved_len = path_len - fake_path_len + real_path_len; - *resolved_path = uvwasi__malloc(uvwasi, *resolved_len + 1); - - if (*resolved_path == NULL) - return UVWASI_ENOMEM; - - memcpy(*resolved_path, fd->real_path, real_path_len); - memcpy(*resolved_path + real_path_len, - path + fake_path_len, - path_len - fake_path_len + 1); - -#ifdef _WIN32 - /* Replace / with \ on Windows. */ - for (i = real_path_len; i < *resolved_len; i++) { - if ((*resolved_path)[i] == '/') - (*resolved_path)[i] = '\\'; - } -#endif /* _WIN32 */ - - return UVWASI_ESUCCESS; -} - - -static uvwasi_errno_t uvwasi__normalize_absolute_path( - const uvwasi_t* uvwasi, - const struct uvwasi_fd_wrap_t* fd, - const char* path, - size_t path_len, - char** normalized_path, - size_t* normalized_len - ) { - uvwasi_errno_t err; - char* abs_path; - int abs_size; - - *normalized_path = NULL; - *normalized_len = 0; - abs_size = path_len + 1; - abs_path = uvwasi__malloc(uvwasi, abs_size); - if (abs_path == NULL) { - err = UVWASI_ENOMEM; - goto exit; - } - - /* Normalize the input path first. */ - err = uvwasi__normalize_path(path, - path_len, - abs_path, - path_len); - if (err != UVWASI_ESUCCESS) - goto exit; - - /* Once the input is normalized, ensure that it is still sandboxed. */ - if (abs_path != strstr(abs_path, fd->path)) { - err = UVWASI_ENOTCAPABLE; - goto exit; - } - - *normalized_path = abs_path; - *normalized_len = abs_size - 1; - return UVWASI_ESUCCESS; - -exit: - uvwasi__free(uvwasi, abs_path); - return err; -} - - -static uvwasi_errno_t uvwasi__normalize_relative_path( - const uvwasi_t* uvwasi, - const struct uvwasi_fd_wrap_t* fd, - const char* path, - size_t path_len, - char** normalized_path, - size_t* normalized_len - ) { - uvwasi_errno_t err; - char* abs_path; - int abs_size; - int r; - - *normalized_path = NULL; - *normalized_len = 0; - abs_size = path_len + strlen(fd->path) + 2; - abs_path = uvwasi__malloc(uvwasi, abs_size); - if (abs_path == NULL) { - err = UVWASI_ENOMEM; - goto exit; - } - - /* Resolve the relative path to an absolute path based on fd's fake path. */ - r = snprintf(abs_path, abs_size, "%s/%s", fd->path, path); - if (r <= 0) { - err = uvwasi__translate_uv_error(uv_translate_sys_error(errno)); - goto exit; - } - - err = uvwasi__normalize_absolute_path(uvwasi, - fd, - abs_path, - abs_size - 1, - normalized_path, - normalized_len); -exit: - uvwasi__free(uvwasi, abs_path); - return err; -} - - -static uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, - const struct uvwasi_fd_wrap_t* fd, - const char* path, - size_t path_len, - char* resolved_path, - uvwasi_lookupflags_t flags) { - uv_fs_t req; - uvwasi_errno_t err; - const char* input; - char* host_path; - char* normalized_path; - char* link_target; - size_t input_len; - size_t host_path_len; - size_t normalized_len; - int follow_count; - int r; - - input = path; - input_len = path_len; - link_target = NULL; - follow_count = 0; - host_path = NULL; - -start: - normalized_path = NULL; - err = UVWASI_ESUCCESS; - - if (1 == uvwasi__is_absolute_path(input, input_len)) { - err = uvwasi__normalize_absolute_path(uvwasi, - fd, - input, - input_len, - &normalized_path, - &normalized_len); - } else { - err = uvwasi__normalize_relative_path(uvwasi, - fd, - input, - input_len, - &normalized_path, - &normalized_len); - } - - if (err != UVWASI_ESUCCESS) - goto exit; - - uvwasi__free(uvwasi, host_path); - err = uvwasi__resolve_path_to_host(uvwasi, - fd, - normalized_path, - normalized_len, - &host_path, - &host_path_len); - if (err != UVWASI_ESUCCESS) - goto exit; - - /* TODO(cjihrig): Currently performing a bounds check here. The TODO is to - stop allocating resolved_path in every caller and instead return the - path allocated in this function. */ - if (host_path_len > PATH_MAX_BYTES) { - err = UVWASI_ENOBUFS; - goto exit; - } - - if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW) == UVWASI_LOOKUP_SYMLINK_FOLLOW) { - r = uv_fs_readlink(NULL, &req, host_path, NULL); - - if (r != 0) { -#ifdef _WIN32 - /* uv_fs_readlink() returns UV__UNKNOWN on Windows. Try to get a better - error using uv_fs_stat(). */ - if (r == UV__UNKNOWN) { - uv_fs_req_cleanup(&req); - r = uv_fs_stat(NULL, &req, host_path, NULL); - - if (r == 0) { - if (uvwasi__stat_to_filetype(&req.statbuf) != - UVWASI_FILETYPE_SYMBOLIC_LINK) { - r = UV_EINVAL; - } - } - - // Fall through. - } -#endif /* _WIN32 */ - - /* Don't report UV_EINVAL or UV_ENOENT. They mean that either the file - does not exist, or it is not a symlink. Both are OK. */ - if (r != UV_EINVAL && r != UV_ENOENT) - err = uvwasi__translate_uv_error(r); - - uv_fs_req_cleanup(&req); - goto exit; - } - - /* Clean up memory and follow the link, unless it's time to return ELOOP. */ - follow_count++; - if (follow_count >= UVWASI__MAX_SYMLINK_FOLLOWS) { - uv_fs_req_cleanup(&req); - err = UVWASI_ELOOP; - goto exit; - } - - input_len = strlen(req.ptr); - uvwasi__free(uvwasi, link_target); - link_target = uvwasi__malloc(uvwasi, input_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); - uv_fs_req_cleanup(&req); - goto start; - } - -exit: - if (err == UVWASI_ESUCCESS) - memcpy(resolved_path, host_path, host_path_len + 1); - - uvwasi__free(uvwasi, link_target); - uvwasi__free(uvwasi, normalized_path); - uvwasi__free(uvwasi, host_path); - return err; -} - - static uvwasi_errno_t uvwasi__lseek(uv_file fd, uvwasi_filedelta_t offset, uvwasi_whence_t whence, diff --git a/deps/uvwasi/uvwasi.gyp b/deps/uvwasi/uvwasi.gyp index 6963cbf20a7923..42769095ecbafd 100644 --- a/deps/uvwasi/uvwasi.gyp +++ b/deps/uvwasi/uvwasi.gyp @@ -11,6 +11,7 @@ 'sources': [ 'src/clocks.c', 'src/fd_table.c', + 'src/path_resolver.c', 'src/uv_mapping.c', 'src/uvwasi.c', 'src/wasi_rights.c',