Bug Summary

File:bin/tools/mimegen.c
Warning:line 322, column 9
Potential leak of memory pointed to by 'output.ptr'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name mimegen.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 2 -pic-is-pie -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -fno-plt -munwind-tables -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -resource-dir /usr/lib/clang/13.0.0 -include /home/buildbot/lwan-worker/clang-analyze/build/lwan-build-config.h -D _FILE_OFFSET_BITS=64 -D _TIME_BITS=64 -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib/missing -I /usr/include/luajit-2.0 -I /usr/include/valgrind -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib -internal-isystem /usr/lib/clang/13.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/11.1.0/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-free-nonheap-object -std=gnu99 -fdebug-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/buildbot/lwan-worker/clang-analyze/CLANG/2021-12-15-043355-1552101-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/mimegen.c
1/*
2 * lwan - simple web server
3 * Copyright (c) 2016 L. A. F. Pereira <l@tia.mat.br>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include <assert.h>
21#include <ctype.h>
22#include <errno(*__errno_location ()).h>
23#include <fcntl.h>
24#include <stdint.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28
29#if defined(HAVE_BROTLI)
30#include <brotli/encode.h>
31#elif defined(HAVE_ZSTD)
32#include <zstd.h>
33#elif defined(HAVE_ZOPFLI)
34#include <zopfli/zopfli.h>
35#else
36#include <zlib.h>
37#endif
38
39#include "../../lib/hash.h"
40
41struct output {
42 char *ptr;
43 size_t used, capacity;
44};
45
46static int
47output_append_full(struct output *output, const char *str, size_t str_len)
48{
49 size_t total_size = output->used + str_len;
50
51 if (total_size >= output->capacity) {
23
Assuming 'total_size' is >= field 'capacity'
24
Taking true branch
52 char *tmp;
53
54 while (total_size
24.1
'total_size' is >= field 'capacity'
>= output->capacity
)
25
Loop condition is true. Entering loop body
26
Assuming 'total_size' is < field 'capacity'
27
Loop condition is false. Execution continues on line 57
55 output->capacity *= 2;
56
57 tmp = realloc(output->ptr, output->capacity);
28
Memory is allocated
58 if (!tmp)
29
Assuming 'tmp' is non-null
30
Taking false branch
59 return -errno(*__errno_location ());
60
61 output->ptr = tmp;
62 }
63
64 memcpy(output->ptr + output->used, str, str_len);
65 output->used = total_size;
66
67 return 0;
68}
69
70static int output_append_padded(struct output *output, const char *str)
71{
72 size_t str_len = strlen(str);
73
74 assert(str_len <= 8)((void) sizeof ((str_len <= 8) ? 1 : 0), __extension__ ({ if
(str_len <= 8) ; else __assert_fail ("str_len <= 8", "/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/mimegen.c"
, 74, __extension__ __PRETTY_FUNCTION__); }))
;
17
Assuming 'str_len' is <= 8
18
Taking true branch
75
76 int r = output_append_full(output, str, str_len);
77 if (r
18.1
'r' is >= 0
< 0)
19
Taking false branch
78 return r;
79
80 if (str_len != 8)
20
Assuming 'str_len' is not equal to 8
21
Taking true branch
81 return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len);
22
Calling 'output_append_full'
31
Returned allocated memory
82
83 return 0;
84}
85
86static int output_append(struct output *output, const char *str)
87{
88 return output_append_full(output, str, strlen(str) + 1);
89}
90
91static int compare_ext(const void *a, const void *b)
92{
93 const char **exta = (const char **)a;
94 const char **extb = (const char **)b;
95
96 return strcasecmp(*exta, *extb);
97}
98
99static char *strend(char *str, char ch)
100{
101 str = strchr(str, ch);
102 if (str) {
103 *str = '\0';
104 return str + 1;
105 }
106 return NULL((void*)0);
107}
108
109static char *compress_output(const struct output *output, size_t *outlen)
110{
111 char *compressed;
112
113#if defined(HAVE_BROTLI)
114 *outlen = BrotliEncoderMaxCompressedSize(output->used);
115
116 compressed = malloc(*outlen);
117 if (!compressed) {
118 fprintf(stderrstderr, "Could not allocate memory for compressed data\n");
119 exit(1);
120 }
121
122 if (BrotliEncoderCompress(BROTLI_MAX_QUALITY11, BROTLI_MAX_WINDOW_BITS24,
123 BROTLI_MODE_TEXT, output->used,
124 (const unsigned char *)output->ptr, outlen,
125 (unsigned char *)compressed) != BROTLI_TRUE1) {
126 fprintf(stderrstderr, "Could not compress mime type table with Brotli\n");
127 exit(1);
128 }
129#elif defined(HAVE_ZSTD)
130 *outlen = ZSTD_compressBound(output->used);
131
132 compressed = malloc(*outlen);
133 if (!compressed) {
134 fprintf(stderrstderr, "Could not allocate memory for compressed data\n");
135 exit(1);
136 }
137
138 *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used,
139 ZSTD_maxCLevel());
140 if (ZSTD_isError(*outlen)) {
141 fprintf(stderrstderr, "Could not compress mime type table with ZSTD\n");
142 exit(1);
143 }
144#elif defined(HAVE_ZOPFLI)
145 ZopfliOptions opts;
146
147 *outlen = 0;
148
149 ZopfliInitOptions(&opts);
150 ZopfliCompress(&opts, ZOPFLI_FORMAT_ZLIB,
151 (const unsigned char *)output->ptr, output->used,
152 (unsigned char **)&compressed, outlen);
153#else
154 *outlen = compressBound((uLong)output->used);
155 compressed = malloc(*outlen);
156 if (!compressed) {
157 fprintf(stderrstderr, "Could not allocate memory for compressed data\n");
158 exit(1);
159 }
160 if (compress2((Bytef *)compressed, outlen, (const Bytef *)output->ptr,
161 output->used, 9) != Z_OK) {
162 fprintf(stderrstderr, "Could not compress data with zlib\n");
163 exit(1);
164 }
165#endif
166 if (!*outlen) {
167 free(compressed);
168 return NULL((void*)0);
169 }
170
171 return compressed;
172}
173
174static bool_Bool is_builtin_mime_type(const char *mime)
175{
176 /* These are the mime types supported by Lwan without having to perform
177 * a bsearch(). application/octet-stream is the fallback. */
178 if (streq(mime, "application/octet-stream"))
179 return true1;
180 if (streq(mime, "application/javascript"))
181 return true1;
182 if (streq(mime, "image/jpeg"))
183 return true1;
184 if (streq(mime, "image/gif"))
185 return true1;
186 if (streq(mime, "image/png"))
187 return true1;
188 if (streq(mime, "text/html"))
189 return true1;
190 if (streq(mime, "text/css"))
191 return true1;
192 if (streq(mime, "text/plain"))
193 return true1;
194 return false0;
195}
196
197int main(int argc, char *argv[])
198{
199 FILE *fp;
200 char buffer[256];
201 struct output output = { .capacity = 1024 };
202 size_t compressed_size;
203 char *compressed, *ext;
204 struct hash *ext_mime;
205 struct hash_iter iter;
206 const char **exts, *key;
207 size_t i;
208
209 if (argc < 2) {
1
Assuming 'argc' is >= 2
2
Taking false branch
210 fprintf(stderrstderr, "Usage: %s /path/to/mime.types\n", argv[0]);
211 return 1;
212 }
213
214 fp = fopen(argv[1], "re");
215 if (!fp) {
3
Assuming 'fp' is non-null
4
Taking false branch
216 fprintf(stderrstderr, "Could not open %s: %s\n", argv[1], strerror(errno(*__errno_location ())));
217 return 1;
218 }
219
220 ext_mime = hash_str_new(free, free);
221 if (!ext_mime) {
5
Assuming 'ext_mime' is non-null
6
Taking false branch
222 fprintf(stderrstderr, "Could not allocate hash table\n");
223 return 1;
224 }
225
226 while (fgets(buffer, sizeof(buffer), fp)) {
7
Loop condition is false. Execution continues on line 283
227 char *start = buffer, *end, *tab, *mime_type;
228
229 while (*start && isspace(*start)((*__ctype_b_loc ())[(int) ((*start))] & (unsigned short int
) _ISspace)
) /* Strip spaces at the start. */
230 start++;
231 if (*start == '#') /* Ignore commented-out lines. */
232 continue;
233
234 strend(start, '\n'); /* Strip line endings. */
235 strend(start, '#'); /* Strip comments from the middle. */
236 tab = strend(start, '\t');
237 if (!tab) /* Find mime-type/extension separator. */
238 continue;
239
240 mime_type = start;
241 if (is_builtin_mime_type(mime_type))
242 continue;
243
244 while (*tab && *tab == '\t') /* Find first extension. */
245 tab++;
246
247 for (ext = tab; *ext; ext += end - ext + 1) {
248 char *k, *v;
249 int r;
250
251 end = strchr(ext, ' '); /* Stop at next extension. */
252 if (!end)
253 end = strchr(ext, '\0'); /* If not found, find last extension. */
254 *end = '\0';
255
256 if (end - ext > 8) {
257 /* Truncate extensions over 8 characters. See commit 2050759297. */
258 ext[8] = '\0';
259 }
260
261 k = strdup(ext);
262 v = strdup(mime_type);
263
264 if (!k || !v) {
265 fprintf(stderrstderr, "Could not allocate memory\n");
266 return 1;
267 }
268
269 r = hash_add_unique(ext_mime, k, v);
270 if (r < 0) {
271 free(k);
272 free(v);
273
274 if (r != -EEXIST17) {
275 fprintf(stderrstderr, "Could not add extension to hash table\n");
276 return 1;
277 }
278 }
279 }
280 }
281
282 /* Get sorted list of extensions. */
283 exts = calloc(hash_get_count(ext_mime), sizeof(char *));
284 if (!exts) {
8
Assuming 'exts' is non-null
9
Taking false branch
285 fprintf(stderrstderr, "Could not allocate extension array\n");
286 return 1;
287 }
288 hash_iter_init(ext_mime, &iter);
289 for (i = 0; hash_iter_next(&iter, (const void **)&key, NULL((void*)0)); i++)
10
Loop condition is false. Execution continues on line 291
290 exts[i] = key;
291 qsort(exts, hash_get_count(ext_mime), sizeof(char *), compare_ext);
292
293 /* Generate uncompressed blob. */
294 output.ptr = malloc(output.capacity);
295 if (!output.ptr) {
11
Assuming field 'ptr' is non-null
12
Taking false branch
296 fprintf(stderrstderr, "Could not allocate temporary memory\n");
297 return 1;
298 }
299 for (i = 0; i < hash_get_count(ext_mime); i++) {
13
Assuming the condition is true
14
Loop condition is true. Entering loop body
34
Assuming the condition is false
35
Loop condition is false. Execution continues on line 312
300 char ext_lower[9] = {0};
301
302 strncpy(ext_lower, exts[i], 8);
303
304 for (char *p = ext_lower; *p; p++)
15
Loop condition is false. Execution continues on line 307
305 *p |= 0x20;
306
307 if (output_append_padded(&output, ext_lower) < 0) {
16
Calling 'output_append_padded'
32
Returned allocated memory
33
Taking false branch
308 fprintf(stderrstderr, "Could not append to output\n");
309 return 1;
310 }
311 }
312 for (i = 0; i < hash_get_count(ext_mime); i++) {
36
Assuming the condition is false
37
Loop condition is false. Execution continues on line 320
313 if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) {
314 fprintf(stderrstderr, "Could not append to output\n");
315 return 1;
316 }
317 }
318
319 /* Compress blob. */
320 compressed = compress_output(&output, &compressed_size);
321 if (!compressed
37.1
'compressed' is null
) {
38
Taking true branch
322 fprintf(stderrstderr, "Could not compress data\n");
39
Potential leak of memory pointed to by 'output.ptr'
323 return 1;
324 }
325
326 /* Print output. */
327#if defined(HAVE_BROTLI)
328 printf("/* Compressed with brotli */\n");
329#elif defined(HAVE_ZSTD)
330 printf("/* Compressed with zstd */\n");
331#elif defined(HAVE_ZOPFLI)
332 printf("/* Compressed with zopfli (deflate) */\n");
333#else
334 printf("/* Compressed with zlib (deflate) */\n");
335#endif
336 printf("#pragma once\n");
337 printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used);
338 printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size);
339 printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime));
340 printf("static const unsigned char mime_entries_compressed[] = {\n");
341 for (i = 1; compressed_size; compressed_size--, i++)
342 printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]);
343 printf("};\n");
344
345 free(compressed);
346 free(output.ptr);
347 free(exts);
348 hash_free(ext_mime);
349 fclose(fp);
350
351 return 0;
352}