Bug Summary

File:src/usr.bin/vi/build/../ex/ex_filter.c
Warning:line 130, column 24
Call to function 'vfork' is insecure as it can lead to denial of service situations in the parent process. Replace calls to vfork with calls to the safer 'posix_spawn' function

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 ex_filter.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/vi/build/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.bin/vi/build -I /usr/src/usr.bin/vi/build/../include -I . -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/vi/build/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/vi/build/../ex/ex_filter.c
1/* $OpenBSD: ex_filter.c,v 1.15 2016/08/01 18:27:35 bentley Exp $ */
2
3/*-
4 * Copyright (c) 1991, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 * Copyright (c) 1991, 1993, 1994, 1995, 1996
7 * Keith Bostic. All rights reserved.
8 *
9 * See the LICENSE file for redistribution information.
10 */
11
12#include "config.h"
13
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <sys/queue.h>
17
18#include <bitstring.h>
19#include <errno(*__errno()).h>
20#include <fcntl.h>
21#include <limits.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include "../common/common.h"
28
29static int filter_ldisplay(SCR *, FILE *);
30
31/*
32 * ex_filter --
33 * Run a range of lines through a filter utility and optionally
34 * replace the original text with the stdout/stderr output of
35 * the utility.
36 *
37 * PUBLIC: int ex_filter(SCR *,
38 * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype);
39 */
40int
41ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, char *cmd,
42 enum filtertype ftype)
43{
44 FILE *ifp, *ofp;
45 pid_t parent_writer_pid, utility_pid;
46 recno_t nread;
47 int input[2], output[2], fd, rval;
48 char *name, tname[] = "/tmp/vi.XXXXXXXXXX";
49
50 rval = 0;
51
52 /* Set return cursor position, which is never less than line 1. */
53 *rp = *fm;
54 if (rp->lno == 0)
55 rp->lno = 1;
56
57 /* We're going to need a shell. */
58 if (opts_empty(sp, O_SHELL, 0))
59 return (1);
60
61 /*
62 * There are three different processes running through this code.
63 * They are the utility, the parent-writer and the parent-reader.
64 * The parent-writer is the process that writes from the file to
65 * the utility, the parent reader is the process that reads from
66 * the utility.
67 *
68 * Input and output are named from the utility's point of view.
69 * The utility reads from input[0] and the parent(s) write to
70 * input[1]. The parent(s) read from output[0] and the utility
71 * writes to output[1].
72 *
73 * !!!
74 * Historically, in the FILTER_READ case, the utility reads from
75 * the terminal (e.g. :r! cat works). Otherwise open up utility
76 * input pipe.
77 */
78 ofp = NULL((void *)0);
79 input[0] = input[1] = output[0] = output[1] = -1;
80
81 if (ftype == FILTER_BANG) {
82 fd = mkstemp(tname);
83 if (fd == -1) {
84 msgq(sp, M_SYSERR,
85 "Unable to create temporary file");
86 if (fd != -1) {
87 (void)close(fd);
88 (void)unlink(tname);
89 }
90 goto err;
91 }
92 if (unlink(tname) == -1)
93 msgq(sp, M_SYSERR, "unlink");
94 if ((ifp = fdopen(fd, "w")) == NULL((void *)0)) {
95 msgq(sp, M_SYSERR, "fdopen");
96 (void)close(fd);
97 goto err;
98 }
99 if ((input[0] = dup(fd)) == -1) {
100 msgq(sp, M_SYSERR, "dup");
101 (void)fclose(ifp);
102 goto err;
103 }
104 /*
105 * Write the selected lines into the temporary file.
106 * This instance of ifp is closed by ex_writefp.
107 */
108 if (ex_writefp(sp, "filter", ifp, fm, tm, NULL((void *)0), NULL((void *)0), 1))
109 goto err;
110 if (lseek(input[0], 0, SEEK_SET0) == -1) {
111 msgq(sp, M_SYSERR, "lseek");
112 goto err;
113 }
114 } else if (ftype != FILTER_READ && pipe(input) < 0) {
115 msgq(sp, M_SYSERR, "pipe");
116 goto err;
117 }
118
119 /* Open up utility output pipe. */
120 if (pipe(output) < 0) {
121 msgq(sp, M_SYSERR, "pipe");
122 goto err;
123 }
124 if ((ofp = fdopen(output[0], "r")) == NULL((void *)0)) {
125 msgq(sp, M_SYSERR, "fdopen");
126 goto err;
127 }
128
129 /* Fork off the utility process. */
130 switch (utility_pid = vfork()) {
Call to function 'vfork' is insecure as it can lead to denial of service situations in the parent process. Replace calls to vfork with calls to the safer 'posix_spawn' function
131 case -1: /* Error. */
132 msgq(sp, M_SYSERR, "vfork");
133err: if (input[0] != -1)
134 (void)close(input[0]);
135 if (input[1] != -1)
136 (void)close(input[1]);
137 if (ofp != NULL((void *)0))
138 (void)fclose(ofp);
139 else if (output[0] != -1)
140 (void)close(output[0]);
141 if (output[1] != -1)
142 (void)close(output[1]);
143 return (1);
144 case 0: /* Utility. */
145 /*
146 * Redirect stdin from the read end of the input pipe, and
147 * redirect stdout/stderr to the write end of the output pipe.
148 *
149 * !!!
150 * Historically, ex only directed stdout into the input pipe,
151 * letting stderr come out on the terminal as usual. Vi did
152 * not, directing both stdout and stderr into the input pipe.
153 * We match that practice in both ex and vi for consistency.
154 */
155 if (input[0] != -1)
156 (void)dup2(input[0], STDIN_FILENO0);
157 (void)dup2(output[1], STDOUT_FILENO1);
158 (void)dup2(output[1], STDERR_FILENO2);
159
160 /* Close the utility's file descriptors. */
161 if (input[0] != -1)
162 (void)close(input[0]);
163 if (input[1] != -1)
164 (void)close(input[1]);
165 (void)close(output[0]);
166 (void)close(output[1]);
167
168 if ((name = strrchr(O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, '/')) == NULL((void *)0))
169 name = O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
;
170 else
171 ++name;
172
173 execl(O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, name, "-c", cmd, (char *)NULL((void *)0));
174 msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, "execl: %s");
175 _exit (127);
176 /* NOTREACHED */
177 default: /* Parent-reader, parent-writer. */
178 /* Close the pipe ends neither parent will use. */
179 if (input[0] != -1)
180 (void)close(input[0]);
181 (void)close(output[1]);
182 break;
183 }
184
185 /*
186 * FILTER_RBANG, FILTER_READ:
187 *
188 * Reading is the simple case -- we don't need a parent writer,
189 * so the parent reads the output from the read end of the output
190 * pipe until it finishes, then waits for the child. Ex_readfp
191 * appends to the MARK, and closes ofp.
192 *
193 * For FILTER_RBANG, there is nothing to write to the utility.
194 * Make sure it doesn't wait forever by closing its standard
195 * input.
196 *
197 * !!!
198 * Set the return cursor to the last line read in for FILTER_READ.
199 * Historically, this behaves differently from ":r file" command,
200 * which leaves the cursor at the first line read in. Check to
201 * make sure that it's not past EOF because we were reading into an
202 * empty file.
203 */
204 if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
205 if (ftype == FILTER_RBANG)
206 (void)close(input[1]);
207
208 if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
209 rval = 1;
210 sp->rptlines[L_ADDED0] += nread;
211 if (ftype == FILTER_READ) {
212 if (fm->lno == 0)
213 rp->lno = nread;
214 else
215 rp->lno += nread;
216 }
217 }
218
219 /*
220 * FILTER_WRITE
221 *
222 * Here we need both a reader and a writer. Temporary files are
223 * expensive and we'd like to avoid disk I/O. Using pipes has the
224 * obvious starvation conditions. It's done as follows:
225 *
226 * fork
227 * child
228 * write lines out
229 * exit
230 * parent
231 * read and display lines
232 * wait for child
233 *
234 * We get away without locking the underlying database because we know
235 * that filter_ldisplay() does not modify it. When the DB code has
236 * locking, we should treat vi as if it were multiple applications
237 * sharing a database, and do the required locking. If necessary a
238 * work-around would be to do explicit locking in the line.c:db_get()
239 * code, based on the flag set here.
240 */
241 if (ftype == FILTER_WRITE) {
242 F_SET(sp->ep, F_MULTILOCK)(((sp->ep)->flags) |= ((0x008)));
243 switch (parent_writer_pid = fork()) {
244 case -1: /* Error. */
245 msgq(sp, M_SYSERR, "fork");
246 (void)close(input[1]);
247 (void)close(output[0]);
248 rval = 1;
249 break;
250 case 0: /* Parent-writer. */
251 /*
252 * Write the selected lines to the write end of the
253 * input pipe. This instance of ifp is closed by
254 * ex_writefp.
255 */
256 (void)close(output[0]);
257 if ((ifp = fdopen(input[1], "w")) == NULL((void *)0))
258 _exit (1);
259 _exit(ex_writefp(sp, "filter",
260 ifp, fm, tm, NULL((void *)0), NULL((void *)0), 1));
261 /* NOTREACHED */
262 default: /* Parent-reader. */
263 (void)close(input[1]);
264 /*
265 * Read the output from the read end of the output
266 * pipe and display it. Filter_ldisplay closes ofp.
267 */
268 if (filter_ldisplay(sp, ofp))
269 rval = 1;
270
271 /* Wait for the parent-writer. */
272 if (proc_wait(sp,
273 parent_writer_pid, "parent-writer", 0, 1))
274 rval = 1;
275 break;
276 }
277 F_CLR(sp->ep, F_MULTILOCK)(((sp->ep)->flags) &= ~((0x008)));
278 }
279
280 /*
281 * FILTER_BANG
282 *
283 * Here we need a temporary file because our database lacks locking.
284 *
285 * XXX
286 * Temporary files are expensive and we'd like to avoid disk I/O.
287 * When the DB code has locking, we should treat vi as if it were
288 * multiple applications sharing a database, and do the required
289 * locking. If necessary a work-around would be to do explicit
290 * locking in the line.c:db_get() code, based on F_MULTILOCK flag set
291 * here.
292 */
293 if (ftype == FILTER_BANG) {
294 /*
295 * Read the output from the read end of the output
296 * pipe. Ex_readfp appends to the MARK and closes
297 * ofp.
298 */
299 if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
300 rval = 1;
301 sp->rptlines[L_ADDED0] += nread;
302
303 /* Delete any lines written to the utility. */
304 if (rval == 0 &&
305 (cut(sp, NULL((void *)0), fm, tm, CUT_LINEMODE0x01) ||
306 del(sp, fm, tm, 1))) {
307 rval = 1;
308 goto uwait;
309 }
310
311 /*
312 * If the filter had no output, we may have just deleted
313 * the cursor. Don't do any real error correction, we'll
314 * try and recover later.
315 */
316 if (rp->lno > 1 && !db_exist(sp, rp->lno))
317 --rp->lno;
318 }
319
320 /*
321 * !!!
322 * Ignore errors on vi file reads, to make reads prettier. It's
323 * completely inconsistent, and historic practice.
324 */
325uwait: return (proc_wait(sp, utility_pid, cmd,
326 ftype == FILTER_READ && F_ISSET(sp, SC_VI)(((sp)->flags) & ((0x00000002))) ? 1 : 0, 0) || rval);
327}
328
329/*
330 * filter_ldisplay --
331 * Display output from a utility.
332 *
333 * !!!
334 * Historically, the characters were passed unmodified to the terminal.
335 * We use the ex print routines to make sure they're printable.
336 */
337static int
338filter_ldisplay(SCR *sp, FILE *fp)
339{
340 size_t len;
341
342 EX_PRIVATE *exp;
343
344 for (exp = EXP(sp)((EX_PRIVATE *)((sp)->ex_private)); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp)(((((sp)->gp)->flags) & ((0x0004))) || (!v_event_get
((sp), ((void *)0), 0, 0x001) && ((((sp)->gp)->
flags) & ((0x0004)))))
;)
345 if (ex_ldisplay(sp, exp->ibp, len, 0, 0))
346 break;
347 if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror
)(fp))
)
348 msgq(sp, M_SYSERR, "filter read");
349 (void)fclose(fp);
350 return (0);
351}