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 <fcntl.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include "city.h"
12 #include "mar_private.h"
13 #include "mar.h"
14 #ifdef XP_WIN
15 # define strdup _strdup
16 #endif
17
18 /* This block must be at most 104 bytes.
19 MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL
20 terminator bytes. We only check for 96 though because we remove 8
21 bytes above from the additionalBlockSize: We subtract
22 sizeof(additionalBlockSize) and sizeof(additionalBlockID) */
23 #define MAXADDITIONALBLOCKSIZE 96
24
mar_hash_name(const char * name)25 static uint32_t mar_hash_name(const char* name) {
26 return CityHash64(name, strlen(name)) % TABLESIZE;
27 }
28
mar_insert_item(MarFile * mar,const char * name,uint32_t namelen,uint32_t offset,uint32_t length,uint32_t flags)29 static int mar_insert_item(MarFile* mar, const char* name, uint32_t namelen,
30 uint32_t offset, uint32_t length, uint32_t flags) {
31 MarItem *item, *root;
32 uint32_t hash;
33
34 item = (MarItem*)malloc(sizeof(MarItem) + namelen);
35 if (!item) {
36 return -1;
37 }
38 item->next = NULL;
39 item->offset = offset;
40 item->length = length;
41 item->flags = flags;
42 memcpy(item->name, name, namelen + 1);
43
44 hash = mar_hash_name(name);
45
46 root = mar->item_table[hash];
47 if (!root) {
48 mar->item_table[hash] = item;
49 } else {
50 /* append item */
51 while (root->next) root = root->next;
52 root->next = item;
53 }
54 return 0;
55 }
56
mar_consume_index(MarFile * mar,char ** buf,const char * buf_end)57 static int mar_consume_index(MarFile* mar, char** buf, const char* buf_end) {
58 /*
59 * Each item has the following structure:
60 * uint32_t offset (network byte order)
61 * uint32_t length (network byte order)
62 * uint32_t flags (network byte order)
63 * char name[N] (where N >= 1)
64 * char null_byte;
65 */
66 uint32_t offset;
67 uint32_t length;
68 uint32_t flags;
69 const char* name;
70 int namelen;
71
72 if ((buf_end - *buf) < (int)(3 * sizeof(uint32_t) + 2)) {
73 return -1;
74 }
75
76 memcpy(&offset, *buf, sizeof(offset));
77 *buf += sizeof(offset);
78
79 memcpy(&length, *buf, sizeof(length));
80 *buf += sizeof(length);
81
82 memcpy(&flags, *buf, sizeof(flags));
83 *buf += sizeof(flags);
84
85 offset = ntohl(offset);
86 length = ntohl(length);
87 flags = ntohl(flags);
88
89 name = *buf;
90 /* find namelen; must take care not to read beyond buf_end */
91 while (**buf) {
92 /* buf_end points one byte past the end of buf's allocation */
93 if (*buf == (buf_end - 1)) {
94 return -1;
95 }
96 ++(*buf);
97 }
98 namelen = (*buf - name);
99 /* must ensure that namelen is valid */
100 if (namelen < 0) {
101 return -1;
102 }
103 /* consume null byte */
104 if (*buf == buf_end) {
105 return -1;
106 }
107 ++(*buf);
108
109 return mar_insert_item(mar, name, namelen, offset, length, flags);
110 }
111
mar_read_index(MarFile * mar)112 static int mar_read_index(MarFile* mar) {
113 char id[MAR_ID_SIZE], *buf, *bufptr, *bufend;
114 uint32_t offset_to_index, size_of_index;
115
116 /* verify MAR ID */
117 fseek(mar->fp, 0, SEEK_SET);
118 if (fread(id, MAR_ID_SIZE, 1, mar->fp) != 1) {
119 return -1;
120 }
121 if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) {
122 return -1;
123 }
124
125 if (fread(&offset_to_index, sizeof(uint32_t), 1, mar->fp) != 1) {
126 return -1;
127 }
128 offset_to_index = ntohl(offset_to_index);
129
130 if (fseek(mar->fp, offset_to_index, SEEK_SET)) {
131 return -1;
132 }
133 if (fread(&size_of_index, sizeof(uint32_t), 1, mar->fp) != 1) {
134 return -1;
135 }
136 size_of_index = ntohl(size_of_index);
137
138 buf = (char*)malloc(size_of_index);
139 if (!buf) {
140 return -1;
141 }
142 if (fread(buf, size_of_index, 1, mar->fp) != 1) {
143 free(buf);
144 return -1;
145 }
146
147 bufptr = buf;
148 bufend = buf + size_of_index;
149 while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0)
150 ;
151
152 free(buf);
153 return (bufptr == bufend) ? 0 : -1;
154 }
155
156 /**
157 * Adds an offset and length to the MarFile's index_list
158 * @param mar The MarFile that owns this offset length pair
159 * @param offset The byte offset in the archive to be marked as processed
160 * @param length The length corresponding to this byte offset
161 * @return int 1 on success, 0 if offset has been previously processed
162 * -1 if unable to allocate space for the SeenIndexes
163 */
mar_insert_offset(MarFile * mar,uint32_t offset,uint32_t length)164 static int mar_insert_offset(MarFile* mar, uint32_t offset, uint32_t length) {
165 /* Ignore files with no length */
166 if (length == 0) {
167 return 1;
168 }
169
170 SeenIndex* index = (SeenIndex*)malloc(sizeof(SeenIndex));
171 if (!index) {
172 return -1;
173 }
174 index->next = NULL;
175 index->offset = offset;
176 index->length = length;
177 uint32_t index_end = index->offset + index->length - 1;
178
179 /* If this is our first index store it at the front */
180 if (mar->index_list == NULL) {
181 mar->index_list = index;
182 return 1;
183 }
184
185 /* Search for matching indexes in the list of those previously visited */
186 SeenIndex* previous;
187 SeenIndex* current = mar->index_list;
188 while (current != NULL) {
189 uint32_t current_end = current->offset + current->length - 1;
190
191 /* If index has collided with the front or end of current or if current has
192 collided with the front or end of index return false */
193 if ((index->offset >= current->offset && index->offset <= current_end) ||
194 (index_end >= current->offset && index_end <= current_end) ||
195 (current->offset >= index->offset && current->offset <= index_end) ||
196 (current_end >= index->offset && current_end <= index_end)) {
197 free(index);
198 return 0;
199 }
200
201 /* else move to the next in the list */
202 previous = current;
203 current = current->next;
204 }
205
206 /* These indexes are valid, track them */
207 previous->next = index;
208 return 1;
209 }
210
211 /**
212 * Internal shared code for mar_open and mar_wopen.
213 * On failure, will fclose(fp).
214 */
mar_fpopen(FILE * fp)215 static MarFile* mar_fpopen(FILE* fp) {
216 MarFile* mar;
217
218 mar = (MarFile*)malloc(sizeof(*mar));
219 if (!mar) {
220 fclose(fp);
221 return NULL;
222 }
223
224 mar->fp = fp;
225 mar->item_table_is_valid = 0;
226 memset(mar->item_table, 0, sizeof(mar->item_table));
227 mar->index_list = NULL;
228
229 return mar;
230 }
231
mar_open(const char * path)232 MarFile* mar_open(const char* path) {
233 FILE* fp;
234
235 fp = fopen(path, "rb");
236 if (!fp) {
237 fprintf(stderr, "ERROR: could not open file in mar_open()\n");
238 perror(path);
239 return NULL;
240 }
241
242 return mar_fpopen(fp);
243 }
244
245 #ifdef XP_WIN
mar_wopen(const wchar_t * path)246 MarFile* mar_wopen(const wchar_t* path) {
247 FILE* fp;
248
249 _wfopen_s(&fp, path, L"rb");
250 if (!fp) {
251 fprintf(stderr, "ERROR: could not open file in mar_wopen()\n");
252 _wperror(path);
253 return NULL;
254 }
255
256 return mar_fpopen(fp);
257 }
258 #endif
259
mar_close(MarFile * mar)260 void mar_close(MarFile* mar) {
261 MarItem* item;
262 SeenIndex* index;
263 int i;
264
265 fclose(mar->fp);
266
267 for (i = 0; i < TABLESIZE; ++i) {
268 item = mar->item_table[i];
269 while (item) {
270 MarItem* temp = item;
271 item = item->next;
272 free(temp);
273 }
274 }
275
276 while (mar->index_list != NULL) {
277 index = mar->index_list;
278 mar->index_list = index->next;
279 free(index);
280 }
281
282 free(mar);
283 }
284
285 /**
286 * Determines the MAR file information.
287 *
288 * @param fp An opened MAR file in read mode.
289 * @param hasSignatureBlock Optional out parameter specifying if the MAR
290 * file has a signature block or not.
291 * @param numSignatures Optional out parameter for storing the number
292 * of signatures in the MAR file.
293 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
294 * file has additional blocks or not.
295 * @param offsetAdditionalBlocks Optional out parameter for the offset to the
296 * first additional block. Value is only valid if
297 * hasAdditionalBlocks is not equal to 0.
298 * @param numAdditionalBlocks Optional out parameter for the number of
299 * additional blocks. Value is only valid if
300 * hasAdditionalBlocks is not equal to 0.
301 * @return 0 on success and non-zero on failure.
302 */
get_mar_file_info_fp(FILE * fp,int * hasSignatureBlock,uint32_t * numSignatures,int * hasAdditionalBlocks,uint32_t * offsetAdditionalBlocks,uint32_t * numAdditionalBlocks)303 int get_mar_file_info_fp(FILE* fp, int* hasSignatureBlock,
304 uint32_t* numSignatures, int* hasAdditionalBlocks,
305 uint32_t* offsetAdditionalBlocks,
306 uint32_t* numAdditionalBlocks) {
307 uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i;
308
309 /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */
310 if (!hasSignatureBlock && !hasAdditionalBlocks) {
311 return -1;
312 }
313
314 /* Skip to the start of the offset index */
315 if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) {
316 return -1;
317 }
318
319 /* Read the offset to the index. */
320 if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) {
321 return -1;
322 }
323 offsetToIndex = ntohl(offsetToIndex);
324
325 if (numSignatures) {
326 /* Skip past the MAR file size field */
327 if (fseek(fp, sizeof(uint64_t), SEEK_CUR)) {
328 return -1;
329 }
330
331 /* Read the number of signatures field */
332 if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) {
333 return -1;
334 }
335 *numSignatures = ntohl(*numSignatures);
336 }
337
338 /* Skip to the first index entry past the index size field
339 We do it in 2 calls because offsetToIndex + sizeof(uint32_t)
340 could oerflow in theory. */
341 if (fseek(fp, offsetToIndex, SEEK_SET)) {
342 return -1;
343 }
344
345 if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) {
346 return -1;
347 }
348
349 /* Read the first offset to content field. */
350 if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) {
351 return -1;
352 }
353 offsetToContent = ntohl(offsetToContent);
354
355 /* Check if we have a new or old MAR file */
356 if (hasSignatureBlock) {
357 if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) {
358 *hasSignatureBlock = 0;
359 } else {
360 *hasSignatureBlock = 1;
361 }
362 }
363
364 /* If the caller doesn't care about the product info block
365 value, then just return */
366 if (!hasAdditionalBlocks) {
367 return 0;
368 }
369
370 /* Skip to the start of the signature block */
371 if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) {
372 return -1;
373 }
374
375 /* Get the number of signatures */
376 if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) {
377 return -1;
378 }
379 signatureCount = ntohl(signatureCount);
380
381 /* Check that we have less than the max amount of signatures so we don't
382 waste too much of either updater's or signmar's time. */
383 if (signatureCount > MAX_SIGNATURES) {
384 return -1;
385 }
386
387 /* Skip past the whole signature block */
388 for (i = 0; i < signatureCount; i++) {
389 /* Skip past the signature algorithm ID */
390 if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) {
391 return -1;
392 }
393
394 /* Read the signature length and skip past the signature */
395 if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) {
396 return -1;
397 }
398 signatureLen = ntohl(signatureLen);
399 if (fseek(fp, signatureLen, SEEK_CUR)) {
400 return -1;
401 }
402 }
403
404 if ((int64_t)ftell(fp) == (int64_t)offsetToContent) {
405 *hasAdditionalBlocks = 0;
406 } else {
407 if (numAdditionalBlocks) {
408 /* We have an additional block, so read in the number of additional blocks
409 and set the offset. */
410 *hasAdditionalBlocks = 1;
411 if (fread(numAdditionalBlocks, sizeof(uint32_t), 1, fp) != 1) {
412 return -1;
413 }
414 *numAdditionalBlocks = ntohl(*numAdditionalBlocks);
415 if (offsetAdditionalBlocks) {
416 *offsetAdditionalBlocks = ftell(fp);
417 }
418 } else if (offsetAdditionalBlocks) {
419 /* numAdditionalBlocks is not specified but offsetAdditionalBlocks
420 is, so fill it! */
421 *offsetAdditionalBlocks = ftell(fp) + sizeof(uint32_t);
422 }
423 }
424
425 return 0;
426 }
427
428 /**
429 * Reads the product info block from the MAR file's additional block section.
430 * The caller is responsible for freeing the fields in infoBlock
431 * if the return is successful.
432 *
433 * @param infoBlock Out parameter for where to store the result to
434 * @return 0 on success, -1 on failure
435 */
read_product_info_block(char * path,struct ProductInformationBlock * infoBlock)436 int read_product_info_block(char* path,
437 struct ProductInformationBlock* infoBlock) {
438 int rv;
439 MarFile mar;
440 mar.fp = fopen(path, "rb");
441 if (!mar.fp) {
442 fprintf(stderr,
443 "ERROR: could not open file in read_product_info_block()\n");
444 perror(path);
445 return -1;
446 }
447 rv = mar_read_product_info_block(&mar, infoBlock);
448 fclose(mar.fp);
449 return rv;
450 }
451
452 /**
453 * Reads the product info block from the MAR file's additional block section.
454 * The caller is responsible for freeing the fields in infoBlock
455 * if the return is successful.
456 *
457 * @param infoBlock Out parameter for where to store the result to
458 * @return 0 on success, -1 on failure
459 */
mar_read_product_info_block(MarFile * mar,struct ProductInformationBlock * infoBlock)460 int mar_read_product_info_block(MarFile* mar,
461 struct ProductInformationBlock* infoBlock) {
462 uint32_t offsetAdditionalBlocks, numAdditionalBlocks, additionalBlockSize,
463 additionalBlockID;
464 int hasAdditionalBlocks;
465
466 /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and
467 product version < 32 bytes + 3 NULL terminator bytes. */
468 char buf[MAXADDITIONALBLOCKSIZE + 1] = {'\0'};
469 if (get_mar_file_info_fp(mar->fp, NULL, NULL, &hasAdditionalBlocks,
470 &offsetAdditionalBlocks,
471 &numAdditionalBlocks) != 0) {
472 return -1;
473 }
474
475 /* We only have the one additional block type and only one is expected to be
476 in a MAR file so check if any exist and process the first found */
477 if (numAdditionalBlocks > 0) {
478 /* Read the additional block size */
479 if (fread(&additionalBlockSize, sizeof(additionalBlockSize), 1, mar->fp) !=
480 1) {
481 return -1;
482 }
483 additionalBlockSize = ntohl(additionalBlockSize) -
484 sizeof(additionalBlockSize) -
485 sizeof(additionalBlockID);
486
487 /* Additional Block sizes should only be 96 bytes long */
488 if (additionalBlockSize > MAXADDITIONALBLOCKSIZE) {
489 return -1;
490 }
491
492 /* Read the additional block ID */
493 if (fread(&additionalBlockID, sizeof(additionalBlockID), 1, mar->fp) != 1) {
494 return -1;
495 }
496 additionalBlockID = ntohl(additionalBlockID);
497
498 if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
499 const char* location;
500 int len;
501
502 if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) {
503 return -1;
504 }
505
506 /* Extract the MAR channel name from the buffer. For now we
507 point to the stack allocated buffer but we strdup this
508 if we are within bounds of each field's max length. */
509 location = buf;
510 len = strlen(location);
511 infoBlock->MARChannelID = location;
512 location += len + 1;
513 if (len >= 64) {
514 infoBlock->MARChannelID = NULL;
515 return -1;
516 }
517
518 /* Extract the version from the buffer */
519 len = strlen(location);
520 infoBlock->productVersion = location;
521 if (len >= 32) {
522 infoBlock->MARChannelID = NULL;
523 infoBlock->productVersion = NULL;
524 return -1;
525 }
526 infoBlock->MARChannelID = strdup(infoBlock->MARChannelID);
527 infoBlock->productVersion = strdup(infoBlock->productVersion);
528 return 0;
529 } else {
530 /* This is not the additional block you're looking for. Move along. */
531 if (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) {
532 return -1;
533 }
534 }
535 }
536
537 /* If we had a product info block we would have already returned */
538 return -1;
539 }
540
mar_find_item(MarFile * mar,const char * name)541 const MarItem* mar_find_item(MarFile* mar, const char* name) {
542 uint32_t hash;
543 const MarItem* item;
544
545 if (!mar->item_table_is_valid) {
546 if (mar_read_index(mar)) {
547 return NULL;
548 } else {
549 mar->item_table_is_valid = 1;
550 }
551 }
552
553 hash = mar_hash_name(name);
554
555 item = mar->item_table[hash];
556 while (item && strcmp(item->name, name) != 0) {
557 item = item->next;
558 }
559
560 /* If this is the first time seeing this item's indexes, return it */
561 if (mar_insert_offset(mar, item->offset, item->length) == 1) {
562 return item;
563 } else {
564 fprintf(stderr, "ERROR: file content collision in mar_find_item()\n");
565 return NULL;
566 }
567 }
568
mar_enum_items(MarFile * mar,MarItemCallback callback,void * closure)569 int mar_enum_items(MarFile* mar, MarItemCallback callback, void* closure) {
570 MarItem* item;
571 int i, rv;
572
573 if (!mar->item_table_is_valid) {
574 if (mar_read_index(mar)) {
575 return -1;
576 } else {
577 mar->item_table_is_valid = 1;
578 }
579 }
580
581 for (i = 0; i < TABLESIZE; ++i) {
582 item = mar->item_table[i];
583 while (item) {
584 /* if this is the first time seeing this item's indexes, process it */
585 if (mar_insert_offset(mar, item->offset, item->length) == 1) {
586 rv = callback(mar, item, closure);
587 if (rv) {
588 return rv;
589 }
590 } else {
591 fprintf(stderr, "ERROR: file content collision in mar_enum_items()\n");
592 return 1;
593 }
594 item = item->next;
595 }
596 }
597
598 return 0;
599 }
600
mar_read(MarFile * mar,const MarItem * item,int offset,uint8_t * buf,int bufsize)601 int mar_read(MarFile* mar, const MarItem* item, int offset, uint8_t* buf,
602 int bufsize) {
603 int nr;
604
605 if (offset == (int)item->length) {
606 return 0;
607 }
608 if (offset > (int)item->length) {
609 return -1;
610 }
611
612 nr = item->length - offset;
613 if (nr > bufsize) {
614 nr = bufsize;
615 }
616
617 if (fseek(mar->fp, item->offset + offset, SEEK_SET)) {
618 return -1;
619 }
620
621 return fread(buf, 1, nr, mar->fp);
622 }
623
624 /**
625 * Determines the MAR file information.
626 *
627 * @param path The path of the MAR file to check.
628 * @param hasSignatureBlock Optional out parameter specifying if the MAR
629 * file has a signature block or not.
630 * @param numSignatures Optional out parameter for storing the number
631 * of signatures in the MAR file.
632 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
633 * file has additional blocks or not.
634 * @param offsetAdditionalBlocks Optional out parameter for the offset to the
635 * first additional block. Value is only valid if
636 * hasAdditionalBlocks is not equal to 0.
637 * @param numAdditionalBlocks Optional out parameter for the number of
638 * additional blocks. Value is only valid if
639 * has_additional_blocks is not equal to 0.
640 * @return 0 on success and non-zero on failure.
641 */
get_mar_file_info(const char * path,int * hasSignatureBlock,uint32_t * numSignatures,int * hasAdditionalBlocks,uint32_t * offsetAdditionalBlocks,uint32_t * numAdditionalBlocks)642 int get_mar_file_info(const char* path, int* hasSignatureBlock,
643 uint32_t* numSignatures, int* hasAdditionalBlocks,
644 uint32_t* offsetAdditionalBlocks,
645 uint32_t* numAdditionalBlocks) {
646 int rv;
647 FILE* fp = fopen(path, "rb");
648 if (!fp) {
649 fprintf(stderr, "ERROR: could not open file in get_mar_file_info()\n");
650 perror(path);
651 return -1;
652 }
653
654 rv = get_mar_file_info_fp(fp, hasSignatureBlock, numSignatures,
655 hasAdditionalBlocks, offsetAdditionalBlocks,
656 numAdditionalBlocks);
657
658 fclose(fp);
659 return rv;
660 }
661