Bug Summary

File:src/usr.sbin/cron/do_command.c
Warning:line 470, column 15
The left operand of '>' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name do_command.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -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 -fno-rounding-math -mconstructor-aliases -munwind-tables -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.sbin/cron/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.sbin/cron -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.sbin/cron/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -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/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.sbin/cron/do_command.c
1/* $OpenBSD: do_command.c,v 1.61 2020/04/16 17:51:56 millert Exp $ */
2
3/* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6 * Copyright (c) 2018 Job Snijders <job@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
18 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <sys/types.h>
22#include <sys/wait.h>
23
24#include <bitstring.h> /* for structs.h */
25#include <bsd_auth.h>
26#include <ctype.h>
27#include <err.h>
28#include <errno(*__errno()).h>
29#include <fcntl.h>
30#include <limits.h>
31#include <login_cap.h>
32#include <pwd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <syslog.h>
38#include <time.h> /* for structs.h */
39#include <unistd.h>
40#include <vis.h>
41
42#include "config.h"
43#include "pathnames.h"
44#include "macros.h"
45#include "structs.h"
46#include "funcs.h"
47
48static void child_process(entry *, user *);
49
50pid_t
51do_command(entry *e, user *u)
52{
53 pid_t pid;
54
55 /* fork to become asynchronous -- parent process is done immediately,
56 * and continues to run the normal cron code, which means return to
57 * tick(). the child and grandchild don't leave this function, alive.
58 *
59 * vfork() is unsuitable, since we have much to do, and the parent
60 * needs to be able to run off and fork other processes.
61 */
62 switch ((pid = fork())) {
63 case -1:
64 syslog(LOG_ERR3, "(CRON) CAN'T FORK (%m)");
65 break;
66 case 0:
67 /* child process */
68 child_process(e, u);
69 _exit(EXIT_SUCCESS0);
70 break;
71 default:
72 /* parent process */
73 if ((e->flags & SINGLE_JOB0x80) == 0)
74 pid = -1;
75 break;
76 }
77
78 /* only return pid if a singleton */
79 return (pid);
80}
81
82static void
83child_process(entry *e, user *u)
84{
85 FILE *in;
86 int stdin_pipe[2], stdout_pipe[2];
87 char **p, *input_data, *usernm;
88 auth_session_t *as;
89 login_cap_t *lc;
90 extern char **environ;
91
92 /* mark ourselves as different to PS command watchers */
93 setproctitle("running job");
94
95 /* close sockets from parent (i.e. cronSock) */
96 closefrom(3);
97
98 /* discover some useful and important environment settings
99 */
100 usernm = e->pwd->pw_name;
101
102 /* our parent is watching for our death by catching SIGCHLD. we
103 * do not care to watch for our children's deaths this way -- we
104 * use wait() explicitly. so we have to reset the signal (which
105 * was inherited from the parent).
106 */
107 (void) signal(SIGCHLD20, SIG_DFL(void (*)(int))0);
108
109 /* create some pipes to talk to our future child
110 */
111 if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
1
Assuming the condition is false
2
Assuming the condition is false
3
Taking false branch
112 syslog(LOG_ERR3, "(CRON) PIPE (%m)");
113 _exit(EXIT_FAILURE1);
114 }
115
116 /* since we are a forked process, we can diddle the command string
117 * we were passed -- nobody else is going to use it again, right?
118 *
119 * if a % is present in the command, previous characters are the
120 * command, and subsequent characters are the additional input to
121 * the command. An escaped % will have the escape character stripped
122 * from it. Subsequent %'s will be transformed into newlines,
123 * but that happens later.
124 */
125 /*local*/{
126 int escaped = FALSE0;
127 int ch;
128 char *p;
129
130 for (input_data = p = e->cmd;
5
Loop condition is false. Execution continues on line 150
131 (ch = *input_data) != '\0';
4
Assuming the condition is false
132 input_data++, p++) {
133 if (p != input_data)
134 *p = ch;
135 if (escaped) {
136 if (ch == '%')
137 *--p = ch;
138 escaped = FALSE0;
139 continue;
140 }
141 if (ch == '\\') {
142 escaped = TRUE1;
143 continue;
144 }
145 if (ch == '%') {
146 *input_data++ = '\0';
147 break;
148 }
149 }
150 *p = '\0';
151 }
152
153 /* fork again, this time so we can exec the user's command.
154 */
155
156 pid_t jobpid;
157 switch (jobpid = fork()) {
6
Control jumps to the 'default' case at line 266
158 case -1:
159 syslog(LOG_ERR3, "(CRON) CAN'T FORK (%m)");
160 _exit(EXIT_FAILURE1);
161 /*NOTREACHED*/
162 case 0:
163 /* write a log message. we've waited this long to do it
164 * because it was not until now that we knew the PID that
165 * the actual user command shell was going to get and the
166 * PID is part of the log message.
167 */
168 if ((e->flags & DONT_LOG0x20) == 0) {
169 char *x;
170 if (stravis(&x, e->cmd, 0) != -1) {
171 syslog(LOG_INFO6, "(%s) CMD (%s)", usernm, x);
172 free(x);
173 }
174 }
175
176 /* get new pgrp, void tty, etc.
177 */
178 (void) setsid();
179
180 /* close the pipe ends that we won't use. this doesn't affect
181 * the parent, who has to read and write them; it keeps the
182 * kernel from recording us as a potential client TWICE --
183 * which would keep it from sending SIGPIPE in otherwise
184 * appropriate circumstances.
185 */
186 close(stdin_pipe[WRITE_PIPE1]);
187 close(stdout_pipe[READ_PIPE0]);
188
189 /* grandchild process. make std{in,out} be the ends of
190 * pipes opened by our daddy; make stderr go to stdout.
191 */
192 if (stdin_pipe[READ_PIPE0] != STDIN_FILENO0) {
193 dup2(stdin_pipe[READ_PIPE0], STDIN_FILENO0);
194 close(stdin_pipe[READ_PIPE0]);
195 }
196 if (stdout_pipe[WRITE_PIPE1] != STDOUT_FILENO1) {
197 dup2(stdout_pipe[WRITE_PIPE1], STDOUT_FILENO1);
198 close(stdout_pipe[WRITE_PIPE1]);
199 }
200 dup2(STDOUT_FILENO1, STDERR_FILENO2);
201
202 /*
203 * From this point on, anything written to stderr will be
204 * mailed to the user as output.
205 */
206
207 /* XXX - should just pass in a login_cap_t * */
208 if ((lc = login_getclass(e->pwd->pw_class)) == NULL((void *)0)) {
209 warnx("unable to get login class for %s",
210 e->pwd->pw_name);
211 syslog(LOG_ERR3, "(CRON) CAN'T GET LOGIN CLASS (%s)",
212 e->pwd->pw_name);
213 _exit(EXIT_FAILURE1);
214 }
215 if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL0x00ff) == -1) {
216 warn("setusercontext failed for %s", e->pwd->pw_name);
217 syslog(LOG_ERR3, "(%s) SETUSERCONTEXT FAILED (%m)",
218 e->pwd->pw_name);
219 _exit(EXIT_FAILURE1);
220 }
221 as = auth_open();
222 if (as == NULL((void *)0) || auth_setpwd(as, e->pwd) != 0) {
223 warn("auth_setpwd");
224 syslog(LOG_ERR3, "(%s) AUTH_SETPWD FAILED (%m)",
225 e->pwd->pw_name);
226 _exit(EXIT_FAILURE1);
227 }
228 if (auth_approval(as, lc, usernm, "cron") <= 0) {
229 warnx("approval failed for %s", e->pwd->pw_name);
230 syslog(LOG_ERR3, "(%s) APPROVAL FAILED (cron)",
231 e->pwd->pw_name);
232 _exit(EXIT_FAILURE1);
233 }
234 auth_close(as);
235 login_close(lc);
236
237 /* If no PATH specified in crontab file but
238 * we just added one via login.conf, add it to
239 * the crontab environment.
240 */
241 if (env_get("PATH", e->envp) == NULL((void *)0) && environ != NULL((void *)0)) {
242 for (p = environ; *p; p++) {
243 if (strncmp(*p, "PATH=", 5) == 0) {
244 e->envp = env_set(e->envp, *p);
245 break;
246 }
247 }
248 }
249 chdir(env_get("HOME", e->envp));
250
251 (void) signal(SIGPIPE13, SIG_DFL(void (*)(int))0);
252
253 /*
254 * Exec the command.
255 */
256 {
257 char *shell = env_get("SHELL", e->envp);
258
259 execle(shell, shell, "-c", e->cmd, (char *)NULL((void *)0), e->envp);
260 warn("unable to execute %s", shell);
261 syslog(LOG_ERR3, "(%s) CAN'T EXEC (%s: %m)",
262 e->pwd->pw_name, shell);
263 _exit(EXIT_FAILURE1);
264 }
265 break;
266 default:
267 /* parent process */
268 break;
7
Execution continues on line 278
269 }
270
271 /* middle process, child of original cron, parent of process running
272 * the user's command.
273 */
274
275 /* close the ends of the pipe that will only be referenced in the
276 * grandchild process...
277 */
278 close(stdin_pipe[READ_PIPE0]);
279 close(stdout_pipe[WRITE_PIPE1]);
280
281 /*
282 * write, to the pipe connected to child's stdin, any input specified
283 * after a % in the crontab entry. while we copy, convert any
284 * additional %'s to newlines. when done, if some characters were
285 * written and the last one wasn't a newline, write a newline.
286 *
287 * Note that if the input data won't fit into one pipe buffer (2K
288 * or 4K on most BSD systems), and the child doesn't read its stdin,
289 * we would block here. thus we must fork again.
290 */
291
292 pid_t stdinjob;
8
'stdinjob' declared without an initial value
293 if (*input_data && (stdinjob = fork()) == 0) {
294 FILE *out = fdopen(stdin_pipe[WRITE_PIPE1], "w");
295 int need_newline = FALSE0;
296 int escaped = FALSE0;
297 int ch;
298
299 /* close the pipe we don't use, since we inherited it and
300 * are part of its reference count now.
301 */
302 close(stdout_pipe[READ_PIPE0]);
303
304 /* translation:
305 * \% -> %
306 * % -> \n
307 * \x -> \x for all x != %
308 */
309 while ((ch = *input_data++) != '\0') {
310 if (escaped) {
311 if (ch != '%')
312 putc('\\', out)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out));
313 } else {
314 if (ch == '%')
315 ch = '\n';
316 }
317
318 if (!(escaped = (ch == '\\'))) {
319 putc(ch, out)(!__isthreaded ? __sputc(ch, out) : (putc)(ch, out));
320 need_newline = (ch != '\n');
321 }
322 }
323 if (escaped)
324 putc('\\', out)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out));
325 if (need_newline)
326 putc('\n', out)(!__isthreaded ? __sputc('\n', out) : (putc)('\n', out));
327
328 /* close the pipe, causing an EOF condition. fclose causes
329 * stdin_pipe[WRITE_PIPE] to be closed, too.
330 */
331 fclose(out);
332
333 _exit(EXIT_SUCCESS0);
334 }
335
336 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
337 * ernie back there has it open and will close it when he's done.
338 */
339 close(stdin_pipe[WRITE_PIPE1]);
340
341 /*
342 * read output from the grandchild. it's stderr has been redirected to
343 * it's stdout, which has been redirected to our pipe. if there is any
344 * output, we'll be mailing it to the user whose crontab this is...
345 * when the grandchild exits, we'll get EOF.
346 */
347
348 (void) signal(SIGPIPE13, SIG_IGN(void (*)(int))1);
349 in = fdopen(stdout_pipe[READ_PIPE0], "r");
350
351 char *mailto;
352 FILE *mail = NULL((void *)0);
353 int status = 0;
354 pid_t mailpid;
355 size_t bytes = 1;
356
357 if (in != NULL((void *)0)) {
9
Assuming 'in' is equal to NULL
10
Taking false branch
358 int ch = getc(in)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)(
*(in)->_p++)) : (getc)(in))
;
359
360 if (ch != EOF(-1)) {
361
362 /* get name of recipient. this is MAILTO if set to a
363 * valid local username; USER otherwise.
364 */
365 mailto = env_get("MAILTO", e->envp);
366 if (!mailto) {
367 /* MAILTO not present, set to USER.
368 */
369 mailto = usernm;
370 } else if (!*mailto || !safe_p(usernm, mailto)) {
371 mailto = NULL((void *)0);
372 }
373
374 /* if we are supposed to be mailing, MAILTO will
375 * be non-NULL. only in this case should we set
376 * up the mail command and subjects and stuff...
377 */
378
379 if (mailto) {
380 char **env;
381 char mailcmd[MAX_COMMAND1000];
382 char hostname[HOST_NAME_MAX255 + 1];
383
384 gethostname(hostname, sizeof(hostname));
385 if (snprintf(mailcmd, sizeof mailcmd, MAILFMT"%s -FCronDaemon -odi -oem -oi -t",
386 MAILARG"/usr/sbin/sendmail") >= sizeof mailcmd) {
387 syslog(LOG_ERR3,
388 "(%s) ERROR (mailcmd too long)",
389 e->pwd->pw_name);
390 (void) _exit(EXIT_FAILURE1);
391 }
392 if (!(mail = cron_popen(mailcmd, "w", e->pwd,
393 &mailpid))) {
394 syslog(LOG_ERR3, "(%s) POPEN (%s)",
395 e->pwd->pw_name, mailcmd);
396 (void) _exit(EXIT_FAILURE1);
397 }
398 fprintf(mail, "From: root (Cron Daemon)\n");
399 fprintf(mail, "To: %s\n", mailto);
400 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
401 usernm, first_word(hostname, "."),
402 e->cmd);
403 fprintf(mail, "Auto-Submitted: auto-generated\n");
404 for (env = e->envp; *env; env++)
405 fprintf(mail, "X-Cron-Env: <%s>\n",
406 *env);
407 fprintf(mail, "\n");
408
409 /* this was the first char from the pipe
410 */
411 fputc(ch, mail);
412 }
413
414 /* we have to read the input pipe no matter whether
415 * we mail or not, but obviously we only write to
416 * mail pipe if we ARE mailing.
417 */
418
419 while (EOF(-1) != (ch = getc(in)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)(
*(in)->_p++)) : (getc)(in))
)) {
420 bytes++;
421 if (mail)
422 fputc(ch, mail);
423 }
424
425 } /*if data from grandchild*/
426
427 fclose(in); /* also closes stdout_pipe[READ_PIPE] */
428 }
429
430 /* wait for children to die.
431 */
432 int waiter;
433 if (jobpid > 0) {
11
Assuming 'jobpid' is <= 0
12
Taking false branch
434 while (waitpid(jobpid, &waiter, 0) == -1 && errno(*__errno()) == EINTR4)
435 ;
436
437 /* If everything went well, and -n was set, _and_ we have mail,
438 * we won't be mailing... so shoot the messenger!
439 */
440 if (WIFEXITED(waiter)(((waiter) & 0177) == 0) && WEXITSTATUS(waiter)(int)(((unsigned)(waiter) >> 8) & 0xff) == 0
441 && (e->flags & MAIL_WHEN_ERR0x40) == MAIL_WHEN_ERR0x40
442 && mail) {
443 kill(mailpid, SIGKILL9);
444 (void)fclose(mail);
445 mail = NULL((void *)0);
446 }
447
448 /* only close pipe if we opened it -- i.e., we're mailing... */
449 if (mail) {
450 /*
451 * Note: the pclose will probably see the termination
452 * of the grandchild in addition to the mail process,
453 * since it (the grandchild) is likely to exit after
454 * closing its stdout.
455 */
456 status = cron_pclose(mail, mailpid);
457 }
458
459 /* if there was output and we could not mail it,
460 * log the facts so the poor user can figure out
461 * what's going on.
462 */
463 if (mail && status) {
464 syslog(LOG_NOTICE5, "(%s) MAIL (mailed %zu byte"
465 "%s of output but got status 0x%04x)", usernm,
466 bytes, (bytes == 1) ? "" : "s", status);
467 }
468 }
469
470 if (stdinjob > 0)
13
The left operand of '>' is a garbage value
471 while (waitpid(stdinjob, &waiter, 0) == -1 && errno(*__errno()) == EINTR4)
472 ;
473}
474
475int
476safe_p(const char *usernm, const char *s)
477{
478 static const char safe_delim[] = "@!:%+-.,"; /* conservative! */
479 const char *t;
480 int ch, first;
481
482 for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) {
483 if (isascii(ch) && isprint(ch) &&
484 (isalnum(ch) || ch == '_' ||
485 (!first && strchr(safe_delim, ch))))
486 continue;
487 syslog(LOG_WARNING4, "(%s) UNSAFE (%s)", usernm, s);
488 return (FALSE0);
489 }
490 return (TRUE1);
491}