mirror of https://github.com/nodejs/node.git
deps: update to uvwasi 0.0.8
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 <me@gus.host> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>pull/33078/head
parent
6ca6db105d
commit
497ad815ae
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,492 @@
|
|||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <limits.h>
|
||||
# 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__ */
|
|
@ -7,14 +7,11 @@
|
|||
# include <unistd.h>
|
||||
# include <dirent.h>
|
||||
# include <time.h>
|
||||
# define IS_SLASH(c) ((c) == '/')
|
||||
#else
|
||||
# include <io.h>
|
||||
# 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 <limits.h>
|
||||
# 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,
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue