Skip to content

Commit 676e3c3

Browse files
cjihrigcodebytere
authored andcommittedMar 17, 2020
deps,test: update to uvwasi 0.0.4
This commit updates the uvwasi dependency to version 0.0.4. The most notable change is a refactor of the way paths are resolved. All paths, including symlinks, are now resolved in terms of sandboxed paths instead of leaking host system paths. PR-URL: #31363 Reviewed-By: David Carlier <devnexen@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com>
1 parent d944fa7 commit 676e3c3

File tree

6 files changed

+285
-95
lines changed

6 files changed

+285
-95
lines changed
 

‎deps/uvwasi/include/fd_table.h

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <stdint.h>
55
#include "uv.h"
66
#include "wasi_types.h"
7-
#include "uv_mapping.h"
87

98
struct uvwasi_s;
109

‎deps/uvwasi/include/uvwasi.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
extern "C" {
66
#endif
77

8+
#include "uv.h"
89
#include "wasi_types.h"
9-
#include "uv_mapping.h"
1010
#include "fd_table.h"
1111

1212
#define UVWASI_VERSION_MAJOR 0
1313
#define UVWASI_VERSION_MINOR 0
14-
#define UVWASI_VERSION_PATCH 3
14+
#define UVWASI_VERSION_PATCH 4
1515
#define UVWASI_VERSION_HEX ((UVWASI_VERSION_MAJOR << 16) | \
1616
(UVWASI_VERSION_MINOR << 8) | \
1717
(UVWASI_VERSION_PATCH))
File renamed without changes.
File renamed without changes.

‎deps/uvwasi/src/uvwasi.c

+277-88
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@
77
# include <unistd.h>
88
# include <dirent.h>
99
# include <time.h>
10-
# define SLASH '/'
11-
# define SLASH_STR "/"
1210
# define IS_SLASH(c) ((c) == '/')
1311
#else
14-
# define SLASH '\\'
15-
# define SLASH_STR "\\"
1612
# define IS_SLASH(c) ((c) == '/' || (c) == '\\')
1713
#endif /* _WIN32 */
1814

1915
#define UVWASI__READDIR_NUM_ENTRIES 1
16+
#define UVWASI__MAX_SYMLINK_FOLLOWS 32
2017

2118
#include "uvwasi.h"
2219
#include "uvwasi_alloc.h"
@@ -86,120 +83,312 @@ static int uvwasi__is_absolute_path(const char* path, size_t path_len) {
8683
}
8784

8885

