Bug Summary

File:src/usr.bin/xargs/xargs.c
Warning:line 207, column 17
Null pointer passed as 2nd argument to string comparison function

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name xargs.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 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/xargs/obj -resource-dir /usr/local/llvm16/lib/clang/16 -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/xargs/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.bin/xargs/xargs.c
1/* $OpenBSD: xargs.c,v 1.38 2023/12/23 15:58:58 millert Exp $ */
2/* $FreeBSD: xargs.c,v 1.51 2003/05/03 19:09:11 obrien Exp $ */
3
4/*-
5 * Copyright (c) 1990, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * John B. Roll Jr.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
36 */
37
38#include <sys/wait.h>
39
40#include <ctype.h>
41#include <err.h>
42#include <errno(*__errno()).h>
43#include <fcntl.h>
44#include <paths.h>
45#include <signal.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50#include <limits.h>
51
52#include "pathnames.h"
53
54static void parse_input(int, char *[]);
55static void prerun(int, char *[]);
56static int prompt(void);
57static void run(char **);
58static void usage(void);
59void strnsubst(char **, const char *, const char *, size_t);
60static void waitchildren(const char *, int);
61
62static char **av, **bxp, **ep, **endxp, **xp;
63static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
64static const char *eofstr;
65static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
66static int cnt, Iflag, jfound, Lflag, wasquoted, xflag, runeof = 1;
67static int curprocs, maxprocs;
68static size_t inpsize;
69
70extern char **environ;
71
72int
73main(int argc, char *argv[])
74{
75 long arg_max;
76 int ch, Jflag, nargs, nflag, nline;
77 size_t linelen;
78 char *endptr;
79 const char *errstr;
80
81 inpline = replstr = NULL((void *)0);
82 ep = environ;
83 eofstr = "";
84 Jflag = nflag = 0;
85
86 /*
87 * POSIX.2 limits the exec line length to ARG_MAX - 2K. Running that
88 * caused some E2BIG errors, so it was changed to ARG_MAX - 4K. Given
89 * that the smallest argument is 2 bytes in length, this means that
90 * the number of arguments is limited to:
91 *
92 * (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
93 *
94 * We arbitrarily limit the number of arguments to 5000. This is
95 * allowed by POSIX.2 as long as the resulting minimum exec line is
96 * at least LINE_MAX. Realloc'ing as necessary is possible, but
97 * probably not worthwhile.
98 */
99 nargs = 5000;
100 if ((arg_max = sysconf(_SC_ARG_MAX1)) == -1)
1
Assuming the condition is false
2
Taking false branch
101 errx(1, "sysconf(_SC_ARG_MAX) failed");
102
103 if (pledge("stdio rpath proc exec", NULL((void *)0)) == -1)
3
Assuming the condition is false
4
Taking false branch
104 err(1, "pledge");
105
106 nline = arg_max - 4 * 1024;
107 while (*ep != NULL((void *)0)) {
5
Assuming the condition is false
6
Loop condition is false. Execution continues on line 111
108 /* 1 byte for each '\0' */
109 nline -= strlen(*ep++) + 1 + sizeof(*ep);
110 }
111 maxprocs = 1;
112 while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:rs:tx")) != -1)
7
Value assigned to 'optarg'
8
Assuming the condition is true
9
Loop condition is true. Entering loop body
12
Execution continues on line 112
13
Assuming the condition is false
14
Loop condition is false. Execution continues on line 175
113 switch (ch) {
10
Control jumps to 'case 74:' at line 123
114 case 'E':
115 eofstr = optarg;
116 break;
117 case 'I':
118 Jflag = 0;
119 Iflag = 1;
120 Lflag = 1;
121 replstr = optarg;
122 break;
123 case 'J':
124 Iflag = 0;
125 Jflag = 1;
126 replstr = optarg;
11
Value assigned to 'replstr'
127 break;
128 case 'L':
129 Lflag = strtonum(optarg, 0, INT_MAX0x7fffffff, &errstr);
130 if (errstr)
131 errx(1, "-L %s: %s", optarg, errstr);
132 break;
133 case 'n':
134 nflag = 1;
135 nargs = strtonum(optarg, 1, INT_MAX0x7fffffff, &errstr);
136 if (errstr)
137 errx(1, "-n %s: %s", optarg, errstr);
138 break;
139 case 'o':
140 oflag = 1;
141 break;
142 case 'P':
143 maxprocs = strtonum(optarg, 1, INT_MAX0x7fffffff, &errstr);
144 if (errstr)
145 errx(1, "-P %s: %s", optarg, errstr);
146 break;
147 case 'p':
148 pflag = 1;
149 break;
150 case 'r':
151 runeof = 0;
152 break;
153 case 'R':
154 Rflag = strtol(optarg, &endptr, 10);
155 if (*endptr != '\0')
156 errx(1, "replacements must be a number");
157 break;
158 case 's':
159 nline = strtonum(optarg, 0, INT_MAX0x7fffffff, &errstr);
160 if (errstr)
161 errx(1, "-s %s: %s", optarg, errstr);
162 break;
163 case 't':
164 tflag = 1;
165 break;
166 case 'x':
167 xflag = 1;
168 break;
169 case '0':
170 zflag = 1;
171 break;
172 default:
173 usage();
174 }
175 argc -= optind;
176 argv += optind;
177
178 if (!Iflag
14.1
'Iflag' is 0
&& Rflag)
15
Assuming 'Rflag' is 0
179 usage();
180 if (Iflag
15.1
'Iflag' is 0
&& !Rflag)
181 Rflag = 5;
182 if (xflag && !nflag)
16
Assuming 'xflag' is 0
183 usage();
184 if (Iflag
16.1
'Iflag' is 0
|| Lflag)
17
Assuming 'Lflag' is 0
185 xflag = 1;
186 if (replstr != NULL((void *)0) && *replstr == '\0')
18
Assuming 'replstr' is equal to NULL
187 errx(1, "replstr may not be empty");
188
189 /*
190 * Allocate pointers for the utility name, the utility arguments,
191 * the maximum arguments to be read from stdin and the trailing
192 * NULL.
193 */
194 linelen = 1 + argc + nargs + 1;
195 if ((av = bxp = calloc(linelen, sizeof(char *))) == NULL((void *)0))
19
Assuming the condition is false
20
Taking false branch
196 err(1, NULL((void *)0));
197
198 /*
199 * Use the user's name for the utility as argv[0], just like the
200 * shell. Echo is the default. Set up pointers for the user's
201 * arguments.
202 */
203 if (*argv == NULL((void *)0))
21
Assuming the condition is false
204 cnt = strlen(*bxp++ = _PATH_ECHO"/bin/echo");
205 else {
206 do {
207 if (Jflag
21.1
'Jflag' is 1
&& strcmp(*argv, replstr) == 0) {
22
Null pointer passed as 2nd argument to string comparison function
208 char **avj;
209 jfound = 1;
210 argv++;
211 for (avj = argv; *avj; avj++)
212 cnt += strlen(*avj) + 1;
213 break;
214 }
215 cnt += strlen(*bxp++ = *argv) + 1;
216 } while (*++argv != NULL((void *)0));
217 }
218
219 /*
220 * Set up begin/end/traversing pointers into the array. The -n
221 * count doesn't include the trailing NULL pointer, so the malloc
222 * added in an extra slot.
223 */
224 endxp = (xp = bxp) + nargs;
225
226 /*
227 * Allocate buffer space for the arguments read from stdin and the
228 * trailing NULL. Buffer space is defined as the default or specified
229 * space, minus the length of the utility name and arguments. Set up
230 * begin/end/traversing pointers into the array. The -s count does
231 * include the trailing NULL, so the malloc didn't add in an extra
232 * slot.
233 */
234 nline -= cnt;
235 if (nline <= 0)
236 errx(1, "insufficient space for command");
237
238 if ((bbp = malloc((size_t)(nline + 1))) == NULL((void *)0))
239 err(1, NULL((void *)0));
240 ebp = (argp = p = bbp) + nline - 1;
241 for (;;)
242 parse_input(argc, argv);
243}
244
245static void
246parse_input(int argc, char *argv[])
247{
248 int hasblank = 0;
249 static int hadblank = 0;
250 int ch, foundeof = 0;
251 char **avj;
252
253 ch = getchar()(!__isthreaded ? (--((&__sF[0]))->_r < 0 ? __srget(
(&__sF[0])) : (int)(*((&__sF[0]))->_p++)) : (getc)
((&__sF[0])))
;
254 if (isblank(ch)) {
255 /* Quotes escape tabs and spaces. */
256 if (insingle || indouble)
257 goto addch;
258 hasblank = 1;
259 if (zflag)
260 goto addch;
261 goto arg2;
262 }
263
264 switch (ch) {
265 case EOF(-1):
266 /* No arguments since last exec. */
267 if (p == bbp) {
268 if (runeof)
269 prerun(0, av);
270 waitchildren(*argv, 1);
271 exit(rval);
272 }
273 goto arg1;
274 case '\0':
275 if (zflag) {
276 /*
277 * Increment 'count', so that nulls will be treated
278 * as end-of-line, as well as end-of-argument. This
279 * is needed so -0 works properly with -I and -L.
280 */
281 count++;
282 goto arg2;
283 }
284 goto addch;
285 case '\n':
286 if (zflag)
287 goto addch;
288 hasblank = 1;
289 if (hadblank == 0)
290 count++;
291
292 /* Quotes do not escape newlines. */
293arg1: if (insingle || indouble)
294 errx(1, "unterminated quote");
295arg2:
296 foundeof = *eofstr != '\0' &&
297 strcmp(argp, eofstr) == 0;
298
299 /*
300 * Do not make empty args unless they are quoted or
301 * we are run as "find -0" and not at EOF.
302 */
303 if (((zflag && ch != EOF(-1)) || argp != p || wasquoted) &&
304 !foundeof) {
305 *p++ = '\0';
306 *xp++ = argp;
307 if (Iflag) {
308 size_t curlen;
309
310 if (inpline == NULL((void *)0))
311 curlen = 0;
312 else {
313 /*
314 * If this string is not zero
315 * length, append a space for
316 * separation before the next
317 * argument.
318 */
319 if ((curlen = strlen(inpline)))
320 strlcat(inpline, " ", inpsize);
321 }
322 curlen++;
323 /*
324 * Allocate enough to hold what we will
325 * be holding in a second, and to append
326 * a space next time through, if we have
327 * to.
328 */
329 inpsize = curlen + 2 + strlen(argp);
330 inpline = realloc(inpline, inpsize);
331 if (inpline == NULL((void *)0))
332 errx(1, "realloc failed");
333 if (curlen == 1)
334 strlcpy(inpline, argp, inpsize);
335 else
336 strlcat(inpline, argp, inpsize);
337 }
338 }
339
340 /*
341 * If max'd out on args or buffer, or reached EOF,
342 * run the command. If xflag and max'd out on buffer
343 * but not on args, object. Having reached the limit
344 * of input lines, as specified by -L is the same as
345 * maxing out on arguments.
346 */
347 if (xp == endxp || p > ebp || ch == EOF(-1) ||
348 (Lflag <= count && xflag) || foundeof) {
349 if (xflag && xp != endxp && p > ebp)
350 errx(1, "insufficient space for arguments");
351 if (jfound) {
352 for (avj = argv; *avj; avj++)
353 *xp++ = *avj;
354 }
355 prerun(argc, av);
356 if (ch == EOF(-1) || foundeof) {
357 waitchildren(*argv, 1);
358 exit(rval);
359 }
360 p = bbp;
361 xp = bxp;
362 count = 0;
363 }
364 argp = p;
365 wasquoted = 0;
366 break;
367 case '\'':
368 if (indouble || zflag)
369 goto addch;
370 insingle = !insingle;
371 wasquoted = 1;
372 break;
373 case '"':
374 if (insingle || zflag)
375 goto addch;
376 indouble = !indouble;
377 wasquoted = 1;
378 break;
379 case '\\':
380 if (zflag)
381 goto addch;
382 /* Backslash escapes anything, is escaped by quotes. */
383 if (!insingle && !indouble && (ch = getchar()(!__isthreaded ? (--((&__sF[0]))->_r < 0 ? __srget(
(&__sF[0])) : (int)(*((&__sF[0]))->_p++)) : (getc)
((&__sF[0])))
) == EOF(-1))
384 errx(1, "backslash at EOF");
385 /* FALLTHROUGH */
386 default:
387addch: if (p < ebp) {
388 *p++ = ch;
389 break;
390 }
391
392 /* If only one argument, not enough buffer space. */
393 if (bxp == xp)
394 errx(1, "insufficient space for argument");
395 /* Didn't hit argument limit, so if xflag object. */
396 if (xflag)
397 errx(1, "insufficient space for arguments");
398
399 if (jfound) {
400 for (avj = argv; *avj; avj++)
401 *xp++ = *avj;
402 }
403 prerun(argc, av);
404 xp = bxp;
405 cnt = ebp - argp;
406 memmove(bbp, argp, (size_t)cnt);
407 p = (argp = bbp) + cnt;
408 *p++ = ch;
409 break;
410 }
411 hadblank = hasblank;
412}
413
414/*
415 * Do things necessary before run()'ing, such as -I substitution,
416 * and then call run().
417 */
418static void
419prerun(int argc, char *argv[])
420{
421 char **tmp, **tmp2, **avj;
422 int repls;
423
424 repls = Rflag;
425 runeof = 0;
426
427 if (argc == 0 || repls == 0) {
428 *xp = NULL((void *)0);
429 run(argv);
430 return;
431 }
432
433 avj = argv;
434
435 /*
436 * Allocate memory to hold the argument list, and
437 * a NULL at the tail.
438 */
439 tmp = calloc(argc + 1, sizeof(char *));
440 if (tmp == NULL((void *)0))
441 err(1, NULL((void *)0));
442 tmp2 = tmp;
443
444 /*
445 * Save the first argument and iterate over it, we
446 * cannot do strnsubst() to it.
447 */
448 if ((*tmp++ = strdup(*avj++)) == NULL((void *)0))
449 err(1, NULL((void *)0));
450
451 /*
452 * For each argument to utility, if we have not used up
453 * the number of replacements we are allowed to do, and
454 * if the argument contains at least one occurrence of
455 * replstr, call strnsubst(), else just save the string.
456 * Iterations over elements of avj and tmp are done
457 * where appropriate.
458 */
459 while (--argc) {
460 *tmp = *avj++;
461 if (repls && strstr(*tmp, replstr) != NULL((void *)0)) {
462 strnsubst(tmp++, replstr, inpline, (size_t)255);
463 if (repls > 0)
464 repls--;
465 } else {
466 if ((*tmp = strdup(*tmp)) == NULL((void *)0))
467 err(1, NULL((void *)0));
468 tmp++;
469 }
470 }
471
472 /*
473 * Run it.
474 */
475 *tmp = NULL((void *)0);
476 run(tmp2);
477
478 /*
479 * Walk from the tail to the head, free along the way.
480 */
481 for (; tmp2 != tmp; tmp--)
482 free(*tmp);
483 /*
484 * Now free the list itself.
485 */
486 free(tmp2);
487
488 /*
489 * Free the input line buffer, if we have one.
490 */
491 free(inpline);
492 inpline = NULL((void *)0);
493}
494
495static void
496run(char **argv)
497{
498 pid_t pid;
499 int fd;
500 char **avec;
501
502 /*
503 * If the user wants to be notified of each command before it is
504 * executed, notify them. If they want the notification to be
505 * followed by a prompt, then prompt them.
506 */
507 if (tflag || pflag) {
508 fprintf(stderr(&__sF[2]), "%s", *argv);
509 for (avec = argv + 1; *avec != NULL((void *)0); ++avec)
510 fprintf(stderr(&__sF[2]), " %s", *avec);
511 /*
512 * If the user has asked to be prompted, do so.
513 */
514 if (pflag)
515 /*
516 * If they asked not to exec, return without execution
517 * but if they asked to, go to the execution. If we
518 * could not open their tty, break the switch and drop
519 * back to -t behaviour.
520 */
521 switch (prompt()) {
522 case 0:
523 return;
524 case 1:
525 goto exec;
526 case 2:
527 break;
528 }
529 fprintf(stderr(&__sF[2]), "\n");
530 fflush(stderr(&__sF[2]));
531 }
532exec:
533 switch (pid = vfork()) {
534 case -1:
535 err(1, "vfork");
536 case 0:
537 if (oflag) {
538 if ((fd = open(_PATH_TTY"/dev/tty", O_RDONLY0x0000)) == -1) {
539 warn("can't open /dev/tty");
540 _exit(1);
541 }
542 } else {
543 fd = open(_PATH_DEVNULL"/dev/null", O_RDONLY0x0000);
544 }
545 if (fd > STDIN_FILENO0) {
546 if (dup2(fd, STDIN_FILENO0) != 0) {
547 warn("can't dup2 to stdin");
548 _exit(1);
549 }
550 close(fd);
551 }
552 execvp(argv[0], argv);
553 warn("%s", argv[0]);
554 _exit(errno(*__errno()) == ENOENT2 ? 127 : 126);
555 }
556 curprocs++;
557 waitchildren(*argv, 0);
558}
559
560static void
561waitchildren(const char *name, int waitall)
562{
563 pid_t pid;
564 int status;
565
566 while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
567 WNOHANG0x01 : 0)) > 0) {
568 curprocs--;
569 /*
570 * According to POSIX, we have to exit if the utility exits
571 * with a 255 status, or is interrupted by a signal.
572 * We are allowed to return any exit status between 1 and
573 * 125 in these cases, but we'll use 124 and 125, the same
574 * values used by GNU xargs.
575 */
576 if (WIFEXITED(status)(((status) & 0177) == 0)) {
577 if (WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) == 255) {
578 warnx("%s exited with status 255", name);
579 exit(124);
580 } else if (WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) == 127 ||
581 WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) == 126) {
582 exit(WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff));
583 } else if (WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) != 0) {
584 rval = 123;
585 }
586 } else if (WIFSIGNALED(status)(((status) & 0177) != 0177 && ((status) & 0177
) != 0)
) {
587 if (WTERMSIG(status)(((status) & 0177)) != SIGPIPE13) {
588 if (WTERMSIG(status)(((status) & 0177)) < NSIG33)
589 warnx("%s terminated by SIG%s", name,
590 sys_signame[WTERMSIG(status)(((status) & 0177))]);
591 else
592 warnx("%s terminated by signal %d",
593 name, WTERMSIG(status)(((status) & 0177)));
594 }
595 exit(125);
596 }
597 }
598 if (pid == -1 && errno(*__errno()) != ECHILD10)
599 err(1, "waitpid");
600}
601
602/*
603 * Prompt the user about running a command.
604 */
605static int
606prompt(void)
607{
608 size_t rsize;
609 char *response;
610 FILE *ttyfp;
611 int doit = 0;
612
613 if ((ttyfp = fopen(_PATH_TTY"/dev/tty", "r")) == NULL((void *)0))
614 return (2); /* Indicate that the TTY failed to open. */
615 fprintf(stderr(&__sF[2]), "?...");
616 fflush(stderr(&__sF[2]));
617 response = fgetln(ttyfp, &rsize);
618 doit = response != NULL((void *)0) && (*response == 'y' || *response == 'Y');
619 fclose(ttyfp);
620 return (doit);
621}
622
623static void
624usage(void)
625{
626 fprintf(stderr(&__sF[2]),
627"usage: xargs [-0oprt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]\n"
628" [-L number] [-n number [-x]] [-P maxprocs] [-s size]\n"
629" [utility [argument ...]]\n");
630 exit(1);
631}