Skip to content

Commit

Permalink
fix: resolving relative paths in symlinks (#224)
Browse files Browse the repository at this point in the history
* fix: resolving relative paths in symlinks

* remove unused variable

* fix memory leak in tests

* fix mistaken assertions for windows in tests
  • Loading branch information
shqld authored Dec 7, 2023
1 parent f504042 commit 0427f19
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 28 deletions.
118 changes: 94 additions & 24 deletions src/path_resolver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
96 changes: 92 additions & 4 deletions test/test-path-resolution.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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));

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

0 comments on commit 0427f19

Please sign in to comment.