Bug Summary

File:mimegen.c
Warning:line 343, 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 -clear-ast-before-backend -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 -ffp-contract=on -fno-rounding-math -mconstructor-aliases -fno-plt -funwind-tables=2 -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/14.0.6 -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.1 -I /usr/include/valgrind -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib -internal-isystem /usr/lib/clang/14.0.6/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-free-nonheap-object -std=gnu11 -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/2022-12-14-044102-331849-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/mimegen.c
1/*
2 * lwan - 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(LWAN_HAVE_BROTLI)
30#include <brotli/encode.h>
31#elif defined(LWAN_HAVE_ZSTD)
32#include <zstd.h>
33#elif defined(LWAN_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) {
22
Assuming 'total_size' is >= field 'capacity'
23
Taking true branch
52 char *tmp;
53
54 while (total_size
23.1
'total_size' is >= field 'capacity'
>= output->capacity
)
24
Loop condition is true. Entering loop body
25
Assuming 'total_size' is < field 'capacity'
26
Loop condition is false. Execution continues on line 57
55 output->capacity *= 2;
56
57 tmp = realloc(output->ptr, output->capacity);
27
Memory is allocated
58 if (!tmp)
28
Assuming 'tmp' is non-null
29
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)__builtin_assume(str_len <= 8);
17
Assuming 'str_len' is <= 8
75
76 int r = output_append_full(output, str, str_len);
77 if (r
17.1
'r' is >= 0
< 0)
18
Taking false branch
78 return r;
79
80 if (str_len != 8)
19
Assuming 'str_len' is not equal to 8
20
Taking true branch
81 return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len);
21
Calling 'output_append_full'
30
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(LWAN_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(LWAN_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(LWAN_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_ext(const char *ext)
175{
176 /* STRING_SWITCH_L() is not used here to not bring in lwan.h */
177 /* FIXME: maybe use an X-macro to keep in sync with lwan-tables.c? */
178 if (strcaseequal_neutral(ext, "css"))
179 return true1;
180 if (strcaseequal_neutral(ext, "gif"))
181 return true1;
182 if (strcaseequal_neutral(ext, "htm"))
183 return true1;
184 if (strcaseequal_neutral(ext, "html"))
185 return true1;
186 if (strcaseequal_neutral(ext, "jpg"))
187 return true1;
188 if (strcaseequal_neutral(ext, "js"))
189 return true1;
190 if (strcaseequal_neutral(ext, "png"))
191 return true1;
192 if (strcaseequal_neutral(ext, "txt"))
193 return true1;
194 return false0;
195}
196
197int main(int argc, char *argv[])
198{
199 /* 32k is sufficient for the provided mime.types, but we can reallocate
200 * if necessary. This is just to avoid unneccessary reallocs. */
201 struct output output = { .capacity = 32768 };
202 FILE *fp;
203 char buffer[256];
204 size_t compressed_size;
205 char *compressed, *ext;
206 struct hash *ext_mime;
207 struct hash_iter iter;
208 const char **exts, *key;
209 size_t i;
210
211 if (argc < 2) {
1
Assuming 'argc' is >= 2
2
Taking false branch
212 fprintf(stderrstderr, "Usage: %s /path/to/mime.types\n", argv[0]);
213 return 1;
214 }
215
216 fp = fopen(argv[1], "re");
217 if (!fp) {
3
Assuming 'fp' is non-null
4
Taking false branch
218 fprintf(stderrstderr, "Could not open %s: %s\n", argv[1], strerror(errno(*__errno_location ())));
219 return 1;
220 }
221
222 ext_mime = hash_str_new(free, free);
223 if (!ext_mime) {
5
Assuming 'ext_mime' is non-null
6
Taking false branch
224 fprintf(stderrstderr, "Could not allocate hash table\n");
225 fclose(fp);
226 return 1;
227 }
228
229 while (fgets(buffer, sizeof(buffer), fp)) {
7
Loop condition is false. Execution continues on line 300
230 char *start = buffer, *end, *tab, *mime_type;
231
232 while (*start && isspace(*start)((*__ctype_b_loc ())[(int) ((*start))] & (unsigned short int
) _ISspace)
) /* Strip spaces at the start. */
233 start++;
234 if (*start == '#') /* Ignore commented-out lines. */
235 continue;
236
237 strend(start, '\n'); /* Strip line endings. */
238 strend(start, '#'); /* Strip comments from the middle. */
239 tab = strend(start, '\t');
240 if (!tab) /* Find mime-type/extension separator. */
241 continue;
242
243 mime_type = start;
244 /* "application/octet-stream" is the fallback, so no need to store
245 * it in the table. It's just one line, though, so maybe not really
246 * necessary? */
247 if (streq(mime_type, "application/octet-stream"))
248 continue;
249
250 while (*tab && *tab == '\t') /* Find first extension. */
251 tab++;
252
253 for (ext = tab; *ext; ext += end - ext + 1) {
254 char *k, *v;
255 int r;
256
257 end = strchr(ext, ' '); /* Stop at next extension. */
258 if (!end)
259 end = strchr(ext, '\0'); /* If not found, find last extension. */
260 *end = '\0';
261
262 /* Check if we have empty extensions. Shouldn't happen with the provided
263 * mime.types file, but check on debug builds if this ever happens. */
264 assert(end != ext)__builtin_assume(end != ext);
265
266 if (end - ext > 8) {
267 /* Truncate extensions over 8 characters. See commit 2050759297. */
268 ext[8] = '\0';
269 }
270
271 /* Lwan has a fast-path for some common extensions, so don't bundle them
272 * in this table if not really needed. */
273 if (is_builtin_ext(ext))
274 continue;
275
276 k = strdup(ext);
277 v = strdup(mime_type);
278
279 if (!k || !v) {
280 fprintf(stderrstderr, "Could not allocate memory\n");
281 fclose(fp);
282 return 1;
283 }
284
285 r = hash_add_unique(ext_mime, k, v);
286 if (r < 0) {
287 free(k);
288 free(v);
289
290 if (r != -EEXIST17) {
291 fprintf(stderrstderr, "Could not add extension to hash table\n");
292 fclose(fp);
293 return 1;
294 }
295 }
296 }
297 }
298
299 /* Get sorted list of extensions. */
300 exts = calloc(hash_get_count(ext_mime), sizeof(char *));
301 if (!exts) {
8
Assuming 'exts' is non-null
9
Taking false branch
302 fprintf(stderrstderr, "Could not allocate extension array\n");
303 fclose(fp);
304 return 1;
305 }
306 hash_iter_init(ext_mime, &iter);
307 for (i = 0; hash_iter_next(&iter, (const void **)&key, NULL((void*)0)); i++)
10
Loop condition is false. Execution continues on line 309
308 exts[i] = key;
309 qsort(exts, hash_get_count(ext_mime), sizeof(char *), compare_ext);
310
311 /* Generate uncompressed blob. */
312 output.ptr = malloc(output.capacity);
313 if (!output.ptr) {
11
Assuming field 'ptr' is non-null
12
Taking false branch
314 fprintf(stderrstderr, "Could not allocate temporary memory\n");
315 fclose(fp);
316 return 1;
317 }
318 for (i = 0; i < hash_get_count(ext_mime); i++) {
13
Assuming the condition is true
14
Loop condition is true. Entering loop body
33
Assuming the condition is false
34
Loop condition is false. Execution continues on line 332
319 char ext_lower[9] = {0};
320
321 strncpy(ext_lower, exts[i], 8);
322
323 for (char *p = ext_lower; *p; p++)
15
Loop condition is false. Execution continues on line 326
324 *p &= ~0x20;
325
326 if (output_append_padded(&output, ext_lower) < 0) {
16
Calling 'output_append_padded'
31
Returned allocated memory
32
Taking false branch
327 fprintf(stderrstderr, "Could not append to output\n");
328 fclose(fp);
329 return 1;
330 }
331 }
332 for (i = 0; i < hash_get_count(ext_mime); i++) {
35
Assuming the condition is false
36
Loop condition is false. Execution continues on line 341
333 if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) {
334 fprintf(stderrstderr, "Could not append to output\n");
335 fclose(fp);
336 return 1;
337 }
338 }
339
340 /* Compress blob. */
341 compressed = compress_output(&output, &compressed_size);
342 if (!compressed
36.1
'compressed' is null
) {
37
Taking true branch
343 fprintf(stderrstderr, "Could not compress data\n");
38
Potential leak of memory pointed to by 'output.ptr'
344 fclose(fp);
345 return 1;
346 }
347
348 /* Print output. */
349#if defined(LWAN_HAVE_BROTLI)
350 printf("/* Compressed with brotli */\n");
351#elif defined(LWAN_HAVE_ZSTD)
352 printf("/* Compressed with zstd */\n");
353#elif defined(LWAN_HAVE_ZOPFLI)
354 printf("/* Compressed with zopfli (deflate) */\n");
355#else
356 printf("/* Compressed with zlib (deflate) */\n");
357#endif
358 printf("#pragma once\n");
359 printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used);
360 printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size);
361 printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime));
362 printf("static const unsigned char mime_entries_compressed[] = {\n");
363 for (i = 1; compressed_size; compressed_size--, i++)
364 printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]);
365 printf("};\n");
366
367 free(compressed);
368 free(output.ptr);
369 free(exts);
370 hash_free(ext_mime);
371 fclose(fp);
372
373 return 0;
374}