Bug Summary

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

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