86+
static char* uvwasi__strchr_slash(const char* s) {
87+
/* strchr() that identifies /, as well as \ on Windows. */
88+
do {
89+
if (IS_SLASH(*s))
90+
return (char*) s;
91+
} while (*s++);
92+
93+
return NULL;
94+
}
95+
96+
97+
static uvwasi_errno_t uvwasi__normalize_path(const char* path,
98+
size_t path_len,
99+
char* normalized_path,
100+
size_t normalized_len) {
101+
const char* cur;
102+
char* ptr;
103+
char* next;
104+
size_t cur_len;
105+
106+
if (path_len > normalized_len)
107+
return UVWASI_ENOBUFS;
108+
109+
normalized_path[0] = '\0';
110+
ptr = normalized_path;
111+
for (cur = path; cur != NULL; cur = next + 1) {
112+
next = uvwasi__strchr_slash(cur);
113+
cur_len = (next == NULL) ? strlen(cur) : (size_t) (next - cur);
114+
115+
if (cur_len == 0 || (cur_len == 1 && cur[0] == '.'))
116+
continue;
117+
118+
if (cur_len == 2 && cur[0] == '.' && cur[1] == '.') {
119+
while (!IS_SLASH(*ptr) && ptr != normalized_path)
120+
ptr--;
121+
*ptr = '\0';
122+
continue;
123+
}
124+
125+
*ptr = '/';
126+
ptr++;
127+
memcpy(ptr, cur, cur_len);
128+
ptr += cur_len;
129+
*ptr = '\0';
130+
131+
if (next == NULL)
132+
break;
133+
}
134+
135+
return UVWASI_ESUCCESS;
136+
}
137+
138+
139+
static uvwasi_errno_t uvwasi__resolve_path_to_host(
140+
const uvwasi_t* uvwasi,
141+
const struct uvwasi_fd_wrap_t* fd,
142+
const char* path,
143+
size_t path_len,
144+
char** resolved_path,
145+
size_t* resolved_len
146+
) {
147+
/* Return the normalized path, but resolved to the host's real path. */
148+
int real_path_len;
149+
int fake_path_len;
150+
#ifdef _WIN32
151+
size_t i;
152+
#endif /* _WIN32 */
153+
154+
real_path_len = strlen(fd->real_path);
155+
fake_path_len = strlen(fd->path);
156+
*resolved_len = path_len - fake_path_len + real_path_len;
157+
*resolved_path = uvwasi__malloc(uvwasi, *resolved_len + 1);
158+
159+
if (*resolved_path == NULL)
160+
return UVWASI_ENOMEM;
161+
162+
memcpy(*resolved_path, fd->real_path, real_path_len);
163+
memcpy(*resolved_path + real_path_len,
164+
path + fake_path_len,
165+
path_len - fake_path_len + 1);
166+
167+
#ifdef _WIN32
168+
/* Replace / with \ on Windows. */
169+
for (i = real_path_len; i < *resolved_len; i++) {
170+
if ((*resolved_path)[i] == '/')
171+
(*resolved_path)[i] = '\\';
172+
}
173+
#endif /* _WIN32 */
174+
175+
return UVWASI_ESUCCESS;
176+
}
177+
178+
179+
static uvwasi_errno_t uvwasi__normalize_absolute_path(
180+
const uvwasi_t* uvwasi,
181+
const struct uvwasi_fd_wrap_t* fd,
182+
const char* path,
183+
size_t path_len,
184+
char** normalized_path,
185+
size_t* normalized_len
186+
) {
187+
uvwasi_errno_t err;
188+
char* abs_path;
189+
int abs_size;
190+
191+
*normalized_path = NULL;
192+
*normalized_len = 0;
193+
abs_size = path_len + 1;
194+
abs_path = uvwasi__malloc(uvwasi, abs_size);
195+
if (abs_path == NULL) {
196+
err = UVWASI_ENOMEM;
197+
goto exit;
198+
}
199+
200+
/* Normalize the input path first. */
201+
err = uvwasi__normalize_path(path,
202+
path_len,
203+
abs_path,
204+
path_len);
205+
if (err != UVWASI_ESUCCESS)
206+
goto exit;
207+
208+
/* Once the input is normalized, ensure that it is still sandboxed. */
209+
if (abs_path != strstr(abs_path, fd->path)) {
210+
err = UVWASI_ENOTCAPABLE;
211+
goto exit;
212+
}
213+
214+
*normalized_path = abs_path;
215+
*normalized_len = abs_size - 1;
216+
return UVWASI_ESUCCESS;
217+
218+
exit:
219+
uvwasi__free(uvwasi, abs_path);
220+
return err;
221+
}
222+
223+
224+
static uvwasi_errno_t uvwasi__normalize_relative_path(
225+
const uvwasi_t* uvwasi,
226+
const struct uvwasi_fd_wrap_t* fd,
227+
const char* path,
228+
size_t path_len,
229+
char** normalized_path,
230+
size_t* normalized_len
231+
) {
232+
uvwasi_errno_t err;
233+
char* abs_path;
234+
int abs_size;
235+
int r;
236+
237+
*normalized_path = NULL;
238+
*normalized_len = 0;
239+
abs_size = path_len + strlen(fd->path) + 2;
240+
abs_path = uvwasi__malloc(uvwasi, abs_size);
241+
if (abs_path == NULL) {
242+
err = UVWASI_ENOMEM;
243+
goto exit;
244+
}
245+
246+
/* Resolve the relative path to an absolute path based on fd's fake path. */
247+
r = snprintf(abs_path, abs_size, "%s/%s", fd->path, path);
248+
if (r <= 0) {
249+
err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
250+
goto exit;
251+
}
252+
253+
err = uvwasi__normalize_absolute_path(uvwasi,
254+
fd,
255+
abs_path,
256+
abs_size - 1,
257+
normalized_path,
258+
normalized_len);
259+
exit:
260+
uvwasi__free(uvwasi, abs_path);
261+
return err;
262+
}
263+
264+
89265
static uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
90266
const struct uvwasi_fd_wrap_t* fd,
91267
const char* path,
92268
size_t path_len,
93269
char* resolved_path,
94270
uvwasi_lookupflags_t flags) {
95-
uv_fs_t realpath_req;
271+
uv_fs_t req;
96272
uvwasi_errno_t err;
97-
char* abs_path;
98-
char* tok;
99-
char* ptr;
100-
int realpath_size;
101-
int abs_size;
102-
int input_is_absolute;
273+
const char* input;
274+
char* host_path;
275+
char* normalized_path;
276+
char* link_target;
277+
size_t input_len;
278+
size_t host_path_len;
279+
size_t normalized_len;
280+
int follow_count;
103281
int r;
104-
#ifdef _WIN32
105-
int i;
106-
#endif /* _WIN32 */
107282

108-
err = UVWASI_ESUCCESS;
109-
input_is_absolute = uvwasi__is_absolute_path(path, path_len);
283+
input = path;
284+
input_len = path_len;
285+
link_target = NULL;
286+
follow_count = 0;
287+
host_path = NULL;
110288

111-
if (1 == input_is_absolute) {
112-
/* TODO(cjihrig): Revisit this. Copying is probably not necessary here. */
113-
abs_size = path_len;
114-
abs_path = uvwasi__malloc(uvwasi, abs_size);
115-
if (abs_path == NULL) {
116-
err = UVWASI_ENOMEM;
117-
goto exit;
118-
}
289+
start:
290+
normalized_path = NULL;
291+
err = UVWASI_ESUCCESS;
119292

120-
memcpy(abs_path, path, abs_size);
293+
if (1 == uvwasi__is_absolute_path(input, input_len)) {
294+
err = uvwasi__normalize_absolute_path(uvwasi,
295+
fd,
296+
input,
297+
input_len,
298+
&normalized_path,
299+
&normalized_len);
121300
} else {
122-
/* Resolve the relative path to fd's real path. */
123-
abs_size = path_len + strlen(fd->real_path) + 2;
124-
abs_path = uvwasi__malloc(uvwasi, abs_size);
125-
if (abs_path == NULL) {
126-
err = UVWASI_ENOMEM;
127-
goto exit;
128-
}
129-
130-
r = snprintf(abs_path, abs_size, "%s/%s", fd->real_path, path);
131-
if (r <= 0) {
132-
err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
133-
goto exit;
134-
}
301+
err = uvwasi__normalize_relative_path(uvwasi,
302+
fd,
303+
input,
304+
input_len,
305+
&normalized_path,
306+
&normalized_len);
135307
}
136308

137-
#ifdef _WIN32
138-
/* On Windows, convert slashes to backslashes. */
139-
for (i = 0; i < abs_size; ++i) {
140-
if (abs_path[i] == '/')
141-
abs_path[i] = SLASH;
142-
}
143-
#endif /* _WIN32 */
309+
if (err != UVWASI_ESUCCESS)
310+
goto exit;
144311

145-
ptr = resolved_path;
146-
tok = strtok(abs_path, SLASH_STR);
147-
for (; tok != NULL; tok = strtok(NULL, SLASH_STR)) {
148-
if (0 == strcmp(tok, "."))
149-
continue;
312+
uvwasi__free(uvwasi, host_path);
313+
err = uvwasi__resolve_path_to_host(uvwasi,
314+
fd,
315+
normalized_path,
316+
normalized_len,
317+
&host_path,
318+
&host_path_len);
319+
if (err != UVWASI_ESUCCESS)
320+
goto exit;
150321

151-
if (0 == strcmp(tok, "..")) {
152-
while (*ptr != SLASH && ptr != resolved_path)
153-
ptr--;
154-
*ptr = '\0';
155-
continue;
156-
}
322+
/* TODO(cjihrig): Currently performing a bounds check here. The TODO is to
323+
stop allocating resolved_path in every caller and instead return the
324+
path allocated in this function. */
325+
if (host_path_len > PATH_MAX_BYTES) {
326+
err = UVWASI_ENOBUFS;
327+
goto exit;
328+
}
329+
330+
if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW) == UVWASI_LOOKUP_SYMLINK_FOLLOW) {
331+
r = uv_fs_readlink(NULL, &req, host_path, NULL);
157332

333+
if (r != 0) {
158334
#ifdef _WIN32
159-
/* On Windows, prevent a leading slash in the path. */
160-
if (ptr == resolved_path)
161-
r = sprintf(ptr, "%s", tok);
162-
else
335+
/* uv_fs_readlink() returns UV__UNKNOWN on Windows. Try to get a better
336+
error using uv_fs_stat(). */
337+
if (r == UV__UNKNOWN) {
338+
uv_fs_req_cleanup(&req);
339+
r = uv_fs_stat(NULL, &req, host_path, NULL);
340+
341+
if (r == 0) {
342+
if (uvwasi__stat_to_filetype(&req.statbuf) !=
343+
UVWASI_FILETYPE_SYMBOLIC_LINK) {
344+
r = UV_EINVAL;
345+
}
346+
}
347+
348+
// Fall through.
349+
}
163350
#endif /* _WIN32 */
164-
r = sprintf(ptr, "%c%s", SLASH, tok);
165351

166-
if (r < 1) { /* At least one character should have been written. */
167-
err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
352+
/* Don't report UV_EINVAL or UV_ENOENT. They mean that either the file
353+
does not exist, or it is not a symlink. Both are OK. */
354+
if (r != UV_EINVAL && r != UV_ENOENT)
355+
err = uvwasi__translate_uv_error(r);
356+
357+
uv_fs_req_cleanup(&req);
168358
goto exit;
169359
}
170360

171-
ptr += r;
172-
}
173-
174-
if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW) == UVWASI_LOOKUP_SYMLINK_FOLLOW) {
175-
r = uv_fs_realpath(NULL, &realpath_req, resolved_path, NULL);
176-
if (r == 0) {
177-
realpath_size = strlen(realpath_req.ptr) + 1;
178-
if (realpath_size > PATH_MAX_BYTES) {
179-
err = UVWASI_ENOBUFS;
180-
uv_fs_req_cleanup(&realpath_req);
181-
goto exit;
182-
}
183-
184-
memcpy(resolved_path, realpath_req.ptr, realpath_size);
185-
} else if (r != UV_ENOENT) {
186-
/* Report errors except ENOENT. */
187-
err = uvwasi__translate_uv_error(r);
188-
uv_fs_req_cleanup(&realpath_req);
361+
/* Clean up memory and follow the link, unless it's time to return ELOOP. */
362+
follow_count++;
363+
if (follow_count >= UVWASI__MAX_SYMLINK_FOLLOWS) {
364+
uv_fs_req_cleanup(&req);
365+
err = UVWASI_ELOOP;
189366
goto exit;
190367
}
191368

192-
uv_fs_req_cleanup(&realpath_req);
193-
}
369+
input_len = strlen(req.ptr);
370+
uvwasi__free(uvwasi, link_target);
371+
link_target = uvwasi__malloc(uvwasi, input_len + 1);
372+
if (link_target == NULL) {
373+
uv_fs_req_cleanup(&req);
374+
err = UVWASI_ENOMEM;
375+
goto exit;
376+
}
194377

195-
/* Verify that the resolved path is still in the sandbox. */
196-
if (resolved_path != strstr(resolved_path, fd->real_path)) {
197-
err = UVWASI_ENOTCAPABLE;
198-
goto exit;
378+
memcpy(link_target, req.ptr, input_len + 1);
379+
input = link_target;
380+
uvwasi__free(uvwasi, normalized_path);
381+
uv_fs_req_cleanup(&req);
382+
goto start;
199383
}
200384

201385
exit:
202-
uvwasi__free(uvwasi, abs_path);
386+
if (err == UVWASI_ESUCCESS)
387+
memcpy(resolved_path, host_path, host_path_len + 1);
388+
389+
uvwasi__free(uvwasi, link_target);
390+
uvwasi__free(uvwasi, normalized_path);
391+
uvwasi__free(uvwasi, host_path);
203392
return err;
204393
}
205394

‎test/wasi/test-wasi-symlinks.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ if (process.argv[2] === 'wasi-child') {
5050
fs.mkdirSync(sandboxedDir);
5151
fs.writeFileSync(sandboxedFile, 'hello from input.txt', 'utf8');
5252
fs.writeFileSync(externalFile, 'this should be inaccessible', 'utf8');
53-
fs.symlinkSync(sandboxedFile, sandboxedSymlink, 'file');
54-
fs.symlinkSync(externalFile, escapingSymlink, 'file');
55-
fs.symlinkSync(loopSymlink2, loopSymlink1, 'file');
56-
fs.symlinkSync(loopSymlink1, loopSymlink2, 'file');
53+
fs.symlinkSync(path.join('.', 'input.txt'), sandboxedSymlink, 'file');
54+
fs.symlinkSync(path.join('..', 'outside.txt'), escapingSymlink, 'file');
55+
fs.symlinkSync(path.join('subdir', 'loop2'),
56+
loopSymlink1, 'file');
57+
fs.symlinkSync(path.join('subdir', 'loop1'),
58+
loopSymlink2, 'file');
5759

5860
function runWASI(options) {
5961
console.log('executing', options.test);

0 commit comments

Comments
 (0)
Please sign in to comment.