Bug Summary

File:src/usr.bin/patch/inp.c
Warning:line 296, column 5
Potential leak of memory pointed to by 'p'

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 inp.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/patch/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/patch/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/patch/inp.c
1/* $OpenBSD: inp.c,v 1.49 2019/06/28 13:35:02 deraadt Exp $ */
2
3/*
4 * patch - a program to apply diffs to original files
5 *
6 * Copyright 1986, Larry Wall
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following condition is met:
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this condition and the following disclaimer.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
17 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 *
25 * -C option added in 1998, original code by Marc Espie, based on FreeBSD
26 * behaviour
27 */
28
29#include <sys/stat.h>
30#include <sys/mman.h>
31
32#include <ctype.h>
33#include <fcntl.h>
34#include <stddef.h>
35#include <stdint.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40
41#include "common.h"
42#include "util.h"
43#include "pch.h"
44#include "inp.h"
45
46
47/* Input-file-with-indexable-lines abstract type */
48
49static off_t i_size; /* size of the input file */
50static char *i_womp; /* plan a buffer for entire file */
51static char **i_ptr; /* pointers to lines in i_womp */
52
53static int tifd = -1; /* plan b virtual string array */
54static char *tibuf[2]; /* plan b buffers */
55static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */
56static size_t lines_per_buf; /* how many lines per buffer */
57static size_t tibuflen; /* plan b buffer length */
58static size_t tireclen; /* length of records in tmp file */
59
60static bool_Bool rev_in_string(const char *);
61static bool_Bool reallocate_lines(size_t *);
62
63/* returns false if insufficient memory */
64static bool_Bool plan_a(const char *);
65
66static void plan_b(const char *);
67
68/* New patch--prepare to edit another file. */
69
70void
71re_input(void)
72{
73 if (using_plan_a) {
74 free(i_ptr);
75 i_ptr = NULL((void*)0);
76 if (i_womp != NULL((void*)0)) {
77 munmap(i_womp, i_size);
78 i_womp = NULL((void*)0);
79 }
80 i_size = 0;
81 } else {
82 using_plan_a = true1; /* maybe the next one is smaller */
83 close(tifd);
84 tifd = -1;
85 free(tibuf[0]);
86 free(tibuf[1]);
87 tibuf[0] = tibuf[1] = NULL((void*)0);
88 tiline[0] = tiline[1] = -1;
89 tireclen = 0;
90 }
91}
92
93/* Construct the line index, somehow or other. */
94
95void
96scan_input(const char *filename)
97{
98 if (!plan_a(filename))
1
Taking true branch
99 plan_b(filename);
2
Calling 'plan_b'
100 if (verbose) {
101 say("Patching file %s using Plan %s...\n", filename,
102 (using_plan_a ? "A" : "B"));
103 }
104}
105
106static bool_Bool
107reallocate_lines(size_t *lines_allocatedp)
108{
109 char **p;
110 size_t new_size;
111
112 new_size = *lines_allocatedp * 3 / 2;
113 p = reallocarray(i_ptr, new_size + 2, sizeof(char *));
114 if (p == NULL((void*)0)) { /* shucks, it was a near thing */
115 munmap(i_womp, i_size);
116 i_womp = NULL((void*)0);
117 free(i_ptr);
118 i_ptr = NULL((void*)0);
119 *lines_allocatedp = 0;
120 return false0;
121 }
122 *lines_allocatedp = new_size;
123 i_ptr = p;
124 return true1;
125}
126
127/* Try keeping everything in memory. */
128
129static bool_Bool
130plan_a(const char *filename)
131{
132 int ifd, statfailed;
133 char *p, *s;
134 struct stat filestat;
135 off_t i;
136 ptrdiff_t sz;
137 size_t iline, lines_allocated;
138
139#ifdef DEBUGGING
140 if (debug & 8)
141 return false0;
142#endif
143
144 if (filename == NULL((void*)0) || *filename == '\0')
145 return false0;
146
147 statfailed = stat(filename, &filestat);
148 if (statfailed && ok_to_create_file) {
149 int fd;
150
151 if (verbose)
152 say("(Creating file %s...)\n", filename);
153
154 /*
155 * in check_patch case, we still display `Creating file' even
156 * though we're not. The rule is that -C should be as similar
157 * to normal patch behavior as possible
158 */
159 if (check_only)
160 return true1;
161 makedirs(filename, true1);
162 if ((fd = open(filename, O_CREAT0x0200 | O_TRUNC0x0400 | O_WRONLY0x0001, 0666)) != -1)
163 close(fd);
164
165 statfailed = stat(filename, &filestat);
166 }
167 if (statfailed)
168 fatal("can't find %s\n", filename);
169 filemode = filestat.st_mode;
170 if (!S_ISREG(filemode)((filemode & 0170000) == 0100000))
171 fatal("%s is not a normal file--can't patch\n", filename);
172 i_size = filestat.st_size;
173 if (out_of_mem) {
174 set_hunkmax(); /* make sure dynamic arrays are allocated */
175 out_of_mem = false0;
176 return false0; /* force plan b because plan a bombed */
177 }
178 if (i_size > SIZE_MAX0xffffffffffffffffUL) {
179 say("block too large to mmap\n");
180 return false0;
181 }
182 if ((ifd = open(filename, O_RDONLY0x0000)) == -1)
183 pfatal("can't open file %s", filename);
184
185 if (i_size) {
186 i_womp = mmap(NULL((void*)0), i_size, PROT_READ0x01, MAP_PRIVATE0x0002, ifd, 0);
187 if (i_womp == MAP_FAILED((void *)-1)) {
188 perror("mmap failed");
189 i_womp = NULL((void*)0);
190 close(ifd);
191 return false0;
192 }
193 } else {
194 i_womp = NULL((void*)0);
195 }
196
197 close(ifd);
198 if (i_size)
199 madvise(i_womp, i_size, MADV_SEQUENTIAL2);
200
201 /* estimate the number of lines */
202 lines_allocated = i_size / 25;
203 if (lines_allocated < 100)
204 lines_allocated = 100;
205
206 if (!reallocate_lines(&lines_allocated))
207 return false0;
208
209 /* now scan the buffer and build pointer array */
210 iline = 1;
211 i_ptr[iline] = i_womp;
212 /* test for NUL too, to maintain the behavior of the original code */
213 for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
214 if (*s == '\n') {
215 if (iline == lines_allocated) {
216 if (!reallocate_lines(&lines_allocated))
217 return false0;
218 }
219 /* these are NOT NUL terminated */
220 i_ptr[++iline] = s + 1;
221 }
222 }
223 /* if the last line contains no EOL, append one */
224 if (i_size > 0 && i_womp[i_size - 1] != '\n') {
225 last_line_missing_eol = true1;
226 /* fix last line */
227 sz = s - i_ptr[iline];
228 p = malloc(sz + 1);
229 if (p == NULL((void*)0)) {
230 free(i_ptr);
231 i_ptr = NULL((void*)0);
232 munmap(i_womp, i_size);
233 i_womp = NULL((void*)0);
234 return false0;
235 }
236
237 memcpy(p, i_ptr[iline], sz);
238 p[sz] = '\n';
239 i_ptr[iline] = p;
240 /* count the extra line and make it point to some valid mem */
241 i_ptr[++iline] = "";
242 } else
243 last_line_missing_eol = false0;
244
245 input_lines = iline - 1;
246
247 /* now check for revision, if any */
248
249 if (revision != NULL((void*)0)) {
250 if (i_womp == NULL((void*)0) || !rev_in_string(i_womp)) {
251 if (force) {
252 if (verbose)
253 say("Warning: this file doesn't appear "
254 "to be the %s version--patching anyway.\n",
255 revision);
256 } else if (batch) {
257 fatal("this file doesn't appear to be the "
258 "%s version--aborting.\n",
259 revision);
260 } else {
261 ask("This file doesn't appear to be the "
262 "%s version--patch anyway? [n] ",
263 revision);
264 if (*buf != 'y')
265 fatal("aborted\n");
266 }
267 } else if (verbose)
268 say("Good. This file appears to be the %s version.\n",
269 revision);
270 }
271 return true1; /* plan a will work */
272}
273
274/* Keep (virtually) nothing in memory. */
275
276static void
277plan_b(const char *filename)
278{
279 FILE *ifp;
280 size_t i = 0, j, len, maxlen = 1;
281 char *lbuf = NULL((void*)0), *p;
282 bool_Bool found_revision = (revision == NULL((void*)0));
3
Assuming 'revision' is not equal to NULL
283
284 using_plan_a = false0;
285 if ((ifp = fopen(filename, "r")) == NULL((void*)0))
4
Assuming the condition is false
5
Taking false branch
286 pfatal("can't open file %s", filename);
287 (void) unlink(TMPINNAME);
288 if ((tifd = open(TMPINNAME, O_EXCL0x0800 | O_CREAT0x0200 | O_WRONLY0x0001, 0666)) == -1)
6
Assuming the condition is false
7
Taking false branch
289 pfatal("can't open file %s", TMPINNAME);
290 while ((p = fgetln(ifp, &len)) != NULL((void*)0)) {
8
Assuming the condition is true
9
Loop condition is true. Entering loop body
18
Assuming the condition is true
19
Loop condition is true. Entering loop body
291 if (p[len - 1] == '\n')
10
Assuming the condition is false
11
Taking false branch
20
Assuming the condition is false
21
Taking false branch
292 p[len - 1] = '\0';
293 else {
294 /* EOF without EOL, copy and add the NUL */
295 if ((lbuf = malloc(len + 1)) == NULL((void*)0))
12
Memory is allocated
13
Assuming the condition is false
14
Taking false branch
22
Assuming the condition is true
23
Taking true branch
296 fatal("out of memory\n");
24
Potential leak of memory pointed to by 'p'
297 memcpy(lbuf, p, len);
298 lbuf[len] = '\0';
299 p = lbuf;
300
301 last_line_missing_eol = true1;
302 len++;
303 }
304 if (revision
14.1
'revision' is not equal to NULL
!= NULL((void*)0) && !found_revision
14.2
'found_revision' is false
&& rev_in_string(p))
15
Assuming the condition is false
16
Taking false branch
305 found_revision = true1;
306 if (len
16.1
'len' is > 'maxlen'
> maxlen)
17
Taking true branch
307 maxlen = len; /* find longest line */
308 }
309 free(lbuf);
310 if (ferror(ifp)(!__isthreaded ? (((ifp)->_flags & 0x0040) != 0) : (ferror
)(ifp))
)
311 pfatal("can't read file %s", filename);
312
313 if (revision != NULL((void*)0)) {
314 if (!found_revision) {
315 if (force) {
316 if (verbose)
317 say("Warning: this file doesn't appear "
318 "to be the %s version--patching anyway.\n",
319 revision);
320 } else if (batch) {
321 fatal("this file doesn't appear to be the "
322 "%s version--aborting.\n",
323 revision);
324 } else {
325 ask("This file doesn't appear to be the %s "
326 "version--patch anyway? [n] ",
327 revision);
328 if (*buf != 'y')
329 fatal("aborted\n");
330 }
331 } else if (verbose)
332 say("Good. This file appears to be the %s version.\n",
333 revision);
334 }
335 fseek(ifp, 0L, SEEK_SET0); /* rewind file */
336 tireclen = maxlen;
337 tibuflen = maxlen > BUFFERSIZE1024 ? maxlen : BUFFERSIZE1024;
338 lines_per_buf = tibuflen / maxlen;
339 tibuf[0] = malloc(tibuflen + 1);
340 if (tibuf[0] == NULL((void*)0))
341 fatal("out of memory\n");
342 tibuf[1] = malloc(tibuflen + 1);
343 if (tibuf[1] == NULL((void*)0))
344 fatal("out of memory\n");
345 for (i = 1;; i++) {
346 p = tibuf[0] + maxlen * (i % lines_per_buf);
347 if (i % lines_per_buf == 0) /* new block */
348 if (write(tifd, tibuf[0], tibuflen) !=
349 (ssize_t) tibuflen)
350 pfatal("can't write temp file");
351 if (fgets(p, maxlen + 1, ifp) == NULL((void*)0)) {
352 input_lines = i - 1;
353 if (i % lines_per_buf != 0)
354 if (write(tifd, tibuf[0], tibuflen) !=
355 (ssize_t) tibuflen)
356 pfatal("can't write temp file");
357 break;
358 }
359 j = strlen(p);
360 /* These are '\n' terminated strings, so no need to add a NUL */
361 if (j == 0 || p[j - 1] != '\n')
362 p[j] = '\n';
363 }
364 fclose(ifp);
365 close(tifd);
366 if ((tifd = open(TMPINNAME, O_RDONLY0x0000)) == -1)
367 pfatal("can't reopen file %s", TMPINNAME);
368}
369
370/*
371 * Fetch a line from the input file, \n terminated, not necessarily \0.
372 */
373char *
374ifetch(LINENUM line, int whichbuf)
375{
376 if (line < 1 || line > input_lines) {
377 if (warn_on_invalid_line) {
378 say("No such line %ld in input file, ignoring\n", line);
379 warn_on_invalid_line = false0;
380 }
381 return NULL((void*)0);
382 }
383 if (using_plan_a)
384 return i_ptr[line];
385 else {
386 LINENUM offline = line % lines_per_buf;
387 LINENUM baseline = line - offline;
388
389 if (tiline[0] == baseline)
390 whichbuf = 0;
391 else if (tiline[1] == baseline)
392 whichbuf = 1;
393 else {
394 tiline[whichbuf] = baseline;
395
396 if (lseek(tifd, (off_t) (baseline / lines_per_buf *
397 tibuflen), SEEK_SET0) == -1)
398 pfatal("cannot seek in the temporary input file");
399
400 if (read(tifd, tibuf[whichbuf], tibuflen)
401 != (ssize_t) tibuflen)
402 pfatal("error reading tmp file %s", TMPINNAME);
403 }
404 return tibuf[whichbuf] + (tireclen * offline);
405 }
406}
407
408/*
409 * True if the string argument contains the revision number we want.
410 */
411static bool_Bool
412rev_in_string(const char *string)
413{
414 const char *s;
415 size_t patlen;
416
417 if (revision == NULL((void*)0))
418 return true1;
419 patlen = strlen(revision);
420 if (strnEQ(string, revision, patlen)(!strncmp(string, revision, patlen)) &&
421 isspace((unsigned char)string[patlen]))
422 return true1;
423 for (s = string; *s; s++) {
424 if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen)(!strncmp(s + 1, revision, patlen)) &&
425 isspace((unsigned char)s[patlen + 1])) {
426 return true1;
427 }
428 }
429 return false0;
430}