Bug Summary

File:src/usr.sbin/rpki-client/rsync.c
Warning:line 327, column 10
Use of memory after it is freed

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 rsync.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/rpki-client/obj -resource-dir /usr/local/llvm16/lib/clang/16 -I /usr/src/usr.sbin/rpki-client -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.sbin/rpki-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/rpki-client/rsync.c
1/* $OpenBSD: rsync.c,v 1.48 2023/11/24 14:05:47 job Exp $ */
2/*
3 * Copyright (c) 2019 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 AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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/queue.h>
19#include <sys/stat.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <err.h>
23#include <errno(*__errno()).h>
24#include <poll.h>
25#include <resolv.h>
26#include <signal.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31#include <imsg.h>
32
33#include "extern.h"
34
35#define __STRINGIFY(x)"x" #x
36#define STRINGIFY(x)"x" __STRINGIFY(x)"x"
37
38/*
39 * A running rsync process.
40 * We can have multiple of these simultaneously and need to keep track
41 * of which process maps to which request.
42 */
43struct rsync {
44 TAILQ_ENTRY(rsync)struct { struct rsync *tqe_next; struct rsync **tqe_prev; } entry;
45 char *uri; /* uri of this rsync proc */
46 char *dst; /* destination directory */
47 char *compdst; /* compare against directory */
48 unsigned int id; /* identity of request */
49 pid_t pid; /* pid of process or 0 if unassociated */
50};
51
52static TAILQ_HEAD(, rsync)struct { struct rsync *tqh_first; struct rsync **tqh_last; } states = TAILQ_HEAD_INITIALIZER(states){ ((void *)0), &(states).tqh_first };
53
54/*
55 * Return the base of a rsync URI (rsync://hostname/module). The
56 * caRepository provided by the RIR CAs point deeper than they should
57 * which would result in many rsync calls for almost every subdirectory.
58 * This is inefficient so instead crop the URI to a common base.
59 * The returned string needs to be freed by the caller.
60 */
61char *
62rsync_base_uri(const char *uri)
63{
64 const char *host, *module, *rest;
65 char *base_uri;
66
67 /* Case-insensitive rsync URI. */
68 if (strncasecmp(uri, "rsync://", 8) != 0) {
69 warnx("%s: not using rsync schema", uri);
70 return NULL((void *)0);
71 }
72
73 /* Parse the non-zero-length hostname. */
74 host = uri + 8;
75
76 if ((module = strchr(host, '/')) == NULL((void *)0)) {
77 warnx("%s: missing rsync module", uri);
78 return NULL((void *)0);
79 } else if (module == host) {
80 warnx("%s: zero-length rsync host", uri);
81 return NULL((void *)0);
82 }
83
84 /* The non-zero-length module follows the hostname. */
85 module++;
86 if (*module == '\0') {
87 warnx("%s: zero-length rsync module", uri);
88 return NULL((void *)0);
89 }
90
91 /* The path component is optional. */
92 if ((rest = strchr(module, '/')) == NULL((void *)0)) {
93 if ((base_uri = strdup(uri)) == NULL((void *)0))
94 err(1, NULL((void *)0));
95 return base_uri;
96 } else if (rest == module) {
97 warnx("%s: zero-length module", uri);
98 return NULL((void *)0);
99 }
100
101 if ((base_uri = strndup(uri, rest - uri)) == NULL((void *)0))
102 err(1, NULL((void *)0));
103 return base_uri;
104}
105
106/*
107 * The directory passed as --compare-dest needs to be relative to
108 * the destination directory. This function takes care of that.
109 */
110static char *
111rsync_fixup_dest(char *destdir, char *compdir)
112{
113 const char *dotdot = "../../../../../../"; /* should be enough */
114 int dirs = 1;
115 char *fn;
116 char c;
117
118 while ((c = *destdir++) != '\0')
119 if (c == '/')
120 dirs++;
121
122 if (dirs > 6)
123 /* too deep for us */
124 return NULL((void *)0);
125
126 if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1)
127 err(1, NULL((void *)0));
128 return fn;
129}
130
131static pid_t
132exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst,
133 char *compdst)
134{
135 pid_t pid;
136 char *args[32];
137 char *reldst;
138 int i;
139
140 if ((pid = fork()) == -1)
141 err(1, "fork");
142
143 if (pid == 0) {
144 if (pledge("stdio exec", NULL((void *)0)) == -1)
145 err(1, "pledge");
146 i = 0;
147 args[i++] = (char *)prog;
148 args[i++] = "-rtO";
149 args[i++] = "--no-motd";
150 args[i++] = "--min-size=" STRINGIFY(MIN_FILE_SIZE)"100";
151 args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE)"4000000";
152 args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT)"15";
153 args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT)"30";
154 args[i++] = "--include=*/";
155 args[i++] = "--include=*.cer";
156 args[i++] = "--include=*.crl";
157 args[i++] = "--include=*.gbr";
158 args[i++] = "--include=*.mft";
159 args[i++] = "--include=*.roa";
160 args[i++] = "--include=*.asa";
161 args[i++] = "--include=*.tak";
162 args[i++] = "--exclude=*";
163 if (bind_addr != NULL((void *)0)) {
164 args[i++] = "--address";
165 args[i++] = (char *)bind_addr;
166 }
167 if (compdst != NULL((void *)0) &&
168 (reldst = rsync_fixup_dest(dst, compdst)) != NULL((void *)0)) {
169 args[i++] = "--compare-dest";
170 args[i++] = reldst;
171 }
172 args[i++] = uri;
173 args[i++] = dst;
174 args[i] = NULL((void *)0);
175 /* XXX args overflow not prevented */
176 execvp(args[0], args);
177 err(1, "%s: execvp", prog);
178 }
179
180 return pid;
181}
182
183static void
184rsync_new(unsigned int id, char *uri, char *dst, char *compdst)
185{
186 struct rsync *s;
187
188 if ((s = calloc(1, sizeof(*s))) == NULL((void *)0))
189 err(1, NULL((void *)0));
190
191 s->id = id;
192 s->uri = uri;
193 s->dst = dst;
194 s->compdst = compdst;
195
196 TAILQ_INSERT_TAIL(&states, s, entry)do { (s)->entry.tqe_next = ((void *)0); (s)->entry.tqe_prev
= (&states)->tqh_last; *(&states)->tqh_last = (
s); (&states)->tqh_last = &(s)->entry.tqe_next;
} while (0)
;
197}
198
199static void
200rsync_free(struct rsync *s)
201{
202 TAILQ_REMOVE(&states, s, entry)do { if (((s)->entry.tqe_next) != ((void *)0)) (s)->entry
.tqe_next->entry.tqe_prev = (s)->entry.tqe_prev; else (
&states)->tqh_last = (s)->entry.tqe_prev; *(s)->
entry.tqe_prev = (s)->entry.tqe_next; ; ; } while (0)
;
38
Assuming field 'tqe_next' is equal to null
39
Taking false branch
40
Loop condition is false. Exiting loop
203 free(s->uri);
204 free(s->dst);
205 free(s->compdst);
206 free(s);
41
Memory is released
207}
208
209static void
210proc_child(int signal)
211{
212
213 /* Nothing: just discard. */
214}
215
216/*
217 * Process used for synchronising repositories.
218 * This simply waits to be told which repository to synchronise, then
219 * does so.
220 * It then responds with the identifier of the repo that it updated.
221 * It only exits cleanly when fd is closed.
222 */
223void
224proc_rsync(char *prog, char *bind_addr, int fd)
225{
226 int nprocs = 0, npending = 0, rc = 0;
227 struct pollfd pfd;
228 struct msgbuf msgq;
229 struct ibuf *b, *inbuf = NULL((void *)0);
230 sigset_t mask, oldmask;
231 struct rsync *s, *ns;
232
233 if (pledge("stdio rpath proc exec unveil", NULL((void *)0)) == -1)
1
Assuming the condition is false
2
Taking false branch
234 err(1, "pledge");
235
236 pfd.fd = fd;
237 msgbuf_init(&msgq);
238 msgq.fd = fd;
239
240 /*
241 * Unveil the command we want to run.
242 * If this has a pathname component in it, interpret as a file
243 * and unveil the file directly.
244 * Otherwise, look up the command in our PATH.
245 */
246
247 if (strchr(prog, '/') == NULL((void *)0)) {
3
Assuming the condition is false
4
Taking false branch
248 const char *pp;
249 char *save, *cmd, *path;
250 struct stat stt;
251
252 if (getenv("PATH") == NULL((void *)0))
253 errx(1, "PATH is unset");
254 if ((path = strdup(getenv("PATH"))) == NULL((void *)0))
255 err(1, NULL((void *)0));
256 save = path;
257 while ((pp = strsep(&path, ":")) != NULL((void *)0)) {
258 if (*pp == '\0')
259 continue;
260 if (asprintf(&cmd, "%s/%s", pp, prog) == -1)
261 err(1, NULL((void *)0));
262 if (lstat(cmd, &stt) == -1) {
263 free(cmd);
264 continue;
265 } else if (unveil(cmd, "x") == -1)
266 err(1, "%s: unveil", cmd);
267 free(cmd);
268 break;
269 }
270 free(save);
271 } else if (unveil(prog, "x") == -1)
5
Assuming the condition is false
6
Taking false branch
272 err(1, "%s: unveil", prog);
273
274 if (pledge("stdio proc exec", NULL((void *)0)) == -1)
7
Assuming the condition is false
8
Taking false branch
275 err(1, "pledge");
276
277 /* Initialise retriever for children exiting. */
278
279 if (sigemptyset(&mask) == -1)
9
Taking false branch
280 err(1, NULL((void *)0));
281 if (signal(SIGCHLD20, proc_child) == SIG_ERR(void (*)(int))-1)
10
Assuming the condition is false
11
Taking false branch
282 err(1, NULL((void *)0));
283 if (sigaddset(&mask, SIGCHLD20) == -1)
12
Taking false branch
284 err(1, NULL((void *)0));
285 if (sigprocmask(SIG_BLOCK1, &mask, &oldmask) == -1)
13
Assuming the condition is false
14
Taking false branch
286 err(1, NULL((void *)0));
287
288 for (;;) {
15
Loop condition is true. Entering loop body
43
Loop condition is true. Entering loop body
289 char *uri, *dst, *compdst;
290 unsigned int id;
291 pid_t pid;
292 int st;
293
294 pfd.events = 0;
295 pfd.events |= POLLIN0x0001;
296 if (msgq.queued
43.1
Field 'queued' is 0
)
16
Assuming field 'queued' is 0
297 pfd.events |= POLLOUT0x0004;
298
299 if (npending
16.1
'npending' is <= 0
43.2
'npending' is <= 0
> 0 && nprocs < MAX_RSYNC_REQUESTS16) {
300 TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); (
s) = ((s)->entry.tqe_next))
{
301 if (s->pid == 0) {
302 s->pid = exec_rsync(prog, bind_addr,
303 s->uri, s->dst, s->compdst);
304 if (++nprocs >= MAX_RSYNC_REQUESTS16)
305 break;
306 if (--npending == 0)
307 break;
308 }
309 }
310 }
311
312 if (ppoll(&pfd, 1, NULL((void *)0), &oldmask) == -1) {
17
Assuming the condition is false
18
Taking false branch
44
Assuming the condition is true
45
Taking true branch
313 if (errno(*__errno()) != EINTR4)
46
Assuming the condition is false
47
Taking false branch
314 err(1, "ppoll");
315
316 /*
317 * If we've received an EINTR, it means that one
318 * of our children has exited and we can reap it
319 * and look up its identifier.
320 * Then we respond to the parent.
321 */
322
323 while ((pid = waitpid(WAIT_ANY(-1), &st, WNOHANG0x01)) > 0) {
48
Assuming the condition is true
49
Loop condition is true. Entering loop body
324 int ok = 1;
325
326 TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); (
s) = ((s)->entry.tqe_next))
50
Loop condition is true. Entering loop body
327 if (s->pid == pid)
51
Use of memory after it is freed
328 break;
329 if (s == NULL((void *)0))
330 errx(1, "waitpid: %d unexpected", pid);
331
332 if (!WIFEXITED(st)(((st) & 0177) == 0)) {
333 warnx("rsync %s terminated abnormally",
334 s->uri);
335 rc = 1;
336 ok = 0;
337 } else if (WEXITSTATUS(st)(int)(((unsigned)(st) >> 8) & 0xff) != 0) {
338 warnx("rsync %s failed", s->uri);
339 ok = 0;
340 }
341
342 b = io_new_buffer();
343 io_simple_buffer(b, &s->id, sizeof(s->id));
344 io_simple_buffer(b, &ok, sizeof(ok));
345 io_close_buffer(&msgq, b);
346
347 rsync_free(s);
348 nprocs--;
349 }
350 if (pid == -1 && errno(*__errno()) != ECHILD10)
351 err(1, "waitpid");
352
353 continue;
354 }
355
356 if (pfd.revents & POLLOUT0x0004) {
19
Assuming the condition is false
20
Taking false branch
357 switch (msgbuf_write(&msgq)) {
358 case 0:
359 errx(1, "write: connection closed");
360 case -1:
361 err(1, "write");
362 }
363 }
364
365 /* connection closed */
366 if (pfd.revents & POLLHUP0x0010)
21
Assuming the condition is false
22
Taking false branch
367 break;
368
369 if (!(pfd.revents & POLLIN0x0001))
23
Assuming the condition is false
24
Taking false branch
370 continue;
371
372 b = io_buf_read(fd, &inbuf);
373 if (b == NULL((void *)0))
25
Assuming 'b' is not equal to NULL
26
Taking false branch
374 continue;
375
376 /* Read host and module. */
377 io_read_buf(b, &id, sizeof(id));
378 io_read_str(b, &dst);
379 io_read_str(b, &compdst);
380 io_read_str(b, &uri);
381
382 ibuf_free(b);
383
384 if (dst != NULL((void *)0)) {
27
Assuming 'dst' is equal to NULL
28
Taking false branch
385 rsync_new(id, uri, dst, compdst);
386 npending++;
387 } else {
388 TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); (
s) = ((s)->entry.tqe_next))
29
Assuming 's' is not equal to null
30
Loop condition is true. Entering loop body
389 if (s->id == id)
31
Assuming 'id' is equal to field 'id'
32
Taking true branch
390 break;
391 if (s
33.1
's' is not equal to NULL
!= NULL((void *)0)) {
33
Execution continues on line 391
34
Taking true branch
392 if (s->pid != 0)
35
Assuming field 'pid' is equal to 0
36
Taking false branch
393 kill(s->pid, SIGTERM15);
394 else
395 rsync_free(s);
37
Calling 'rsync_free'
42
Returning; memory was released via 1st parameter
396 }
397 }
398 }
399
400 /* No need for these to be hanging around. */
401 TAILQ_FOREACH_SAFE(s, &states, entry, ns)for ((s) = ((&states)->tqh_first); (s) != ((void *)0) &&
((ns) = ((s)->entry.tqe_next), 1); (s) = (ns))
{
402 if (s->pid != 0)
403 kill(s->pid, SIGTERM15);
404 rsync_free(s);
405 }
406
407 msgbuf_clear(&msgq);
408 exit(rc);
409}