• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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