Bug Summary

File:src/usr.bin/ftp/complete.c
Warning:line 266, column 18
Access to field 'sl_cur' results in a dereference of a null pointer (loaded from variable 'dirlist')

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 complete.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.bin/ftp/obj -resource-dir /usr/local/lib/clang/13.0.0 -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/ftp/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.bin/ftp/complete.c
1/* $OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian Exp $ */
2/* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */
3
4/*-
5 * Copyright (c) 1997 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Luke Mewburn.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#ifndef SMALL
34
35/*
36 * FTP user program - command and file completion routines
37 */
38
39#include <ctype.h>
40#include <err.h>
41#include <dirent.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45
46#include "ftp_var.h"
47
48static int comparstr(const void *, const void *);
49static unsigned char complete_ambiguous(char *, int, StringList *);
50static unsigned char complete_command(char *, int);
51static unsigned char complete_local(char *, int);
52static unsigned char complete_remote(char *, int);
53static void ftpvis(char *, size_t, const char *, size_t);
54
55static int
56comparstr(const void *a, const void *b)
57{
58 return (strcmp(*(char **)a, *(char **)b));
59}
60
61/*
62 * Determine if complete is ambiguous. If unique, insert.
63 * If no choices, error. If unambiguous prefix, insert that.
64 * Otherwise, list choices. words is assumed to be filtered
65 * to only contain possible choices.
66 * Args:
67 * word word which started the match
68 * list list by default
69 * words stringlist containing possible matches
70 */
71static unsigned char
72complete_ambiguous(char *word, int list, StringList *words)
73{
74 char insertstr[PATH_MAX1024 * 2];
75 char *lastmatch;
76 int i, j;
77 size_t matchlen, wordlen;
78
79 wordlen = strlen(word);
80 if (words->sl_cur == 0)
81 return (CC_ERROR6); /* no choices available */
82
83 if (words->sl_cur == 1) { /* only once choice available */
84 char *p = words->sl_str[0] + wordlen;
85 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
86 if (el_insertstr(el, insertstr) == -1)
87 return (CC_ERROR6);
88 else
89 return (CC_REFRESH4);
90 }
91
92 if (!list) {
93 lastmatch = words->sl_str[0];
94 matchlen = strlen(lastmatch);
95 for (i = 1 ; i < words->sl_cur ; i++) {
96 for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
97 if (lastmatch[j] != words->sl_str[i][j])
98 break;
99 if (j < matchlen)
100 matchlen = j;
101 }
102 if (matchlen > wordlen) {
103 ftpvis(insertstr, sizeof(insertstr),
104 lastmatch + wordlen, matchlen - wordlen);
105 if (el_insertstr(el, insertstr) == -1)
106 return (CC_ERROR6);
107 else
108 /*
109 * XXX: really want CC_REFRESH_BEEP
110 */
111 return (CC_REFRESH4);
112 }
113 }
114
115 putc('\n', ttyout)(!__isthreaded ? __sputc('\n', ttyout) : (putc)('\n', ttyout)
)
;
116 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
117 list_vertical(words);
118 return (CC_REDISPLAY8);
119}
120
121/*
122 * Complete a command
123 */
124static unsigned char
125complete_command(char *word, int list)
126{
127 struct cmd *c;
128 StringList *words;
129 size_t wordlen;
130 unsigned char rv;
131
132 words = sl_init();
133 wordlen = strlen(word);
134
135 for (c = cmdtab; c->c_name != NULL((void*)0); c++) {
136 if (wordlen > strlen(c->c_name))
137 continue;
138 if (strncmp(word, c->c_name, wordlen) == 0)
139 sl_add(words, c->c_name);
140 }
141
142 rv = complete_ambiguous(word, list, words);
143 sl_free(words, 0);
144 return (rv);
145}
146
147/*
148 * Complete a local file
149 */
150static unsigned char
151complete_local(char *word, int list)
152{
153 StringList *words;
154 char dir[PATH_MAX1024];
155 char *file;
156 DIR *dd;
157 struct dirent *dp;
158 unsigned char rv;
159
160 if ((file = strrchr(word, '/')) == NULL((void*)0)) {
161 dir[0] = '.';
162 dir[1] = '\0';
163 file = word;
164 } else {
165 if (file == word) {
166 dir[0] = '/';
167 dir[1] = '\0';
168 } else {
169 (void)strlcpy(dir, word, (size_t)(file - word) + 1);
170 }
171 file++;
172 }
173
174 if ((dd = opendir(dir)) == NULL((void*)0))
175 return (CC_ERROR6);
176
177 words = sl_init();
178
179 for (dp = readdir(dd); dp != NULL((void*)0); dp = readdir(dd)) {
180 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
181 continue;
182 if (strlen(file) > dp->d_namlen)
183 continue;
184 if (strncmp(file, dp->d_name, strlen(file)) == 0) {
185 char *tcp;
186
187 tcp = strdup(dp->d_name);
188 if (tcp == NULL((void*)0))
189 errx(1, "Can't allocate memory for local dir");
190 sl_add(words, tcp);
191 }
192 }
193 closedir(dd);
194
195 rv = complete_ambiguous(file, list, words);
196 sl_free(words, 1);
197 return (rv);
198}
199
200/*
201 * Complete a remote file
202 */
203static unsigned char
204complete_remote(char *word, int list)
205{
206 static StringList *dirlist;
19
'dirlist' initialized to a null pointer value
207 static char lastdir[PATH_MAX1024];
208 StringList *words;
209 char dir[PATH_MAX1024];
210 char *file, *cp;
211 int i;
212 unsigned char rv;
213
214 char *dummyargv[] = { "complete", dir, NULL((void*)0) };
215
216 if ((file = strrchr(word, '/')) == NULL((void*)0)) {
20
Assuming the condition is true
21
Taking true branch
217 dir[0] = '.';
218 dir[1] = '\0';
219 file = word;
220 } else {
221 cp = file;
222 while (*cp == '/' && cp > word)
223 cp--;
224 (void)strlcpy(dir, word, (size_t)(cp - word + 2));
225 file++;
226 }
227
228 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
22
Assuming 'dirchange' is 0
23
Assuming the condition is false
24
Taking false branch
229 char *emesg;
230
231 sl_free(dirlist, 1);
232 dirlist = sl_init();
233
234 mflag = 1;
235 emesg = NULL((void*)0);
236 if (debug)
237 (void)putc('\n', ttyout)(!__isthreaded ? __sputc('\n', ttyout) : (putc)('\n', ttyout)
)
;
238 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL((void*)0)) {
239 char *tcp;
240
241 if (!mflag)
242 continue;
243 if (*cp == '\0') {
244 mflag = 0;
245 continue;
246 }
247 tcp = strrchr(cp, '/');
248 if (tcp)
249 tcp++;
250 else
251 tcp = cp;
252 tcp = strdup(tcp);
253 if (tcp == NULL((void*)0))
254 errx(1, "Can't allocate memory for remote dir");
255 sl_add(dirlist, tcp);
256 }
257 if (emesg != NULL((void*)0)) {
258 fprintf(ttyout, "\n%s\n", emesg);
259 return (CC_REDISPLAY8);
260 }
261 (void)strlcpy(lastdir, dir, sizeof lastdir);
262 dirchange = 0;
263 }
264
265 words = sl_init();
266 for (i = 0; i < dirlist->sl_cur; i++) {
25
Access to field 'sl_cur' results in a dereference of a null pointer (loaded from variable 'dirlist')
267 cp = dirlist->sl_str[i];
268 if (strlen(file) > strlen(cp))
269 continue;
270 if (strncmp(file, cp, strlen(file)) == 0)
271 sl_add(words, cp);
272 }
273 rv = complete_ambiguous(file, list, words);
274 sl_free(words, 0);
275 return (rv);
276}
277
278/*
279 * Generic complete routine
280 */
281unsigned char
282complete(EditLine *el, int ch)
283{
284 static char word[FTPBUFLEN1024 + 200];
285 static int lastc_argc, lastc_argo;
286 struct cmd *c;
287 const LineInfo *lf;
288 int celems, dolist;
289 size_t len;
290
291 lf = el_line(el);
292 len = lf->lastchar - lf->buffer;
293 if (len >= sizeof(line))
1
Assuming the condition is false
2
Taking false branch
294 return (CC_ERROR6);
295 (void)memcpy(line, lf->buffer, len);
296 line[len] = '\0';
297 cursor_pos = line + (lf->cursor - lf->buffer);
298 lastc_argc = cursor_argc; /* remember last cursor pos */
299 lastc_argo = cursor_argo;
300 makeargv(); /* build argc/argv of current line */
301
302 if (cursor_argo >= sizeof(word))
3
Assuming the condition is false
4
Taking false branch
303 return (CC_ERROR6);
304
305 dolist = 0;
306 /* if cursor and word is same, list alternatives */
307 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
5
Assuming 'lastc_argc' is not equal to 'cursor_argc'
308 && strncmp(word, margv(marg_sl->sl_str)[cursor_argc], cursor_argo) == 0)
309 dolist = 1;
310 else if (cursor_argo)
6
Assuming 'cursor_argo' is 0
7
Taking false branch
311 memcpy(word, margv(marg_sl->sl_str)[cursor_argc], cursor_argo);
312 word[cursor_argo] = '\0';
313
314 if (cursor_argc == 0)
8
Assuming 'cursor_argc' is not equal to 0
9
Taking false branch
315 return (complete_command(word, dolist));
316
317 c = getcmd(margv(marg_sl->sl_str)[0]);
318 if (c == (struct cmd *)-1 || c == 0)
10
Assuming the condition is false
11
Assuming 'c' is not equal to null
12
Taking false branch
319 return (CC_ERROR6);
320 celems = strlen(c->c_complete);
321
322 /* check for 'continuation' completes (which are uppercase) */
323 if ((cursor_argc > celems) && (celems > 0)
13
Assuming 'cursor_argc' is <= 'celems'
324 && isupper((unsigned char)c->c_complete[celems - 1]))
325 cursor_argc = celems;
326
327 if (cursor_argc
13.1
'cursor_argc' is <= 'celems'
> celems)
14
Taking false branch
328 return (CC_ERROR6);
329
330 switch (c->c_complete[cursor_argc - 1]) {
15
Control jumps to 'case 82:' at line 335
331 case 'l': /* local complete */
332 case 'L':
333 return (complete_local(word, dolist));
334 case 'r': /* remote complete */
335 case 'R':
336 if (connected != -1) {
16
Assuming the condition is false
17
Taking false branch
337 fputs("\nMust be logged in to complete.\n", ttyout);
338 return (CC_REDISPLAY8);
339 }
340 return (complete_remote(word, dolist));
18
Calling 'complete_remote'
341 case 'c': /* command complete */
342 case 'C':
343 return (complete_command(word, dolist));
344 case 'n': /* no complete */
345 return (CC_ERROR6);
346 }
347
348 return (CC_ERROR6);
349}
350
351/*
352 * Copy characters from src into dst, \ quoting characters that require it.
353 */
354static void
355ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
356{
357 size_t di, si;
358
359 di = si = 0;
360 while (di + 1 < dstlen && si < srclen && src[si] != '\0') {
361 switch (src[si]) {
362 case '\\':
363 case ' ':
364 case '\t':
365 case '\r':
366 case '\n':
367 case '"':
368 /* Need room for two characters and NUL, avoiding
369 * incomplete escape sequences at end of dst. */
370 if (di + 3 >= dstlen)
371 break;
372 dst[di++] = '\\';
373 /* FALLTHROUGH */
374 default:
375 dst[di++] = src[si++];
376 }
377 }
378 if (dstlen != 0)
379 dst[di] = '\0';
380}
381#endif /* !SMALL */