Bug Summary

File:var/lib/jenkins/workspace/firefox-scan-build/modules/libmar/src/mar_create.c
Warning:line 386, column 3
Potential leak of memory pointed to by 'stack.head'

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 mar_create.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 -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/modules/libmar/src -fcoverage-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/modules/libmar/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D XP_UNIX -D DEBUG=1 -I /var/lib/jenkins/workspace/firefox-scan-build/modules/libmar/src -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/modules/libmar/src -I /var/lib/jenkins/workspace/firefox-scan-build/other-licenses/nsis/Contrib/CityHash/cityhash -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nspr -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2024-05-16-034744-15991-1 -x c /var/lib/jenkins/workspace/firefox-scan-build/modules/libmar/src/mar_create.c
1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* vim:set ts=2 sw=2 sts=2 et cindent: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7#include <sys/types.h>
8#include <sys/stat.h>
9#include <fcntl.h>
10#include <stdlib.h>
11#include <string.h>
12#include "mar_private.h"
13#include "mar_cmdline.h"
14#include "mar.h"
15
16#ifdef XP_WIN
17# include <winsock2.h>
18#else
19# include <netinet/in.h>
20# include <unistd.h>
21#endif
22
23struct MarItemStack {
24 void* head;
25 uint32_t size_used;
26 uint32_t size_allocated;
27 uint32_t last_offset;
28};
29
30/**
31 * Push a new item onto the stack of items. The stack is a single block
32 * of memory.
33 */
34static int mar_push(struct MarItemStack* stack, uint32_t length, uint32_t flags,
35 const char* name) {
36 int namelen;
37 uint32_t n_offset, n_length, n_flags;
38 uint32_t size;
39 char* data;
40
41 namelen = strlen(name);
42 size = MAR_ITEM_SIZE(namelen)(3 * sizeof(uint32_t) + (namelen) + 1);
43
44 if (stack->size_allocated - stack->size_used < size) {
14
Taking true branch
27
Assuming the condition is true
28
Taking true branch
45 /* increase size of stack */
46 size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE)(((stack->size_used + size) / (4096) + 1) * (4096));
47 stack->head = realloc(stack->head, size_needed);
15
Memory is allocated
29
Attempt to reallocate memory
48 if (!stack->head) {
16
Assuming field 'head' is non-null
17
Taking false branch
30
Assuming field 'head' is null
31
Reallocation failed
32
Taking true branch
49 return -1;
50 }
51 stack->size_allocated = size_needed;
52 }
53
54 data = (((char*)stack->head) + stack->size_used);
55
56 n_offset = htonl(stack->last_offset)__bswap_32 (stack->last_offset);
57 n_length = htonl(length)__bswap_32 (length);
58 n_flags = htonl(flags)__bswap_32 (flags);
59
60 memcpy(data, &n_offset, sizeof(n_offset));
61 data += sizeof(n_offset);
62
63 memcpy(data, &n_length, sizeof(n_length));
64 data += sizeof(n_length);
65
66 memcpy(data, &n_flags, sizeof(n_flags));
67 data += sizeof(n_flags);
68
69 memcpy(data, name, namelen + 1);
70
71 stack->size_used += size;
72 stack->last_offset += length;
73 return 0;
74}
75
76static int mar_concat_file(FILE* fp, const char* path) {
77 FILE* in;
78 char buf[BLOCKSIZE4096];
79 size_t len;
80 int rv = 0;
81
82 in = fopen(path, "rb");
83 if (!in) {
84 fprintf(stderrstderr, "ERROR: could not open file in mar_concat_file()\n");
85 perror(path);
86 return -1;
87 }
88
89 while ((len = fread(buf, 1, BLOCKSIZE4096, in)) > 0) {
90 if (fwrite(buf, len, 1, fp) != 1) {
91 rv = -1;
92 break;
93 }
94 }
95
96 fclose(in);
97 return rv;
98}
99
100/**
101 * Writes out the product information block to the specified file.
102 *
103 * @param fp The opened MAR file being created.
104 * @param stack A pointer to the MAR item stack being used to create
105 * the MAR
106 * @param infoBlock The product info block to store in the file.
107 * @return 0 on success.
108 */
109static int mar_concat_product_info_block(
110 FILE* fp, struct MarItemStack* stack,
111 struct ProductInformationBlock* infoBlock) {
112 char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE63 + PIB_MAX_PRODUCT_VERSION_SIZE31];
113 uint32_t additionalBlockID = 1, infoBlockSize, unused;
114 if (!fp || !infoBlock || !infoBlock->MARChannelID ||
115 !infoBlock->productVersion) {
116 return -1;
117 }
118
119 /* The MAR channel name must be < 64 bytes per the spec */
120 if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE63) {
121 return -1;
122 }
123
124 /* The product version must be < 32 bytes per the spec */
125 if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE31) {
126 return -1;
127 }
128
129 /* Although we don't need the product information block size to include the
130 maximum MAR channel name and product version, we allocate the maximum
131 amount to make it easier to modify the MAR file for repurposing MAR files
132 to different MAR channels. + 2 is for the NULL terminators. */
133 infoBlockSize = sizeof(infoBlockSize) + sizeof(additionalBlockID) +
134 PIB_MAX_MAR_CHANNEL_ID_SIZE63 + PIB_MAX_PRODUCT_VERSION_SIZE31 +
135 2;
136 if (stack) {
137 stack->last_offset += infoBlockSize;
138 }
139
140 /* Write out the product info block size */
141 infoBlockSize = htonl(infoBlockSize)__bswap_32 (infoBlockSize);
142 if (fwrite(&infoBlockSize, sizeof(infoBlockSize), 1, fp) != 1) {
143 return -1;
144 }
145 infoBlockSize = ntohl(infoBlockSize)__bswap_32 (infoBlockSize);
146
147 /* Write out the product info block ID */
148 additionalBlockID = htonl(additionalBlockID)__bswap_32 (additionalBlockID);
149 if (fwrite(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) {
150 return -1;
151 }
152 additionalBlockID = ntohl(additionalBlockID)__bswap_32 (additionalBlockID);
153
154 /* Write out the channel name and NULL terminator */
155 if (fwrite(infoBlock->MARChannelID, strlen(infoBlock->MARChannelID) + 1, 1,
156 fp) != 1) {
157 return -1;
158 }
159
160 /* Write out the product version string and NULL terminator */
161 if (fwrite(infoBlock->productVersion, strlen(infoBlock->productVersion) + 1,
162 1, fp) != 1) {
163 return -1;
164 }
165
166 /* Write out the rest of the block that is unused */
167 unused = infoBlockSize - (sizeof(infoBlockSize) + sizeof(additionalBlockID) +
168 strlen(infoBlock->MARChannelID) +
169 strlen(infoBlock->productVersion) + 2);
170 memset(buf, 0, sizeof(buf));
171 if (fwrite(buf, unused, 1, fp) != 1) {
172 return -1;
173 }
174 return 0;
175}
176
177/**
178 * Refreshes the product information block with the new information.
179 * The input MAR must not be signed or the function call will fail.
180 *
181 * @param path The path to the MAR file whose product info block
182 * should be refreshed.
183 * @param infoBlock Out parameter for where to store the result to
184 * @return 0 on success, -1 on failure
185 */
186int refresh_product_info_block(const char* path,
187 struct ProductInformationBlock* infoBlock) {
188 FILE* fp;
189 int rv;
190 uint32_t numSignatures, additionalBlockSize, additionalBlockID,
191 offsetAdditionalBlocks, numAdditionalBlocks, i;
192 int additionalBlocks, hasSignatureBlock;
193 int64_t oldPos;
194
195 rv = get_mar_file_info(path, &hasSignatureBlock, &numSignatures,
196 &additionalBlocks, &offsetAdditionalBlocks,
197 &numAdditionalBlocks);
198 if (rv) {
199 fprintf(stderrstderr, "ERROR: Could not obtain MAR information.\n");
200 return -1;
201 }
202
203 if (hasSignatureBlock && numSignatures) {
204 fprintf(stderrstderr, "ERROR: Cannot refresh a signed MAR\n");
205 return -1;
206 }
207
208 fp = fopen(path, "r+b");
209 if (!fp) {
210 fprintf(stderrstderr, "ERROR: could not open target file: %s\n", path);
211 return -1;
212 }
213
214 if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET0)) {
215 fprintf(stderrstderr, "ERROR: could not seek to additional blocks\n");
216 fclose(fp);
217 return -1;
218 }
219
220 for (i = 0; i < numAdditionalBlocks; ++i) {
221 /* Get the position of the start of this block */
222 oldPos = ftello(fp);
223
224 /* Read the additional block size */
225 if (fread(&additionalBlockSize, sizeof(additionalBlockSize), 1, fp) != 1) {
226 fclose(fp);
227 return -1;
228 }
229 additionalBlockSize = ntohl(additionalBlockSize)__bswap_32 (additionalBlockSize);
230
231 /* Read the additional block ID */
232 if (fread(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) {
233 fclose(fp);
234 return -1;
235 }
236 additionalBlockID = ntohl(additionalBlockID)__bswap_32 (additionalBlockID);
237
238 if (PRODUCT_INFO_BLOCK_ID1 == additionalBlockID) {
239 if (fseeko(fp, oldPos, SEEK_SET0)) {
240 fprintf(stderrstderr, "Could not seek back to Product Information Block\n");
241 fclose(fp);
242 return -1;
243 }
244
245 if (mar_concat_product_info_block(fp, NULL((void*)0), infoBlock)) {
246 fprintf(stderrstderr, "Could not concat Product Information Block\n");
247 fclose(fp);
248 return -1;
249 }
250
251 fclose(fp);
252 return 0;
253 }
254
255 /* This is not the additional block you're looking for. Move along. */
256 if (fseek(fp, additionalBlockSize, SEEK_CUR1)) {
257 fprintf(stderrstderr, "ERROR: Could not seek past current block.\n");
258 fclose(fp);
259 return -1;
260 }
261 }
262
263 /* If we had a product info block we would have already returned */
264 fclose(fp);
265 fprintf(stderrstderr, "ERROR: Could not refresh because block does not exist\n");
266 return -1;
267}
268
269/**
270 * Create a MAR file from a set of files.
271 * @param dest The path to the file to create. This path must be
272 * compatible with fopen.
273 * @param numfiles The number of files to store in the archive.
274 * @param files The list of null-terminated file paths. Each file
275 * path must be compatible with fopen.
276 * @param infoBlock The information to store in the product information block.
277 * @return A non-zero value if an error occurs.
278 */
279int mar_create(const char* dest, int num_files, char** files,
280 struct ProductInformationBlock* infoBlock) {
281 struct MarItemStack stack;
282 uint32_t offset_to_index = 0, size_of_index, numSignatures,
283 numAdditionalSections;
284 uint64_t sizeOfEntireMAR = 0;
285 struct stat st;
286 FILE* fp;
287 int i, rv = -1;
288
289 memset(&stack, 0, sizeof(stack));
290
291 fp = fopen(dest, "wb");
292 if (!fp) {
1
Assuming 'fp' is non-null
2
Taking false branch
293 fprintf(stderrstderr, "ERROR: could not create target file: %s\n", dest);
294 return -1;
295 }
296
297 if (fwrite(MAR_ID"MAR1", MAR_ID_SIZE4, 1, fp) != 1) {
3
Taking false branch
298 goto failure;
299 }
300 if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) {
4
Taking false branch
301 goto failure;
302 }
303
304 stack.last_offset = MAR_ID_SIZE4 + sizeof(offset_to_index) +
305 sizeof(numSignatures) + sizeof(numAdditionalSections) +
306 sizeof(sizeOfEntireMAR);
307
308 /* We will circle back on this at the end of the MAR creation to fill it */
309 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
5
Taking false branch
310 goto failure;
311 }
312
313 /* Write out the number of signatures, for now only at most 1 is supported */
314 numSignatures = 0;
315 if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
6
Taking false branch
316 goto failure;
317 }
318
319 /* Write out the number of additional sections, for now just 1
320 for the product info block */
321 numAdditionalSections = htonl(1)__bswap_32 (1);
322 if (fwrite(&numAdditionalSections, sizeof(numAdditionalSections), 1, fp) !=
7
Taking false branch
323 1) {
324 goto failure;
325 }
326 numAdditionalSections = ntohl(numAdditionalSections)__bswap_32 (numAdditionalSections);
327
328 if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
8
Taking false branch
329 goto failure;
330 }
331
332 for (i = 0; i < num_files; ++i) {
9
Assuming 'i' is < 'num_files'
10
Loop condition is true. Entering loop body
22
Assuming 'i' is < 'num_files'
23
Loop condition is true. Entering loop body
333 if (stat(files[i], &st)) {
11
Assuming the condition is false
12
Taking false branch
24
Assuming the condition is false
25
Taking false branch
334 fprintf(stderrstderr, "ERROR: file not found: %s\n", files[i]);
335 goto failure;
336 }
337
338 if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) {
13
Calling 'mar_push'
18
Returned allocated memory
19
Taking false branch
26
Calling 'mar_push'
33
Reallocation failed
34
Taking true branch
339 goto failure;
35
Control jumps to line 383
340 }
341
342 /* concatenate input file to archive */
343 if (mar_concat_file(fp, files[i])) {
20
Assuming the condition is false
21
Taking false branch
344 goto failure;
345 }
346 }
347
348 /* write out the index (prefixed with length of index) */
349 size_of_index = htonl(stack.size_used)__bswap_32 (stack.size_used);
350 if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) {
351 goto failure;
352 }
353 if (fwrite(stack.head, stack.size_used, 1, fp) != 1) {
354 goto failure;
355 }
356
357 /* To protect against invalid MAR files, we assumes that the MAR file
358 size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
359 if (ftell(fp) > MAX_SIZE_OF_MAR_FILE((int64_t)524288000)) {
360 goto failure;
361 }
362
363 /* write out offset to index file in network byte order */
364 offset_to_index = htonl(stack.last_offset)__bswap_32 (stack.last_offset);
365 if (fseek(fp, MAR_ID_SIZE4, SEEK_SET0)) {
366 goto failure;
367 }
368 if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) {
369 goto failure;
370 }
371 offset_to_index = ntohl(stack.last_offset)__bswap_32 (stack.last_offset);
372
373 sizeOfEntireMAR =
374 ((uint64_t)stack.last_offset) + stack.size_used + sizeof(size_of_index);
375 sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR)(((((uint64_t)sizeOfEntireMAR) & 0xFF) << 56) | (((
(uint64_t)sizeOfEntireMAR) >> 8) & 0xFF) << 48
) | (((((uint64_t)sizeOfEntireMAR) >> 16) & 0xFF) <<
40) | (((((uint64_t)sizeOfEntireMAR) >> 24) & 0xFF
) << 32) | (((((uint64_t)sizeOfEntireMAR) >> 32) &
0xFF) << 24) | (((((uint64_t)sizeOfEntireMAR) >>
40) & 0xFF) << 16) | (((((uint64_t)sizeOfEntireMAR
) >> 48) & 0xFF) << 8) | (((uint64_t)sizeOfEntireMAR
) >> 56)
;
376 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
377 goto failure;
378 }
379 sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR)(((((uint64_t)sizeOfEntireMAR) & 0xFF) << 56) | (((
(uint64_t)sizeOfEntireMAR) >> 8) & 0xFF) << 48
) | (((((uint64_t)sizeOfEntireMAR) >> 16) & 0xFF) <<
40) | (((((uint64_t)sizeOfEntireMAR) >> 24) & 0xFF
) << 32) | (((((uint64_t)sizeOfEntireMAR) >> 32) &
0xFF) << 24) | (((((uint64_t)sizeOfEntireMAR) >>
40) & 0xFF) << 16) | (((((uint64_t)sizeOfEntireMAR
) >> 48) & 0xFF) << 8) | (((uint64_t)sizeOfEntireMAR
) >> 56)
;
380
381 rv = 0;
382failure:
383 if (stack.head
35.1
Field 'head' is null
) {
36
Taking false branch
384 free(stack.head);
385 }
386 fclose(fp);
37
Potential leak of memory pointed to by 'stack.head'
387 if (rv) {
388 remove(dest);
389 }
390 return rv;
391}