Bug Summary

File:root/firefox-clang/modules/libmar/src/mar_create.c
Warning:line 87, column 17
File position of the stream might be 'indeterminate' after a failed operation. Can cause undefined behavior

Annotated Source Code

Press '?' to see keyboard shortcuts

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