Bug Summary

File:samples/smolsite/main.c
Warning:line 157, column 13
Potential leak of memory pointed to by 'file'

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 main.c -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/samples/smolsite -resource-dir /usr/lib/clang/16 -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 /home/buildbot/lwan-worker/clang-analyze/build -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/16/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-override-init -Wno-free-nonheap-object -std=gnu11 -fdebug-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite -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/2024-02-24-173021-450469-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c
1/*
2 * smolsite.zip
3 * Copyright (c) 2023 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,
18 * USA.
19 */
20
21#define _GNU_SOURCE
22#include <ctype.h>
23#include <errno(*__errno_location ()).h>
24#include <fcntl.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include "base64.h"
29#include "hash.h"
30#include "int-to-str.h"
31#include "lwan.h"
32#include "sha1.h"
33#include "lwan-cache.h"
34#include "lwan-private.h"
35#include "lwan-template.h"
36
37#include "junzip.h"
38#include "qrcodegen.h"
39#include "../clock/gifenc.h"
40
41#include "smolsite.h"
42
43#define CACHE_FOR_MINUTES15 15
44
45static struct cache *sites;
46
47struct file {
48 ptrdiff_t data_offset;
49 size_t size_compressed;
50 const char *mime_type;
51 bool_Bool deflated;
52};
53
54struct site {
55 struct cache_entry entry;
56 struct lwan_value zipped;
57 struct hash *files;
58 struct lwan_strbuf qr_code;
59 int has_qr_code;
60};
61
62struct iframe_tpl_vars {
63 const char *digest;
64 int has_qr_code;
65};
66
67#undef TPL_STRUCTstruct iframe_tpl_vars
68#define TPL_STRUCTstruct iframe_tpl_vars struct iframe_tpl_vars
69static const struct lwan_var_descriptor iframe_tpl_desc[] = {
70 TPL_VAR_STR(digest){ .name = "digest", .offset = __builtin_offsetof(struct iframe_tpl_vars
, digest), .append_to_strbuf = lwan_append_str_to_strbuf, .get_is_empty
= lwan_tpl_str_is_empty }
,
71 TPL_VAR_INT(has_qr_code){ .name = "has_qr_code", .offset = __builtin_offsetof(struct iframe_tpl_vars
, has_qr_code), .append_to_strbuf = lwan_append_int_to_strbuf
, .get_is_empty = lwan_tpl_int_is_empty }
,
72 TPL_VAR_SENTINEL{ ((void*)0), 0, ((void*)0), ((void*)0), ((void*)0), ((void*)
0) }
,
73};
74
75static struct lwan_tpl *iframe_tpl;
76static struct lwan_value smolsite_zip_base64;
77
78static void calc_hash(struct lwan_value value,
79 char digest_str[static 41])
80{
81 /* FIXME: Is SHA-1 overkill? */
82 sha1_context ctx;
83 unsigned char digest[20];
84
85 sha1_init(&ctx);
86 sha1_update(&ctx, (const unsigned char *)value.value, value.len);
87 sha1_finalize(&ctx, digest);
88
89 snprintf(digest_str, 41,
90 "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
91 "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
92 digest[0], digest[1], digest[2], digest[3], digest[4], digest[5],
93 digest[6], digest[7], digest[8], digest[9], digest[10], digest[11],
94 digest[12], digest[13], digest[14], digest[15], digest[16],
95 digest[17], digest[18], digest[19]);
96}
97
98static struct hash *pending_sites(void)
99{
100 /* This is kind of a hack: we can't have just a single thread-local
101 * for the current thread's pending site because a coroutine might
102 * yield while trying to obtain an item from the sites cache, which
103 * would override that value. Store these in a thread-local hash
104 * table instead, which can be consulted by the create_site() function.
105 * Items are removed from this table in a defer handler. */
106 static __thread struct hash *pending_sites;
107
108 if (!pending_sites) {
109 pending_sites = hash_str_new(free, NULL((void*)0));
110 if (!pending_sites) {
111 lwan_status_critical("Could not allocate pending sites hash table")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 111, __FUNCTION__, "Could not allocate pending sites hash table"
)
;
112 }
113 }
114
115 return pending_sites;
116}
117
118static int file_cb(
119 JZFile *zip, int idx, JZFileHeader *header, char *filename, void *user_data)
120{
121 struct site *site = user_data;
122 char filename_buf[1024];
123 ptrdiff_t data_offset;
124 size_t cur_offset = zip->tell(zip);
125
126 if (zip->seek(zip, header->offset, SEEK_SET0))
1
Assuming the condition is false
2
Taking false branch
127 return 0;
128
129 JZFileHeader local;
130 if (jzReadLocalFileHeader(zip, &local, filename_buf, sizeof(filename_buf)))
3
Assuming the condition is false
4
Taking false branch
131 return 0;
132 if (__builtin_add_overflow(zip->tell(zip), local.offset, &data_offset))
5
Assuming the condition is false
6
Taking false branch
133 return 0;
134 if (data_offset < 0)
7
Assuming 'data_offset' is >= 0
8
Taking false branch
135 return 0;
136 if ((size_t)data_offset > site->zipped.len)
9
Assuming 'data_offset' is <= field 'len'
10
Taking false branch
137 return 0;
138
139 uint32_t last_data_offset;
140 if (__builtin_add_overflow(local.compressedSize, data_offset, &last_data_offset))
11
Assuming the condition is false
12
Taking false branch
141 return 0;
142 if (last_data_offset > site->zipped.len)
13
Assuming 'last_data_offset' is <= field 'len'
14
Taking false branch
143 return 0;
144
145 struct file *file = malloc(sizeof(*file));
15
Memory is allocated
146 if (!file)
16
Assuming 'file' is non-null
17
Taking false branch
147 return 0;
148
149 file->data_offset = data_offset;
150 file->deflated = local.compressionMethod == 8;
18
Assuming field 'compressionMethod' is not equal to 8
151 file->size_compressed = local.compressedSize;
152 file->mime_type = lwan_determine_mime_type_for_file_name(filename);
153
154 char *key = strdup(filename);
155 if (key) {
19
Assuming 'key' is non-null
20
Taking true branch
156 if (!hash_add_unique(site->files, key, file)) {
21
Assuming the condition is true
22
Taking true branch
157 zip->seek(zip, cur_offset, SEEK_SET0);
23
Potential leak of memory pointed to by 'file'
158 return 1;
159 }
160 }
161
162 free(key);
163 free(file);
164 return 0;
165}
166
167static bool_Bool generate_qr_code_gif(const char *b64, struct lwan_strbuf *output)
168{
169 uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX((((40) * 4 + 17) * ((40) * 4 + 17) + 7) / 8 + 1)];
170 uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX((((40) * 4 + 17) * ((40) * 4 + 17) + 7) / 8 + 1)];
171 char *url;
172 bool_Bool ok;
173
174 if (!lwan_strbuf_init(output))
175 return false0;
176
177 if (asprintf(&url, "https://smolsite.zip/%s", b64) < 0)
178 return false0;
179
180 ok = qrcodegen_encodeText(url, tempBuffer, qrcode, qrcodegen_Ecc_LOW,
181 qrcodegen_VERSION_MIN1, qrcodegen_VERSION_MAX40,
182 qrcodegen_Mask_AUTO, true1);
183 free(url);
184 if (!ok)
185 return false0;
186
187 int size = qrcodegen_getSize(qrcode);
188 if ((int)(uint16_t)size != size)
189 return false0;
190
191 ge_GIF *gif =
192 ge_new_gif(output, (uint16_t)size, (uint16_t)size, NULL((void*)0), 4, -1);
193 if (!gif) {
194 lwan_strbuf_free(output);
195 return false0;
196 }
197
198 for (int y = 0; y < size; y++) {
199 for (int x = 0; x < size; x++) {
200 gif->frame[y * size + x] = qrcodegen_getModule(qrcode, x, y) ? 0 : 15;
201 }
202 }
203 ge_add_frame(gif, 0);
204 ge_close_gif(gif);
205
206 return true1;
207}
208
209static struct cache_entry *create_site(const void *key, void *context)
210{
211 struct lwan_strbuf qr_code = LWAN_STRBUF_STATIC_INIT(struct lwan_strbuf) { .buffer = "" };
212 const struct lwan_value *base64_encoded =
213 hash_find(pending_sites(), (const void *)key);
214 unsigned char *decoded = NULL((void*)0);
215 size_t decoded_len;
216
217 if (!base64_encoded)
218 return NULL((void*)0);
219
220 if (UNLIKELY(!base64_validate((unsigned char *)base64_encoded->value,__builtin_expect(((!base64_validate((unsigned char *)base64_encoded
->value, base64_encoded->len))), (0))
221 base64_encoded->len))__builtin_expect(((!base64_validate((unsigned char *)base64_encoded
->value, base64_encoded->len))), (0))
) {
222 return NULL((void*)0);
223 }
224
225 decoded = base64_decode((unsigned char *)base64_encoded->value,
226 base64_encoded->len, &decoded_len);
227 if (UNLIKELY(!decoded)__builtin_expect(((!decoded)), (0)))
228 return NULL((void*)0);
229
230 struct site *site = malloc(sizeof(*site));
231 if (!site)
232 goto no_site;
233
234 site->has_qr_code =
235 generate_qr_code_gif((const char *)base64_encoded->value, &qr_code);
236
237 site->qr_code = qr_code;
238 site->zipped = (struct lwan_value) {.value = (char *)decoded, .len = decoded_len};
239
240 FILE *zip_mem = fmemopen(decoded, decoded_len, "rb");
241 if (!zip_mem)
242 goto no_file;
243
244 JZFile *zip = jzfile_from_stdio_file(zip_mem);
245 if (!zip) {
246 fclose(zip_mem);
247 goto no_file;
248 }
249
250 JZEndRecord end_record;
251 if (jzReadEndRecord(zip, &end_record))
252 goto no_end_record;
253
254 site->files = hash_str_new(free, free);
255 if (!site->files)
256 goto no_hash;
257
258 if (jzReadCentralDirectory(zip, &end_record, file_cb, site))
259 goto no_central_dir;
260
261 jzfile_free(zip);
262
263 return (struct cache_entry *)site;
264
265no_central_dir:
266 hash_free(site->files);
267no_hash:
268no_end_record:
269 jzfile_free(zip);
270no_file:
271 free(site);
272no_site:
273 free(decoded);
274 lwan_strbuf_free(&qr_code);
275 return NULL((void*)0);
276}
277
278static void destroy_site(struct cache_entry *entry, void *context)
279{
280 struct site *site = (struct site *)entry;
281 lwan_strbuf_free(&site->qr_code);
282 hash_free(site->files);
283 free(site->zipped.value);
284 free(site);
285}
286
287static void remove_from_pending_defer(void *data)
288{
289 char *key = data;
290 hash_del(pending_sites(), key);
291}
292
293LWAN_HANDLER_ROUTE(view_root, "/")static enum lwan_http_status lwan_handler_view_root( struct lwan_request
*, struct lwan_response *, void *); static const struct lwan_handler_info
__attribute__((used, section("lwan_handler"))) __attribute__
((aligned(8))) lwan_handler_info_view_root = { .name = "view_root"
, .route = "/", .handler = lwan_handler_view_root, }; __attribute__
((used)) static enum lwan_http_status lwan_handler_view_root(
struct lwan_request *request __attribute__((unused)), struct
lwan_response *response __attribute__((unused)), void *data __attribute__
((unused)))
294{
295 char digest_str[41];
296 struct site *site;
297
298 if (!request->url.len) {
299 const struct lwan_key_value redir_headers[] = {
300 {"Location", smolsite_zip_base64.value},
301 {"Cache-Control", "no-cache, max-age=0, private, no-transform"},
302 {},
303 };
304 response->headers = coro_memdup(request->conn->coro,
305 redir_headers,
306 sizeof(redir_headers));
307 return response->headers ? HTTP_TEMPORARY_REDIRECT : HTTP_INTERNAL_ERROR;
308 }
309
310 /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64
311 * alphabet */
312 for (char *p = strchr(request->url.value, ' '); p; p = strchr(p + 1, ' '))
313 *p = '+';
314
315 calc_hash(request->url, digest_str);
316
317 site = (struct site *)cache_coro_get_and_ref_entry(
318 sites, request->conn->coro, digest_str);
319 if (!site) {
320 char *key = strdup(digest_str);
321 if (!key) {
322 lwan_status_debug("a")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 322, __FUNCTION__, "a")
;
323 return HTTP_INTERNAL_ERROR;
324 }
325
326 if (UNLIKELY(hash_add_unique(pending_sites(), key, &request->url))__builtin_expect(((hash_add_unique(pending_sites(), key, &
request->url))), (0))
) {
327 lwan_status_debug("b")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 327, __FUNCTION__, "b")
;
328 return HTTP_INTERNAL_ERROR;
329 }
330
331 coro_defer(request->conn->coro, remove_from_pending_defer, key);
332
333 site = (struct site *)cache_coro_get_and_ref_entry(
334 sites, request->conn->coro, key);
335 if (UNLIKELY(!site)__builtin_expect(((!site)), (0))) {
336 lwan_status_debug("c")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 336, __FUNCTION__, "c")
;
337 return HTTP_INTERNAL_ERROR;
338}
339 }
340
341 response->mime_type = "text/html; charset=utf-8";
342
343 struct iframe_tpl_vars vars = {
344 .digest = digest_str,
345 .has_qr_code = site->has_qr_code,
346 };
347 if (!lwan_tpl_apply_with_buffer(iframe_tpl, response->buffer, &vars))
348 return HTTP_INTERNAL_ERROR;
349
350 return HTTP_OK;
351}
352
353LWAN_HANDLER_ROUTE(qr_code, "/q/")static enum lwan_http_status lwan_handler_qr_code( struct lwan_request
*, struct lwan_response *, void *); static const struct lwan_handler_info
__attribute__((used, section("lwan_handler"))) __attribute__
((aligned(8))) lwan_handler_info_qr_code = { .name = "qr_code"
, .route = "/q/", .handler = lwan_handler_qr_code, }; __attribute__
((used)) static enum lwan_http_status lwan_handler_qr_code( struct
lwan_request *request __attribute__((unused)), struct lwan_response
*response __attribute__((unused)), void *data __attribute__(
(unused)))
354{
355 struct site *site = (struct site *)cache_coro_get_and_ref_entry(
356 sites, request->conn->coro, request->url.value);
357 if (!site)
358 return HTTP_NOT_FOUND;
359 if (!site->has_qr_code)
360 return HTTP_NOT_FOUND;
361
362 lwan_strbuf_set_static(response->buffer,
363 lwan_strbuf_get_buffer(&site->qr_code),
364 lwan_strbuf_get_length(&site->qr_code));
365 response->mime_type = "image/gif";
366 return HTTP_OK;
367}
368
369LWAN_HANDLER_ROUTE(view_site, "/s/")static enum lwan_http_status lwan_handler_view_site( struct lwan_request
*, struct lwan_response *, void *); static const struct lwan_handler_info
__attribute__((used, section("lwan_handler"))) __attribute__
((aligned(8))) lwan_handler_info_view_site = { .name = "view_site"
, .route = "/s/", .handler = lwan_handler_view_site, }; __attribute__
((used)) static enum lwan_http_status lwan_handler_view_site(
struct lwan_request *request __attribute__((unused)), struct
lwan_response *response __attribute__((unused)), void *data __attribute__
((unused)))
370{
371 if (request->url.len < 40)
372 return HTTP_NOT_FOUND;
373
374 char *slash = memchr(request->url.value, '/', request->url.len);
375 if (!slash)
376 return HTTP_NOT_FOUND;
377 if (slash - request->url.value < 40)
378 return HTTP_NOT_FOUND;
379 *slash = '\0';
380 if (strcspn(request->url.value, "0123456789abcdefABCDEF"))
381 return HTTP_NOT_FOUND;
382
383 const char *file_name = slash + 1;
384
385 struct site *site = (struct site *)cache_coro_get_and_ref_entry(
386 sites, request->conn->coro, request->url.value);
387 if (!site)
388 return HTTP_NOT_FOUND;
389
390 if (*file_name == '\0')
391 file_name = "index.html";
392
393 struct file *file = hash_find(site->files, file_name);
394 if (!file)
395 return HTTP_NOT_FOUND;
396
397 if (file->deflated) {
398 enum lwan_request_flags accept =
399 lwan_request_get_accept_encoding(request);
400
401 if (!(accept & REQUEST_ACCEPT_DEFLATE))
402 return HTTP_NOT_ACCEPTABLE;
403
404 static const struct lwan_key_value deflate_headers[] = {
405 {"Content-Encoding", "deflate"},
406 {},
407 };
408 response->headers = deflate_headers;
409 }
410
411 lwan_strbuf_set_static(response->buffer,
412 site->zipped.value + file->data_offset,
413 file->size_compressed);
414 response->mime_type = file->mime_type;
415
416 return HTTP_OK;
417}
418
419static struct lwan_value base64_encode_to_value(struct lwan_value input)
420{
421 size_t len;
422 unsigned char *encoded =
423 base64_encode((unsigned char *)input.value, input.len, &len);
424 if (!encoded)
425 lwan_status_critical("Could not base64-encode smolsite.zip!")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 425, __FUNCTION__, "Could not base64-encode smolsite.zip!")
;
426 return (struct lwan_value){.value = (char *)encoded, .len = len};
427}
428
429int main(void)
430{
431 struct lwan l;
432
433 lwan_init(&l);
434 lwan_detect_url_map(&l);
435
436 smolsite_zip_base64 = base64_encode_to_value(smolsite_zip_value);
437
438 iframe_tpl = lwan_tpl_compile_value_full(smolsite_html_value,
439 iframe_tpl_desc,
440 LWAN_TPL_FLAG_CONST_TEMPLATE);
441 if (!iframe_tpl)
442 lwan_status_critical("Could not compile template")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 442, __FUNCTION__, "Could not compile template")
;
443
444 sites = cache_create_full(create_site, destroy_site, hash_str_new, NULL((void*)0),
445 CACHE_FOR_MINUTES15 * 60);
446 if (!sites)
447 lwan_status_critical("Could not create site cache")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c"
, 447, __FUNCTION__, "Could not create site cache")
;
448
449 lwan_main_loop(&l);
450 lwan_shutdown(&l);
451 cache_destroy(sites);
452 lwan_tpl_free(iframe_tpl);
453 free(smolsite_zip_base64.value);
454
455 return 0;
456}