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 |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | |
29 | static 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 | */ |
40 | int |
41 | ex_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"); |
133 | err: 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 | */ |
325 | uwait: 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 | */ |
337 | static int |
338 | filter_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 | } |