Bug Summary

File:out/../deps/uvwasi/src/path_resolver.c
Warning:line 341, column 9
Array access (from variable 'stripped_path') results in a null pointer dereference

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name path_resolver.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/maurizio/node-v18.6.0/out -resource-dir /usr/local/lib/clang/16.0.0 -D V8_DEPRECATION_WARNINGS -D V8_IMMINENT_DEPRECATION_WARNINGS -D _GLIBCXX_USE_CXX11_ABI=1 -D NODE_OPENSSL_CONF_NAME=nodejs_conf -D NODE_OPENSSL_HAS_QUIC -D __STDC_FORMAT_MACROS -D OPENSSL_NO_PINSHARED -D OPENSSL_THREADS -D _GNU_SOURCE -D _POSIX_C_SOURCE=200112 -D _LARGEFILE_SOURCE -D _FILE_OFFSET_BITS=64 -I ../deps/uvwasi/include -I ../deps/uv/include -internal-isystem /usr/local/lib/clang/16.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-unused-parameter -fdebug-compilation-dir=/home/maurizio/node-v18.6.0/out -ferror-limit 19 -fvisibility hidden -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-08-22-142216-507842-1 -x c ../deps/uvwasi/src/path_resolver.c
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_FOLLOWS32 32
10
11#ifndef _WIN32
12# define IS_SLASH(c)((c) == '/') ((c) == '/')
13#else
14# define IS_SLASH(c)((c) == '/') ((c) == '/' || (c) == '\\')
15#endif /* _WIN32 */
16
17
18static 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((void*)0) && path_len > 0 && path[0] == '/';
21}
22
23
24static char* uvwasi__strchr_slash(const char* s) {
25 /* strchr() that identifies /, as well as \ on Windows. */
26 do {
27 if (IS_SLASH(*s)((*s) == '/'))
28 return (char*) s;
29 } while (*s++);
30
31 return NULL((void*)0);
32}
33
34
35uvwasi_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_ENOBUFS42;
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((void*)0); cur = next + 1) {
53 next = uvwasi__strchr_slash(cur);
54 cur_len = (next == NULL((void*)0)) ? strlen(cur) : (size_t) (next - cur);
55
56 if (cur_len == 0) {
57 if (ptr == normalized_path && next != NULL((void*)0) && 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) == '/') && 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((void*)0))
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_ESUCCESS0;
124}
125
126
127static 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
172static 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((void*)0);
191 *normalized_len = 0;
192 abs_size = path_len + 1;
193 abs_path = uvwasi__malloc(uvwasi, abs_size);
194 if (abs_path == NULL((void*)0)) {
195 err = UVWASI_ENOMEM48;
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_ESUCCESS0)
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_ENOTCAPABLE76;
210 goto exit;
211 }
212
213 *normalized_path = abs_path;
214 *normalized_len = abs_size - 1;
215 return UVWASI_ESUCCESS0;
216
217exit:
218 uvwasi__free(uvwasi, abs_path);
219 return err;
220}
221
222
223static 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((void*)0);
3
Null pointer value stored to 'normalized_path'
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((void*)0))
4
Assuming 'combined' is not equal to NULL
5
Taking false branch
251 return UVWASI_ENOMEM48;
252
253 normalized = uvwasi__malloc(uvwasi, combined_size);
254 if (normalized == NULL((void*)0)) {
6
Assuming 'normalized' is not equal to NULL
7
Taking false branch
255 err = UVWASI_ENOMEM48;
256 goto exit;
257 }
258
259 r = snprintf(combined, combined_size, "%s/%s", fd->normalized_path, path);
260 if (r <= 0) {
8
Assuming 'r' is <= 0
9
Taking true branch
261 err = uvwasi__translate_uv_error(uv_translate_sys_error(errno(*__errno_location ())));
262 goto exit;
10
Control jumps to line 289
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_ESUCCESS0)
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_ENOTCAPABLE76;
281 goto exit;
282 }
283
284 err = UVWASI_ESUCCESS0;
285 *normalized_path = normalized;
286 *normalized_len = norm_len;
287
288exit:
289 if (err != UVWASI_ESUCCESS0)
11
Assuming 'err' is equal to UVWASI_ESUCCESS
12
Taking false branch
290 uvwasi__free(uvwasi, normalized);
291
292 uvwasi__free(uvwasi, combined);
293 return err;
294}
295
296
297static 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] == '.') {
17
Assuming 'fake_path_len' is not equal to 1
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((void*)0))
18
Assuming the condition is false
19
Taking false branch
332 return UVWASI_ENOMEM48;
333
334 res_path = *resolved_path;
335 stripped_path = (char*) path + fake_path_len;
20
Null pointer value stored to 'stripped_path'
336 memcpy(res_path, fd->real_path, real_path_len);
337 res_path += real_path_len;
338
339 if (stripped_len > 1 ||
21
Assuming 'stripped_len' is > 1
340 (stripped_len == 1 && stripped_path[0] != '/')) {
341 if (stripped_path[0] != '/') {
22
Array access (from variable 'stripped_path') results in a null pointer dereference
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_ESUCCESS0;
362}
363
364
365uvwasi_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((void*)0);
386 follow_count = 0;
387 host_path = NULL((void*)0);
388
389start:
390 normalized_path = NULL((void*)0);
391 err = UVWASI_ESUCCESS0;
392
393 if (1 == uvwasi__is_absolute_path(input, input_len)) {
1
Taking false branch
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,
2
Calling 'uvwasi__normalize_relative_path'
13
Returning from 'uvwasi__normalize_relative_path'
402 fd,
403 input,
404 input_len,
405 &normalized_path,
406 &normalized_len);
407 }
408
409 if (err
13.1
'err' is equal to UVWASI_ESUCCESS
!= UVWASI_ESUCCESS0)
14
Taking false branch
410 goto exit;
411
412 uvwasi__free(uvwasi, host_path);
413 err = uvwasi__resolve_path_to_host(uvwasi,
16
Calling 'uvwasi__resolve_path_to_host'
414 fd,
415 normalized_path,
15
Passing null pointer value via 3rd parameter 'path'
416 normalized_len,
417 &host_path,
418 &host_path_len);
419 if (err != UVWASI_ESUCCESS0)
420 goto exit;
421
422 if ((flags & UVWASI_LOOKUP_SYMLINK_FOLLOW(1 << 0)) == UVWASI_LOOKUP_SYMLINK_FOLLOW(1 << 0)) {
423 r = uv_fs_readlink(NULL((void*)0), &req, host_path, NULL((void*)0));
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(-4094)) {
430 uv_fs_req_cleanup(&req);
431 r = uv_fs_stat(NULL((void*)0), &req, host_path, NULL((void*)0));
432
433 if (r == 0) {
434 if (uvwasi__stat_to_filetype(&req.statbuf) !=
435 UVWASI_FILETYPE_SYMBOLIC_LINK7) {
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_FOLLOWS32) {
456 uv_fs_req_cleanup(&req);
457 err = UVWASI_ELOOP32;
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((void*)0)) {
465 uv_fs_req_cleanup(&req);
466 err = UVWASI_ENOMEM48;
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
477exit:
478 if (err == UVWASI_ESUCCESS0) {
479 *resolved_path = host_path;
480 } else {
481 *resolved_path = NULL((void*)0);
482 uvwasi__free(uvwasi, host_path);
483 }
484
485 uvwasi__free(uvwasi, link_target);
486 uvwasi__free(uvwasi, normalized_path);
487 return err;
488}