Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resolving relative paths in symlinks #224

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Loading