Bug Summary

File:src/usr.sbin/acme-client/main.c
Warning:line 166, column 10
Potential leak of memory pointed to by 'chngdir'

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 main.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/acme-client/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.sbin/acme-client -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.sbin/acme-client/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/acme-client/main.c
1/* $Id: main.c,v 1.54 2020/05/10 12:06:18 benno Exp $ */
2/*
3 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/socket.h>
19
20#include <ctype.h>
21#include <err.h>
22#include <libgen.h>
23#include <stdarg.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28
29#include "extern.h"
30#include "parse.h"
31
32#define WWW_DIR"/var/www/acme" "/var/www/acme"
33#define CONF_FILE"/etc/acme-client.conf" "/etc/acme-client.conf"
34
35int verbose;
36enum comp proccomp;
37
38int
39main(int argc, char *argv[])
40{
41 const char **alts = NULL((void *)0);
42 char *certdir = NULL((void *)0);
43 char *chngdir = NULL((void *)0), *auth = NULL((void *)0);
44 char *conffile = CONF_FILE"/etc/acme-client.conf";
45 char *tmps, *tmpsd;
46 int key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
47 int file_fds[2], dns_fds[2], rvk_fds[2];
48 int force = 0;
49 int c, rc, revocate = 0;
50 int popts = 0;
51 pid_t pids[COMP__MAX];
52 size_t i, altsz, ne;
53
54 struct acme_conf *conf = NULL((void *)0);
55 struct authority_c *authority = NULL((void *)0);
56 struct domain_c *domain = NULL((void *)0);
57 struct altname_c *ac;
58
59 while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
1
Assuming the condition is false
2
Loop condition is false. Execution continues on line 82
60 switch (c) {
61 case 'F':
62 force = 1;
63 break;
64 case 'f':
65 if ((conffile = strdup(optarg)) == NULL((void *)0))
66 err(EXIT_FAILURE1, "strdup");
67 break;
68 case 'n':
69 popts |= ACME_OPT_CHECK0x00000004;
70 break;
71 case 'r':
72 revocate = 1;
73 break;
74 case 'v':
75 verbose = verbose ? 2 : 1;
76 popts |= ACME_OPT_VERBOSE0x00000001;
77 break;
78 default:
79 goto usage;
80 }
81
82 if (getuid() != 0)
3
Assuming the condition is false
4
Taking false branch
83 errx(EXIT_FAILURE1, "must be run as root");
84
85 /* parse config file */
86 if ((conf = parse_config(conffile, popts)) == NULL((void *)0))
5
Assuming the condition is false
6
Taking false branch
87 return EXIT_FAILURE1;
88
89 argc -= optind;
90 argv += optind;
91 if (argc != 1)
7
Assuming 'argc' is equal to 1
8
Taking false branch
92 goto usage;
93
94 if ((domain = domain_find_handle(conf, argv[0])) == NULL((void *)0))
9
Assuming the condition is false
10
Taking false branch
95 errx(EXIT_FAILURE1, "domain %s not found", argv[0]);
96
97 argc--;
98 argv++;
99
100 /*
101 * The parser enforces that at least cert or fullchain is set.
102 * XXX Test if cert, chain and fullchain have the same dirname?
103 */
104 tmps = domain->cert ? domain->cert : domain->fullchain;
11
Assuming field 'cert' is null
12
'?' condition is false
105 if ((tmps = strdup(tmps)) == NULL((void *)0))
13
Assuming the condition is false
14
Taking false branch
106 err(EXIT_FAILURE1, "strdup");
107 if ((tmpsd = dirname(tmps)) == NULL((void *)0))
15
Assuming the condition is false
16
Taking false branch
108 err(EXIT_FAILURE1, "dirname");
109 if ((certdir = strdup(tmpsd)) == NULL((void *)0))
17
Assuming the condition is false
18
Taking false branch
110 err(EXIT_FAILURE1, "strdup");
111 free(tmps);
112 tmps = tmpsd = NULL((void *)0);
113
114
115 /* chain or fullchain can be relative paths according */
116 if (domain->chain && domain->chain[0] != '/') {
19
Assuming field 'chain' is null
117 if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
118 err(EXIT_FAILURE1, "asprintf");
119 free(domain->chain);
120 domain->chain = tmps;
121 tmps = NULL((void *)0);
122 }
123 if (domain->fullchain && domain->fullchain[0] != '/') {
20
Assuming field 'fullchain' is null
124 if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
125 err(EXIT_FAILURE1, "asprintf");
126 free(domain->fullchain);
127 domain->fullchain = tmps;
128 tmps = NULL((void *)0);
129 }
130
131 if ((auth = domain->auth) == NULL((void *)0)) {
21
Assuming the condition is true
22
Taking true branch
132 /* use the first authority from the config as default XXX */
133 authority = authority_find0(conf);
134 if (authority == NULL((void *)0))
23
Assuming 'authority' is not equal to NULL
24
Taking false branch
135 errx(EXIT_FAILURE1, "no authorities configured");
136 } else {
137 authority = authority_find(conf, auth);
138 if (authority == NULL((void *)0))
139 errx(EXIT_FAILURE1, "authority %s not found", auth);
140 }
141
142 if ((chngdir = domain->challengedir) == NULL((void *)0))
25
Assuming the condition is true
26
Taking true branch
143 if ((chngdir = strdup(WWW_DIR"/var/www/acme")) == NULL((void *)0))
27
Memory is allocated
28
Assuming the condition is false
29
Taking false branch
144 err(EXIT_FAILURE1, "strdup");
145
146 /*
147 * Do some quick checks to see if our paths exist.
148 * This will be done in the children, but we might as well check
149 * now before the fork.
150 * XXX maybe use conf_check_file() from parse.y
151 */
152
153 ne = 0;
154
155 if (access(certdir, R_OK0x04) == -1) {
30
Assuming the condition is false
31
Taking false branch
156 warnx("%s: cert directory must exist", certdir);
157 ne++;
158 }
159
160 if (access(chngdir, R_OK0x04) == -1) {
32
Assuming the condition is true
33
Taking true branch
161 warnx("%s: challenge directory must exist", chngdir);
162 ne++;
163 }
164
165 if (ne
33.1
'ne' is > 0
> 0)
34
Taking true branch
166 return EXIT_FAILURE1;
35
Potential leak of memory pointed to by 'chngdir'
167
168 if (popts & ACME_OPT_CHECK0x00000004)
169 return EXIT_SUCCESS0;
170
171 /* Set the zeroth altname as our domain. */
172 altsz = domain->altname_count + 1;
173 alts = calloc(altsz, sizeof(char *));
174 if (alts == NULL((void *)0))
175 err(EXIT_FAILURE1, "calloc");
176 alts[0] = domain->domain;
177 i = 1;
178 /* XXX get rid of alts[] later */
179 TAILQ_FOREACH(ac, &domain->altname_list, entry)for((ac) = ((&domain->altname_list)->tqh_first); (ac
) != ((void *)0); (ac) = ((ac)->entry.tqe_next))
180 alts[i++] = ac->domain;
181
182 /*
183 * Open channels between our components.
184 */
185
186 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, key_fds) == -1)
187 err(EXIT_FAILURE1, "socketpair");
188 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, acct_fds) == -1)
189 err(EXIT_FAILURE1, "socketpair");
190 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, chng_fds) == -1)
191 err(EXIT_FAILURE1, "socketpair");
192 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, cert_fds) == -1)
193 err(EXIT_FAILURE1, "socketpair");
194 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, file_fds) == -1)
195 err(EXIT_FAILURE1, "socketpair");
196 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, dns_fds) == -1)
197 err(EXIT_FAILURE1, "socketpair");
198 if (socketpair(AF_UNIX1, SOCK_STREAM1, 0, rvk_fds) == -1)
199 err(EXIT_FAILURE1, "socketpair");
200
201 /* Start with the network-touching process. */
202
203 if ((pids[COMP_NET] = fork()) == -1)
204 err(EXIT_FAILURE1, "fork");
205
206 if (pids[COMP_NET] == 0) {
207 proccomp = COMP_NET;
208 close(key_fds[0]);
209 close(acct_fds[0]);
210 close(chng_fds[0]);
211 close(cert_fds[0]);
212 close(file_fds[0]);
213 close(file_fds[1]);
214 close(dns_fds[0]);
215 close(rvk_fds[0]);
216 c = netproc(key_fds[1], acct_fds[1],
217 chng_fds[1], cert_fds[1],
218 dns_fds[1], rvk_fds[1],
219 revocate, authority,
220 (const char *const *)alts, altsz);
221 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
222 }
223
224 close(key_fds[1]);
225 close(acct_fds[1]);
226 close(chng_fds[1]);
227 close(cert_fds[1]);
228 close(dns_fds[1]);
229 close(rvk_fds[1]);
230
231 /* Now the key-touching component. */
232
233 if ((pids[COMP_KEY] = fork()) == -1)
234 err(EXIT_FAILURE1, "fork");
235
236 if (pids[COMP_KEY] == 0) {
237 proccomp = COMP_KEY;
238 close(cert_fds[0]);
239 close(dns_fds[0]);
240 close(rvk_fds[0]);
241 close(acct_fds[0]);
242 close(chng_fds[0]);
243 close(file_fds[0]);
244 close(file_fds[1]);
245 c = keyproc(key_fds[0], domain->key,
246 (const char **)alts, altsz,
247 domain->keytype);
248 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
249 }
250
251 close(key_fds[0]);
252
253 /* The account-touching component. */
254
255 if ((pids[COMP_ACCOUNT] = fork()) == -1)
256 err(EXIT_FAILURE1, "fork");
257
258 if (pids[COMP_ACCOUNT] == 0) {
259 proccomp = COMP_ACCOUNT;
260 close(cert_fds[0]);
261 close(dns_fds[0]);
262 close(rvk_fds[0]);
263 close(chng_fds[0]);
264 close(file_fds[0]);
265 close(file_fds[1]);
266 c = acctproc(acct_fds[0], authority->account,
267 authority->keytype);
268 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
269 }
270
271 close(acct_fds[0]);
272
273 /* The challenge-accepting component. */
274
275 if ((pids[COMP_CHALLENGE] = fork()) == -1)
276 err(EXIT_FAILURE1, "fork");
277
278 if (pids[COMP_CHALLENGE] == 0) {
279 proccomp = COMP_CHALLENGE;
280 close(cert_fds[0]);
281 close(dns_fds[0]);
282 close(rvk_fds[0]);
283 close(file_fds[0]);
284 close(file_fds[1]);
285 c = chngproc(chng_fds[0], chngdir);
286 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
287 }
288
289 close(chng_fds[0]);
290
291 /* The certificate-handling component. */
292
293 if ((pids[COMP_CERT] = fork()) == -1)
294 err(EXIT_FAILURE1, "fork");
295
296 if (pids[COMP_CERT] == 0) {
297 proccomp = COMP_CERT;
298 close(dns_fds[0]);
299 close(rvk_fds[0]);
300 close(file_fds[1]);
301 c = certproc(cert_fds[0], file_fds[0]);
302 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
303 }
304
305 close(cert_fds[0]);
306 close(file_fds[0]);
307
308 /* The certificate-handling component. */
309
310 if ((pids[COMP_FILE] = fork()) == -1)
311 err(EXIT_FAILURE1, "fork");
312
313 if (pids[COMP_FILE] == 0) {
314 proccomp = COMP_FILE;
315 close(dns_fds[0]);
316 close(rvk_fds[0]);
317 c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
318 domain->fullchain);
319 /*
320 * This is different from the other processes in that it
321 * can return 2 if the certificates were updated.
322 */
323 exit(c > 1 ? 2 : (c ? EXIT_SUCCESS0 : EXIT_FAILURE1));
324 }
325
326 close(file_fds[1]);
327
328 /* The DNS lookup component. */
329
330 if ((pids[COMP_DNS] = fork()) == -1)
331 err(EXIT_FAILURE1, "fork");
332
333 if (pids[COMP_DNS] == 0) {
334 proccomp = COMP_DNS;
335 close(rvk_fds[0]);
336 c = dnsproc(dns_fds[0]);
337 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
338 }
339
340 close(dns_fds[0]);
341
342 /* The expiration component. */
343
344 if ((pids[COMP_REVOKE] = fork()) == -1)
345 err(EXIT_FAILURE1, "fork");
346
347 if (pids[COMP_REVOKE] == 0) {
348 proccomp = COMP_REVOKE;
349 c = revokeproc(rvk_fds[0], domain->cert != NULL((void *)0) ? domain->cert :
350 domain->fullchain, force, revocate,
351 (const char *const *)alts, altsz);
352 exit(c ? EXIT_SUCCESS0 : EXIT_FAILURE1);
353 }
354
355 close(rvk_fds[0]);
356
357 /* Jail: sandbox, file-system, user. */
358
359 if (pledge("stdio", NULL((void *)0)) == -1)
360 err(EXIT_FAILURE1, "pledge");
361
362 /*
363 * Collect our subprocesses.
364 * Require that they both have exited cleanly.
365 */
366
367 rc = checkexit(pids[COMP_KEY], COMP_KEY) +
368 checkexit(pids[COMP_CERT], COMP_CERT) +
369 checkexit(pids[COMP_NET], COMP_NET) +
370 checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
371 checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
372 checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
373 checkexit(pids[COMP_DNS], COMP_DNS) +
374 checkexit(pids[COMP_REVOKE], COMP_REVOKE);
375
376 return rc != COMP__MAX ? EXIT_FAILURE1 : (c == 2 ? EXIT_SUCCESS0 : 2);
377usage:
378 fprintf(stderr(&__sF[2]),
379 "usage: acme-client [-Fnrv] [-f configfile] handle\n");
380 return EXIT_FAILURE1;
381}