|
7 | 7 | # include <unistd.h>
|
8 | 8 | # include <dirent.h>
|
9 | 9 | # include <time.h>
|
10 |
| -# define SLASH '/' |
11 |
| -# define SLASH_STR "/" |
12 | 10 | # define IS_SLASH(c) ((c) == '/')
|
13 | 11 | #else
|
14 |
| -# define SLASH '\\' |
15 |
| -# define SLASH_STR "\\" |
16 | 12 | # define IS_SLASH(c) ((c) == '/' || (c) == '\\')
|
17 | 13 | #endif /* _WIN32 */
|
18 | 14 |
|
19 | 15 | #define UVWASI__READDIR_NUM_ENTRIES 1
|
| 16 | +#define UVWASI__MAX_SYMLINK_FOLLOWS 32 |
20 | 17 |
|
21 | 18 | #include "uvwasi.h"
|
22 | 19 | #include "uvwasi_alloc.h"
|
@@ -86,120 +83,312 @@ static int uvwasi__is_absolute_path(const char* path, size_t path_len) {
|
86 | 83 | }
|
87 | 84 |
|
88 | 85 |
|
| 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 | + |
89 | 265 | static uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi,
|
90 | 266 | const struct uvwasi_fd_wrap_t* fd,
|
91 | 267 | const char* path,
|
92 | 268 | size_t path_len,
|
93 | 269 | char* resolved_path,
|
94 | 270 | uvwasi_lookupflags_t flags) {
|
95 |
| - uv_fs_t realpath_req; |
| 271 | + uv_fs_t req; |
96 | 272 | 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; |
103 | 281 | int r;
|
104 |
| -#ifdef _WIN32 |
105 |
| - int i; |
106 |
| -#endif /* _WIN32 */ |
107 | 282 |
|
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; |
110 | 288 |
|
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; |
119 | 292 |
|
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); |
121 | 300 | } 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); |
135 | 307 | }
|
136 | 308 |
|
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; |
144 | 311 |
|
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; |
150 | 321 |
|
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); |
157 | 332 |
|
| 333 | + if (r != 0) { |
158 | 334 | #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 | + } |
163 | 350 | #endif /* _WIN32 */
|
164 |
| - r = sprintf(ptr, "%c%s", SLASH, tok); |
165 | 351 |
|
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); |
168 | 358 | goto exit;
|
169 | 359 | }
|
170 | 360 |
|
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; |
189 | 366 | goto exit;
|
190 | 367 | }
|
191 | 368 |
|
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 | + } |
194 | 377 |
|
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; |
199 | 383 | }
|
200 | 384 |
|
201 | 385 | 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); |
203 | 392 | return err;
|
204 | 393 | }
|
205 | 394 |
|
|
0 commit comments