1 #include <string.h>
2
3 #include "uv.h"
4 #include "uvwasi.h"
5 #include "uvwasi_alloc.h"
6 #include "uv_mapping.h"
7 #include "path_resolver.h"
8
9 #define UVWASI__MAX_SYMLINK_FOLLOWS 32
10
11 #ifndef _WIN32
12 # define IS_SLASH(c) ((c) == '/')
13 #else
14 # define IS_SLASH(c) ((c) == '/' || (c) == '\\')
15 #endif /* _WIN32 */
16
17
uvwasi__is_absolute_path(const char * path,uvwasi_size_t path_len)18 static int uvwasi__is_absolute_path(const char* path, uvwasi_size_t path_len) {
19 /* It's expected that only Unix style paths will be generated by WASI. */
20 return path != NULL && path_len > 0 && path[0] == '/';
21 }
22
23
uvwasi__strchr_slash(const char * s)24 static char* uvwasi__strchr_slash(const char* s) {
25 /* strchr() that identifies /, as well as \ on Windows. */
26 do {
27 if (IS_SLASH(*s))
28 return (char*) s;
29 } while (*s++);
30
31 return NULL;
32 }
33
34
uvwasi__normalize_path(const char * path,uvwasi_size_t path_len,char * normalized_path,uvwasi_size_t normalized_len)35 uvwasi_errno_t uvwasi__normalize_path(const char* path,
36 uvwasi_size_t path_len,
37 char* normalized_path,
38 uvwasi_size_t normalized_len) {
39 const char* cur;
40 char* ptr;
41 char* next;
42 char* last;
43 size_t cur_len;
44 int is_absolute;
45
46 if (path_len > normalized_len)
47 return UVWASI_ENOBUFS;
48
49 is_absolute = uvwasi__is_absolute_path(path, path_len);
50 normalized_path[0] = '\0';
51 ptr = normalized_path;
52 for (cur = path; cur != NULL; cur = next + 1) {
53 next = uvwasi__strchr_slash(cur);
54 cur_len = (next == NULL) ? strlen(cur) : (size_t) (next - cur);
55
56 if (cur_len == 0) {
57 if (ptr == normalized_path && next != NULL && is_absolute) {
58 *ptr = '/';
59 ptr++;
60 }
61
62 *ptr = '\0';
63 } else if (cur_len == 1 && cur[0] == '.') {
64 /* No-op. Just consume the '.' */
65 } else if (cur_len == 2 && cur[0] == '.' && cur[1] == '.') {
66 /* Identify the path segment that preceded the current one. */
67 last = ptr;
68 while (!IS_SLASH(*last) && last != normalized_path) {
69 last--;
70 }
71
72 /* If the result is currently empty, or the last prior path is also '..'
73 then output '..'. Otherwise, remove the last path segment. */
74 if (ptr == normalized_path ||
75 (last == ptr - 2 && last[0] == '.' && last[1] == '.') ||
76 (last == ptr - 3 && last[0] == '/' &&
77 last[1] == '.' && last[2] == '.')) {
78 if (ptr != normalized_path && *(ptr - 1) != '/') {
79 *ptr = '/';
80 ptr++;
81 }
82
83 *ptr = '.';
84 ptr++;
85 *ptr = '.';
86 ptr++;
87 } else {
88 /* Strip the last segment, but make sure not to strip the '/' if that
89 is the entire path. */
90 if (last == normalized_path && *last == '/')
91 ptr = last + 1;
92 else
93 ptr = last;
94 }
95
96 *ptr = '\0';
97 } else {
98 if (ptr != normalized_path && *(ptr - 1) != '/') {
99 *ptr = '/';
100 ptr++;
101 }
102
103 memcpy(ptr, cur, cur_len);
104 ptr += cur_len;
105 *ptr = '\0';
106 }
107
108 if (next == NULL)
109 break;
110 }
111
112 /* Normalized the path to the empty string. Return either '/' or '.'. */
113 if (ptr == normalized_path) {
114 if (1 == is_absolute)
115 *ptr = '/';
116 else
117 *ptr = '.';
118
119 ptr++;
120 *ptr = '\0';
121 }
122
123 return UVWASI_ESUCCESS;
124 }
125
126
uvwasi__is_path_sandboxed(const char * path,uvwasi_size_t path_len,const char * fd_path,uvwasi_size_t fd_path_len)127 static int uvwasi__is_path_sandboxed(const char* path,
128 uvwasi_size_t path_len,
129 const char* fd_path,
130 uvwasi_size_t fd_path_len) {
131 char* ptr;
132 int remaining_len;
133
134 if (1 == uvwasi__is_absolute_path(fd_path, fd_path_len))
135 return path == strstr(path, fd_path) ? 1 : 0;
136
137 /* Handle relative fds that normalized to '.' */
138 if (fd_path_len == 1 && fd_path[0] == '.') {
139 /* If the fd's path is '.', then any path does not begin with '..' is OK. */
140 if ((path_len == 2 && path[0] == '.' && path[1] == '.') ||
141 (path_len > 2 && path[0] == '.' && path[1] == '.' && path[2] == '/')) {
142 return 0;
143 }
144
145 return 1;
146 }
147
148 if (path != strstr(path, fd_path))
149 return 0;
150
151 /* Fail if the remaining path starts with '..', '../', '/..', or '/../'. */
152 ptr = (char*) path + fd_path_len;
153 remaining_len = path_len - fd_path_len;
154 if (remaining_len < 2)
155 return 1;
156
157 /* Strip a leading slash so the check is only for '..' and '../'. */
158 if (*ptr == '/') {
159 ptr++;
160 remaining_len--;
161 }
162
163 if ((remaining_len == 2 && ptr[0] == '.' && ptr[1] == '.') ||
164 (remaining_len > 2 && ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '/')) {
165 return 0;
166 }
167
168 return 1;
169 }
170
171
uvwasi__normalize_absolute_path(const uvwasi_t * uvwasi,const struct uvwasi_fd_wrap_t * fd,const char * path,uvwasi_size_t path_len,char ** normalized_path,uvwasi_size_t * normalized_len)172 static uvwasi_errno_t uvwasi__normalize_absolute_path(
173 const uvwasi_t* uvwasi,
174 const struct uvwasi_fd_wrap_t* fd,
175 const char* path,
176 uvwasi_size_t path_len,
177 char** normalized_path,
178 uvwasi_size_t* normalized_len
179 ) {
180 /* This function resolves an absolute path to the provided file descriptor.
181 If the file descriptor's path is relative, then this operation will fail
182 with UVWASI_ENOTCAPABLE since it doesn't make sense to resolve an absolute
183 path to a relative prefix. If the file desciptor's path is also absolute,
184 then we just need to verify that the normalized path still starts with
185 the file descriptor's path. */
186 uvwasi_errno_t err;
187 char* abs_path;
188 int abs_size;
189
190 *normalized_path = NULL;
191 *normalized_len = 0;
192 abs_size = path_len + 1;
193 abs_path = uvwasi__malloc(uvwasi, abs_size);
194 if (abs_path == NULL) {
195 err = UVWASI_ENOMEM;
196 goto exit;
197 }
198
199 /* Normalize the input path first. */
200 err = uvwasi__normalize_path(path, path_len, abs_path, path_len);
201 if (err != UVWASI_ESUCCESS)
202 goto exit;
203
204 /* Once the input is normalized, ensure that it is still sandboxed. */
205 if (0 == uvwasi__is_path_sandboxed(abs_path,
206 path_len,
207 fd->normalized_path,
208 strlen(fd->normalized_path))) {
209 err = UVWASI_ENOTCAPABLE;
210 goto exit;
211 }
212
213 *normalized_path = abs_path;
214 *normalized_len = abs_size - 1;
215 return UVWASI_ESUCCESS;
216
217 exit:
218 uvwasi__free(uvwasi, abs_path);
219 return err;
220 }
221
222
uvwasi__normalize_relative_path(const uvwasi_t * uvwasi,const struct uvwasi_fd_wrap_t * fd,const char * path,uvwasi_size_t path_len,char ** normalized_path,uvwasi_size_t * normalized_len)223 static uvwasi_errno_t uvwasi__normalize_relative_path(
224 const uvwasi_t* uvwasi,
225 const struct uvwasi_fd_wrap_t* fd,
226 const char* path,
227 uvwasi_size_t path_len,
228 char** normalized_path,
229 uvwasi_size_t* normalized_len
230 ) {
231 /* This function resolves a relative path to the provided file descriptor.
232 The relative path is concatenated to the file descriptor's path, and then
233 normalized. */
234 uvwasi_errno_t err;
235 char* combined;
236 char* normalized;
237 int combined_size;
238 int fd_path_len;
239 int norm_len;
240 int r;
241
242 *normalized_path = NULL;
243 *normalized_len = 0;
244
245 /* The max combined size is the path length + the file descriptor's path
246 length + 2 for a terminating NULL and a possible path separator. */
247 fd_path_len = strlen(fd->normalized_path);
248 combined_size = path_len + fd_path_len + 2;
249 combined = uvwasi__malloc(uvwasi, combined_size);
250 if (combined == NULL)
251 return UVWASI_ENOMEM;
252
253 normalized = uvwasi__malloc(uvwasi, combined_size);
254 if (normalized == NULL) {
255 err = UVWASI_ENOMEM;
256 goto exit;
257 }
258
259 r = snprintf(combined, combined_size, "%s/%s", fd->normalized_path, path);
260 if (r <= 0) {
261 err = uvwasi__translate_uv_error(uv_translate_sys_error(errno));
262 goto exit;
263 }
264
265 /* Normalize the input path. */
266 err = uvwasi__normalize_path(combined,
267 combined_size - 1,
268 normalized,
269 combined_size - 1);
270 if (err != UVWASI_ESUCCESS)
271 goto exit;
272
273 norm_len = strlen(normalized);
274
275 /* Once the path is normalized, ensure that it is still sandboxed. */
276 if (0 == uvwasi__is_path_sandboxed(normalized,
277 norm_len,
278 fd->normalized_path,
279 fd_path_len)) {
280 err = UVWASI_ENOTCAPABLE;
281 goto exit;
282 }
283
284 err = UVWASI_ESUCCESS;
285 *normalized_path = normalized;
286 *normalized_len = norm_len;
287
288 exit:
289 if (err != UVWASI_ESUCCESS)
290 uvwasi__free(uvwasi, normalized);
291
292 uvwasi__free(uvwasi, combined);
293 return err;
294 }
295
296
uvwasi__resolve_path_to_host(const uvwasi_t * uvwasi,const struct uvwasi_fd_wrap_t * fd,const char * path,uvwasi_size_t path_len,char ** resolved_path,uvwasi_size_t * resolved_len)297 static uvwasi_errno_t uvwasi__resolve_path_to_host(
298 const uvwasi_t* uvwasi,
299 const struct uvwasi_fd_wrap_t* fd,
300 const char* path,
301 uvwasi_size_t path_len,
302 char** resolved_path,
303 uvwasi_size_t* resolved_len
304 ) {
305 /* Return the normalized path, but resolved to the host's real path. */
306 char* res_path;
307 char* stripped_path;
308 int real_path_len;
309 int fake_path_len;
310 int stripped_len;
311 #ifdef _WIN32
312 uvwasi_size_t i;
313 #endif /* _WIN32 */
314
315 real_path_len = strlen(fd->real_path);
316 fake_path_len = strlen(fd->normalized_path);
317
318 /* If the fake path is '.' just ignore it. */
319 if (fake_path_len == 1 && fd->normalized_path[0] == '.') {
320 fake_path_len = 0;
321 }
322
323 stripped_len = path_len - fake_path_len;
324
325 /* The resolved path's length is calculated as: the length of the fd's real
326 path, + 1 for a path separator, and the length of the input path (with the
327 fake path stripped off). */
328 *resolved_len = stripped_len + real_path_len + 1;
329 *resolved_path = uvwasi__malloc(uvwasi, *resolved_len + 1);
330
331 if (*resolved_path == NULL)
332 return UVWASI_ENOMEM;
333
334 res_path = *resolved_path;
335 stripped_path = (char*) path + fake_path_len;
336 memcpy(res_path, fd->real_path, real_path_len);
337 res_path += real_path_len;
338
339 if (stripped_len > 1 ||
340 (stripped_len == 1 && stripped_path[0] != '/')) {
341 if (stripped_path[0] != '/') {
342 *res_path = '/';
343 res_path++;
344 }
345
346 memcpy(res_path, stripped_path, stripped_len);
347 res_path += stripped_len;
348 }
349
350 *res_path = '\0';
351
352 #ifdef _WIN32
353 /* Replace / with \ on Windows. */
354 res_path = *resolved_path;
355 for (i = real_path_len; i < *resolved_len; i++) {
356 if (res_path[i] == '/')
357 res_path[i] = '\\';
358 }
359 #endif /* _WIN32 */
360
361 return UVWASI_ESUCCESS;
362 }
363
364
uvwasi__resolve_path(const uvwasi_t * uvwasi,const struct uvwasi_fd_wrap_t * fd,const char * path,uvwasi_size_t path_len,char ** resolved_path,uvwasi_lookupflags_t flags)365 uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
366 const struct uvwasi_fd_wrap_t* fd,
367 const char* path,
368 uvwasi_size_t path_len,
369 char** resolved_path,
370 uvwasi_lookupflags_t flags) {
371 uv_fs_t req;
372 uvwasi_errno_t err;
373 const char* input;
374 char* host_path;
375 char* normalized_path;
376 char* link_target;
377 uvwasi_size_t input_len;
378 uvwasi_size_t host_path_len;
379 uvwasi_size_t normalized_len;
380 int follow_count;
381 int r;
382
383 input = path;
384 input_len = path_len;
385 link_target = NULL;
386 follow_count = 0;
387 host_path = NULL;
388
389 start:
390 normalized_path = NULL;
391 err = UVWASI_ESUCCESS;
392
393 if (1 == uvwasi__is_absolute_path(input, input_len)) {
394 err = uvwasi__normalize_absolute_path(uvwasi,
395 fd,
396 input,
397 input_len,
398 &normalized_path,
399 &normalized_len);
400 } else {
401 err = uvwasi__normalize_relative_path(uvwasi,
402 fd,
403 input,
404 input_len,
405 &normalized_path,
406 &normalized_len);
407 }
408
409 if (err != UVWASI_ESUCCESS)
410 goto exit;
411
412 uvwasi__free(uvwasi, host_path);
413 err = uvwasi__resolve_path_to_host(uvwasi,
414 fd,
415 normalized_path,
416 normalized_len,
417 &host_path,
418 &host_path_len);
419 if (err != UVWASI_ESUCCESS)
420 goto exit;
421
422 if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW) == UVWASI_LOOKUP_SYMLINK_FOLLOW) {
423 r = uv_fs_readlink(NULL, &req, host_path, NULL);
424
425 if (r != 0) {
426 #ifdef _WIN32
427 /* uv_fs_readlink() returns UV__UNKNOWN on Windows. Try to get a better
428 error using uv_fs_stat(). */
429 if (r == UV__UNKNOWN) {
430 uv_fs_req_cleanup(&req);
431 r = uv_fs_stat(NULL, &req, host_path, NULL);
432
433 if (r == 0) {
434 if (uvwasi__stat_to_filetype(&req.statbuf) !=
435 UVWASI_FILETYPE_SYMBOLIC_LINK) {
436 r = UV_EINVAL;
437 }
438 }
439
440 /* Fall through. */
441 }
442 #endif /* _WIN32 */
443
444 /* Don't report UV_EINVAL or UV_ENOENT. They mean that either the file
445 does not exist, or it is not a symlink. Both are OK. */
446 if (r != UV_EINVAL && r != UV_ENOENT)
447 err = uvwasi__translate_uv_error(r);
448
449 uv_fs_req_cleanup(&req);
450 goto exit;
451 }
452
453 /* Clean up memory and follow the link, unless it's time to return ELOOP. */
454 follow_count++;
455 if (follow_count >= UVWASI__MAX_SYMLINK_FOLLOWS) {
456 uv_fs_req_cleanup(&req);
457 err = UVWASI_ELOOP;
458 goto exit;
459 }
460
461 input_len = strlen(req.ptr);
462 uvwasi__free(uvwasi, link_target);
463 link_target = uvwasi__malloc(uvwasi, input_len + 1);
464 if (link_target == NULL) {
465 uv_fs_req_cleanup(&req);
466 err = UVWASI_ENOMEM;
467 goto exit;
468 }
469
470 memcpy(link_target, req.ptr, input_len + 1);
471 input = link_target;
472 uvwasi__free(uvwasi, normalized_path);
473 uv_fs_req_cleanup(&req);
474 goto start;
475 }
476
477 exit:
478 if (err == UVWASI_ESUCCESS) {
479 *resolved_path = host_path;
480 } else {
481 *resolved_path = NULL;
482 uvwasi__free(uvwasi, host_path);
483 }
484
485 uvwasi__free(uvwasi, link_target);
486 uvwasi__free(uvwasi, normalized_path);
487 return err;
488 }
489