Bug Summary

File:src/libexec/mail.local/mail.local.c
Warning:line 140, column 7
Access to field '_flags' results in a dereference of a null pointer (loaded from variable 'fp')

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 mail.local.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/libexec/mail.local/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/libexec/mail.local/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/libexec/mail.local/mail.local.c
1/* $OpenBSD: mail.local.c,v 1.42 2023/06/05 08:07:18 op Exp $ */
2
3/*-
4 * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com>
5 * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu>
6 * Copyright (c) 1990 The Regents of the University of California.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <sys/socket.h>
37#include <sys/wait.h>
38#include <netinet/in.h>
39#include <sysexits.h>
40#include <syslog.h>
41#include <fcntl.h>
42#include <netdb.h>
43#include <pwd.h>
44#include <time.h>
45#include <unistd.h>
46#include <limits.h>
47#include <errno(*__errno()).h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <signal.h>
52#include "pathnames.h"
53#include "mail.local.h"
54
55int
56main(int argc, char *argv[])
57{
58 struct passwd *pw;
59 int ch, fd, eval, lockfile=1;
60 uid_t uid;
61 char *from;
62
63 openlog("mail.local", LOG_PERROR0x20, LOG_MAIL(2<<3));
64
65 from = NULL((void *)0);
66 while ((ch = getopt(argc, argv, "lLdf:r:")) != -1)
1
Assuming the condition is false
2
Loop condition is false. Execution continues on line 85
67 switch (ch) {
68 case 'd': /* backward compatible */
69 break;
70 case 'f':
71 case 'r': /* backward compatible */
72 if (from)
73 merr(EX_USAGE64, "multiple -f options");
74 from = optarg;
75 break;
76 case 'l':
77 lockfile=1;
78 break;
79 case 'L':
80 lockfile=0;
81 break;
82 default:
83 usage();
84 }
85 argc -= optind;
86 argv += optind;
87
88 if (!*argv)
3
Assuming the condition is false
4
Taking false branch
89 usage();
90
91 /*
92 * If from not specified, use the name from getlogin() if the
93 * uid matches, otherwise, use the name from the password file
94 * corresponding to the uid.
95 */
96 uid = getuid();
97 if (!from
4.1
'from' is null
&& (!(from = getlogin()) ||
5
Assuming 'from' is non-null
8
Taking false branch
98 !(pw = getpwnam(from)) || pw->pw_uid != uid))
6
Assuming 'pw' is non-null
7
Assuming 'uid' is equal to field 'pw_uid'
99 from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
100
101 fd = storemail(from);
9
Calling 'storemail'
102 for (eval = 0; *argv; ++argv) {
103 if ((ch = deliver(fd, *argv, lockfile)) != 0)
104 eval = ch;
105 }
106 exit(eval);
107}
108
109int
110storemail(char *from)
111{
112 FILE *fp = NULL((void *)0);
10
'fp' initialized to a null pointer value
113 time_t tval;
114 int fd, eline = 1;
115 char *tbuf, *line = NULL((void *)0);
116 size_t linesize = 0;
117 ssize_t linelen;
118
119 if ((tbuf = strdup(_PATH_LOCTMP"/tmp/local.XXXXXXXXXX")) == NULL((void *)0))
11
Assuming the condition is false
120 merr(EX_OSERR71, "unable to allocate memory");
121 if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+")))
12
Assuming the condition is true
122 merr(EX_OSERR71, "unable to open temporary file");
123 (void)unlink(tbuf);
124 free(tbuf);
125
126 (void)time(&tval);
127 (void)fprintf(fp, "From %s %s", from, ctime(&tval));
128
129 while ((linelen = getline(&line, &linesize, stdin(&__sF[0]))) != -1) {
13
Assuming the condition is true
14
Loop condition is true. Entering loop body
130 if (line[linelen - 1] == '\n')
15
Assuming the condition is false
16
Taking false branch
131 line[linelen - 1] = '\0';
132 if (line[0] == '\0')
17
Assuming the condition is true
18
Taking true branch
133 eline = 1;
134 else {
135 if (eline && !strncmp(line, "From ", 5))
136 (void)putc('>', fp)(!__isthreaded ? __sputc('>', fp) : (putc)('>', fp));
137 eline = 0;
138 }
139 (void)fprintf(fp, "%s\n", line);
140 if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror
)(fp))
)
19
Assuming '__isthreaded' is 0
20
'?' condition is true
21
Access to field '_flags' results in a dereference of a null pointer (loaded from variable 'fp')
141 break;
142 }
143 free(line);
144
145 /* Output a newline; note, empty messages are allowed. */
146 (void)putc('\n', fp)(!__isthreaded ? __sputc('\n', fp) : (putc)('\n', fp));
147 (void)fflush(fp);
148 if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror
)(fp))
)
149 merr(EX_OSERR71, "temporary file write error");
150 return(fd);
151}
152
153int
154deliver(int fd, char *name, int lockfile)
155{
156 struct stat sb, fsb;
157 struct passwd *pw;
158 int mbfd=-1, lfd=-1, rval=EX_OSERR71;
159 char biffmsg[100], buf[8*1024], path[PATH_MAX1024];
160 off_t curoff;
161 size_t off;
162 ssize_t nr, nw;
163
164 /*
165 * Disallow delivery to unknown names -- special mailboxes can be
166 * handled in the sendmail aliases file.
167 */
168 if (!(pw = getpwnam(name))) {
169 mwarn("unknown name: %s", name);
170 return(EX_NOUSER67);
171 }
172
173 (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR"/var/mail", name);
174
175 if (lockfile) {
176 lfd = lockspool(name, pw);
177 if (lfd == -1)
178 return(EX_OSERR71);
179 }
180
181 /* after this point, always exit via bad to remove lockfile */
182retry:
183 if (lstat(path, &sb)) {
184 if (errno(*__errno()) != ENOENT2) {
185 mwarn("%s: %s", path, strerror(errno(*__errno())));
186 goto bad;
187 }
188 if ((mbfd = open(path, O_APPEND0x0008|O_CREAT0x0200|O_EXCL0x0800|O_WRONLY0x0001|O_EXLOCK0x0020,
189 S_IRUSR0000400|S_IWUSR0000200)) == -1) {
190 if (errno(*__errno()) == EEXIST17) {
191 /* file appeared since lstat */
192 goto retry;
193 } else {
194 mwarn("%s: %s", path, strerror(errno(*__errno())));
195 rval = EX_CANTCREAT73;
196 goto bad;
197 }
198 }
199 /*
200 * Set the owner and group. Historically, binmail repeated
201 * this at each mail delivery. We no longer do this, assuming
202 * that if the ownership or permissions were changed there
203 * was a reason for doing so.
204 */
205 if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) {
206 mwarn("chown %u:%u: %s", pw->pw_uid, pw->pw_gid, name);
207 goto bad;
208 }
209 } else {
210 if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)((sb.st_mode & 0170000) == 0100000)) {
211 mwarn("%s: linked or special file", path);
212 goto bad;
213 }
214 if ((mbfd = open(path, O_APPEND0x0008|O_WRONLY0x0001|O_EXLOCK0x0020,
215 S_IRUSR0000400|S_IWUSR0000200)) == -1) {
216 mwarn("%s: %s", path, strerror(errno(*__errno())));
217 goto bad;
218 }
219 if (fstat(mbfd, &fsb) == -1) {
220 /* relating error to path may be bad style */
221 mwarn("%s: %s", path, strerror(errno(*__errno())));
222 goto bad;
223 }
224 if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) {
225 mwarn("%s: changed after open", path);
226 goto bad;
227 }
228 /* paranoia? */
229 if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)((fsb.st_mode & 0170000) == 0100000)) {
230 mwarn("%s: linked or special file", path);
231 rval = EX_CANTCREAT73;
232 goto bad;
233 }
234 }
235
236 curoff = lseek(mbfd, 0, SEEK_END2);
237 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name,
238 (long long)curoff);
239 if (lseek(fd, 0, SEEK_SET0) == (off_t)-1) {
240 mwarn("temporary file: %s", strerror(errno(*__errno())));
241 goto bad;
242 }
243
244 while ((nr = read(fd, buf, sizeof(buf))) > 0)
245 for (off = 0; off < nr; off += nw)
246 if ((nw = write(mbfd, buf + off, nr - off)) == -1) {
247 mwarn("%s: %s", path, strerror(errno(*__errno())));
248 (void)ftruncate(mbfd, curoff);
249 goto bad;
250 }
251
252 if (nr == 0) {
253 rval = 0;
254 } else {
255 (void)ftruncate(mbfd, curoff);
256 mwarn("temporary file: %s", strerror(errno(*__errno())));
257 }
258
259bad:
260 if (lfd != -1)
261 unlockspool();
262
263 if (mbfd != -1) {
264 (void)fsync(mbfd); /* Don't wait for update. */
265 (void)close(mbfd); /* Implicit unlock. */
266 }
267
268 if (!rval)
269 notifybiff(biffmsg);
270 return(rval);
271}
272
273void
274notifybiff(char *msg)
275{
276 static struct addrinfo *res0;
277 struct addrinfo hints, *res;
278 static int f = -1;
279 size_t len;
280 int error;
281
282 if (res0 == NULL((void *)0)) {
283 memset(&hints, 0, sizeof(hints));
284 hints.ai_family = PF_UNSPEC0;
285 hints.ai_socktype = SOCK_DGRAM2;
286
287 error = getaddrinfo("localhost", "biff", &hints, &res0);
288 if (error) {
289 /* Be silent if biff service not available. */
290 if (error != EAI_SERVICE-8) {
291 mwarn("localhost: %s", gai_strerror(error));
292 }
293 return;
294 }
295 }
296
297 if (f == -1) {
298 for (res = res0; res != NULL((void *)0); res = res->ai_next) {
299 f = socket(res->ai_family, res->ai_socktype,
300 res->ai_protocol);
301 if (f != -1)
302 break;
303 }
304 }
305 if (f == -1) {
306 mwarn("socket: %s", strerror(errno(*__errno())));
307 return;
308 }
309
310 len = strlen(msg) + 1; /* XXX */
311 if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len)
312 mwarn("sendto biff: %s", strerror(errno(*__errno())));
313}
314
315static int lockfd = -1;
316static pid_t lockpid = -1;
317
318int
319lockspool(const char *name, struct passwd *pw)
320{
321 int pfd[2];
322 char ch;
323
324 if (geteuid() == 0)
325 return getlock(name, pw);
326
327 /* If not privileged, open pipe to lockspool(1) instead */
328 if (pipe2(pfd, O_CLOEXEC0x10000) == -1) {
329 merr(EX_OSERR71, "pipe: %s", strerror(errno(*__errno())));
330 return -1;
331 }
332
333 signal(SIGPIPE13, SIG_IGN(void (*)(int))1);
334 switch ((lockpid = fork())) {
335 case -1:
336 merr(EX_OSERR71, "fork: %s", strerror(errno(*__errno())));
337 return -1;
338 case 0:
339 /* child */
340 close(pfd[0]);
341 dup2(pfd[1], STDOUT_FILENO1);
342 execl(_PATH_LOCKSPOOL"/usr/libexec/lockspool", "lockspool", (char *)NULL((void *)0));
343 merr(EX_OSERR71, "execl: lockspool: %s", strerror(errno(*__errno())));
344 /* NOTREACHED */
345 break;
346 default:
347 /* parent */
348 close(pfd[1]);
349 lockfd = pfd[0];
350 break;
351 }
352
353 if (read(lockfd, &ch, 1) != 1 || ch != '1') {
354 unlockspool();
355 merr(EX_OSERR71, "lockspool: unable to get lock");
356 }
357
358 return lockfd;
359}
360
361void
362unlockspool(void)
363{
364 if (lockpid != -1) {
365 waitpid(lockpid, NULL((void *)0), 0);
366 lockpid = -1;
367 } else {
368 rellock();
369 }
370 close(lockfd);
371 lockfd = -1;
372}
373
374void
375usage(void)
376{
377 merr(EX_USAGE64, "usage: mail.local [-Ll] [-f from] user ...");
378}