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/. */
12 #include "mar_private.h"
15 # define strdup _strdup
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
25 static uint32_t mar_hash_name(const char* name
) {
26 return CityHash64(name
, strlen(name
)) % TABLESIZE
;
29 static int mar_insert_item(MarFile
* mar
, const char* name
, uint32_t namelen
,
30 uint32_t offset
, uint32_t length
, uint32_t flags
) {
34 item
= (MarItem
*)malloc(sizeof(MarItem
) + namelen
);
39 item
->offset
= offset
;
40 item
->length
= length
;
42 memcpy(item
->name
, name
, namelen
+ 1);
44 hash
= mar_hash_name(name
);
46 root
= mar
->item_table
[hash
];
48 mar
->item_table
[hash
] = item
;
51 while (root
->next
) root
= root
->next
;
57 static int mar_consume_index(MarFile
* mar
, char** buf
, const char* buf_end
) {
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)
72 if ((buf_end
- *buf
) < (int)(3 * sizeof(uint32_t) + 2)) {
76 memcpy(&offset
, *buf
, sizeof(offset
));
77 *buf
+= sizeof(offset
);
79 memcpy(&length
, *buf
, sizeof(length
));
80 *buf
+= sizeof(length
);
82 memcpy(&flags
, *buf
, sizeof(flags
));
83 *buf
+= sizeof(flags
);
85 offset
= ntohl(offset
);
86 length
= ntohl(length
);
90 /* find namelen; must take care not to read beyond buf_end */
92 /* buf_end points one byte past the end of buf's allocation */
93 if (*buf
== (buf_end
- 1)) {
98 namelen
= (*buf
- name
);
99 /* must ensure that namelen is valid */
103 /* consume null byte */
104 if (*buf
== buf_end
) {
109 return mar_insert_item(mar
, name
, namelen
, offset
, length
, flags
);
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 size_t mar_position
= 0;
118 if (mar_read_buffer(mar
, id
, &mar_position
, MAR_ID_SIZE
) != 0) {
121 if (memcmp(id
, MAR_ID
, MAR_ID_SIZE
) != 0) {
125 if (mar_read_buffer(mar
, &offset_to_index
, &mar_position
, sizeof(uint32_t)) !=
129 offset_to_index
= ntohl(offset_to_index
);
132 if (mar_buffer_seek(mar
, &mar_position
, offset_to_index
) != 0) {
135 if (mar_read_buffer(mar
, &size_of_index
, &mar_position
, sizeof(uint32_t)) !=
139 size_of_index
= ntohl(size_of_index
);
141 buf
= (char*)malloc(size_of_index
);
145 if (mar_read_buffer(mar
, buf
, &mar_position
, size_of_index
) != 0) {
151 bufend
= buf
+ size_of_index
;
152 while (bufptr
< bufend
&& mar_consume_index(mar
, &bufptr
, bufend
) == 0)
156 return (bufptr
== bufend
) ? 0 : -1;
160 * Adds an offset and length to the MarFile's index_list
161 * @param mar The MarFile that owns this offset length pair
162 * @param offset The byte offset in the archive to be marked as processed
163 * @param length The length corresponding to this byte offset
164 * @return int 1 on success, 0 if offset has been previously processed
165 * -1 if unable to allocate space for the SeenIndexes
167 static int mar_insert_offset(MarFile
* mar
, uint32_t offset
, uint32_t length
) {
168 /* Ignore files with no length */
173 SeenIndex
* index
= (SeenIndex
*)malloc(sizeof(SeenIndex
));
178 index
->offset
= offset
;
179 index
->length
= length
;
180 uint32_t index_end
= index
->offset
+ index
->length
- 1;
182 /* If this is our first index store it at the front */
183 if (mar
->index_list
== NULL
) {
184 mar
->index_list
= index
;
188 /* Search for matching indexes in the list of those previously visited */
190 SeenIndex
* current
= mar
->index_list
;
191 while (current
!= NULL
) {
192 uint32_t current_end
= current
->offset
+ current
->length
- 1;
194 /* If index has collided with the front or end of current or if current has
195 collided with the front or end of index return false */
196 if ((index
->offset
>= current
->offset
&& index
->offset
<= current_end
) ||
197 (index_end
>= current
->offset
&& index_end
<= current_end
) ||
198 (current
->offset
>= index
->offset
&& current
->offset
<= index_end
) ||
199 (current_end
>= index
->offset
&& current_end
<= index_end
)) {
204 /* else move to the next in the list */
206 current
= current
->next
;
209 /* These indexes are valid, track them */
210 previous
->next
= index
;
215 * Internal shared code for mar_open and mar_wopen.
216 * Reads the entire MAR into memory. Fails if it is bigger than
217 * MAX_SIZE_OF_MAR_FILE bytes.
219 static MarReadResult
mar_fpopen(FILE* fp
, MarFile
** out_mar
) {
223 mar
= (MarFile
*)malloc(sizeof(*mar
));
225 return MAR_MEM_ERROR
;
228 off_t buffer_size
= -1;
229 if (fseeko(fp
, 0, SEEK_END
) == 0) {
230 buffer_size
= ftello(fp
);
233 if (buffer_size
< 0) {
234 fprintf(stderr
, "Warning: MAR size could not be determined\n");
235 buffer_size
= MAX_SIZE_OF_MAR_FILE
;
237 if (buffer_size
> MAX_SIZE_OF_MAR_FILE
) {
238 fprintf(stderr
, "ERROR: MAR exceeds maximum size (%lli)\n",
239 (long long int)buffer_size
);
241 return MAR_FILE_TOO_BIG_ERROR
;
244 mar
->buffer
= malloc(buffer_size
);
246 fprintf(stderr
, "ERROR: MAR buffer could not be allocated\n");
248 return MAR_MEM_ERROR
;
250 mar
->data_len
= fread(mar
->buffer
, 1, buffer_size
, fp
);
251 if (fgetc(fp
) != EOF
) {
252 fprintf(stderr
, "ERROR: File is larger than buffer (%lli)\n",
253 (long long int)buffer_size
);
259 fprintf(stderr
, "ERROR: Failed to read MAR\n");
265 mar
->item_table_is_valid
= 0;
266 memset(mar
->item_table
, 0, sizeof(mar
->item_table
));
267 mar
->index_list
= NULL
;
270 return MAR_READ_SUCCESS
;
273 MarReadResult
mar_open(const char* path
, MarFile
** out_mar
) {
278 fp
= fopen(path
, "rb");
280 fprintf(stderr
, "ERROR: could not open file in mar_open()\n");
285 MarReadResult result
= mar_fpopen(fp
, out_mar
);
291 MarReadResult
mar_wopen(const wchar_t* path
, MarFile
** out_mar
) {
296 _wfopen_s(&fp
, path
, L
"rb");
298 fprintf(stderr
, "ERROR: could not open file in mar_wopen()\n");
303 MarReadResult result
= mar_fpopen(fp
, out_mar
);
309 void mar_close(MarFile
* mar
) {
316 for (i
= 0; i
< TABLESIZE
; ++i
) {
317 item
= mar
->item_table
[i
];
319 MarItem
* temp
= item
;
325 while (mar
->index_list
!= NULL
) {
326 index
= mar
->index_list
;
327 mar
->index_list
= index
->next
;
334 int mar_read_buffer(MarFile
* mar
, void* dest
, size_t* position
, size_t size
) {
335 // size may be provided by the MAR, which we may not have finished validating
336 // the signature on yet. Make sure not to trust it in a way that could
337 // cause an overflow.
338 if (size
> mar
->data_len
) {
341 if (*position
> mar
->data_len
- size
) {
344 memcpy(dest
, mar
->buffer
+ *position
, size
);
349 int mar_read_buffer_max(MarFile
* mar
, void* dest
, size_t* position
,
351 // size may be provided by the MAR, which we may not have finished validating
352 // the signature on yet. Make sure not to trust it in a way that could
353 // cause an overflow.
354 if (mar
->data_len
<= *position
) {
357 size_t read_count
= mar
->data_len
- *position
;
358 if (read_count
> size
) {
361 memcpy(dest
, mar
->buffer
+ *position
, read_count
);
362 *position
+= read_count
;
366 int mar_buffer_seek(MarFile
* mar
, size_t* position
, size_t distance
) {
367 // distance may be provided by the MAR, which we may not have finished
368 // validating the signature on yet. Make sure not to trust it in a way that
369 // could cause an overflow.
370 if (distance
> mar
->data_len
) {
373 if (*position
> mar
->data_len
- distance
) {
376 *position
+= distance
;
381 * Determines the MAR file information.
383 * @param mar An open MAR file.
384 * @param mar_position The current position in the MAR.
385 * Its value will be updated to the current
386 * position in the MAR after the function exits.
387 * Since its initial value will never actually be
388 * used, this is effectively an outparam.
389 * @param hasSignatureBlock Optional out parameter specifying if the MAR
390 * file has a signature block or not.
391 * @param numSignatures Optional out parameter for storing the number
392 * of signatures in the MAR file.
393 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
394 * file has additional blocks or not.
395 * @param offsetAdditionalBlocks Optional out parameter for the offset to the
396 * first additional block. Value is only valid if
397 * hasAdditionalBlocks is not equal to 0.
398 * @param numAdditionalBlocks Optional out parameter for the number of
399 * additional blocks. Value is only valid if
400 * hasAdditionalBlocks is not equal to 0.
401 * @return 0 on success and non-zero on failure.
403 int get_open_mar_file_info(MarFile
* mar
, size_t* mar_position
,
404 int* hasSignatureBlock
, uint32_t* numSignatures
,
405 int* hasAdditionalBlocks
,
406 uint32_t* offsetAdditionalBlocks
,
407 uint32_t* numAdditionalBlocks
) {
408 uint32_t offsetToIndex
, offsetToContent
, signatureCount
, signatureLen
, i
;
410 /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */
411 if (!hasSignatureBlock
&& !hasAdditionalBlocks
) {
415 /* Skip to the start of the offset index */
417 if (mar_buffer_seek(mar
, mar_position
, MAR_ID_SIZE
) != 0) {
421 /* Read the offset to the index. */
422 if (mar_read_buffer(mar
, &offsetToIndex
, mar_position
,
423 sizeof(offsetToIndex
)) != 0) {
426 offsetToIndex
= ntohl(offsetToIndex
);
429 /* Skip past the MAR file size field */
430 if (mar_buffer_seek(mar
, mar_position
, sizeof(uint64_t)) != 0) {
434 /* Read the number of signatures field */
435 if (mar_read_buffer(mar
, numSignatures
, mar_position
,
436 sizeof(*numSignatures
)) != 0) {
439 *numSignatures
= ntohl(*numSignatures
);
442 /* Skip to the first index entry past the index size field
443 We do it in 2 calls because offsetToIndex + sizeof(uint32_t)
444 could overflow in theory. */
446 if (mar_buffer_seek(mar
, mar_position
, offsetToIndex
) != 0) {
450 if (mar_buffer_seek(mar
, mar_position
, sizeof(uint32_t)) != 0) {
454 /* Read the first offset to content field. */
455 if (mar_read_buffer(mar
, &offsetToContent
, mar_position
,
456 sizeof(offsetToContent
)) != 0) {
459 offsetToContent
= ntohl(offsetToContent
);
461 /* Check if we have a new or old MAR file */
462 if (hasSignatureBlock
) {
463 if (offsetToContent
== MAR_ID_SIZE
+ sizeof(uint32_t)) {
464 *hasSignatureBlock
= 0;
466 *hasSignatureBlock
= 1;
470 /* If the caller doesn't care about the product info block
471 value, then just return */
472 if (!hasAdditionalBlocks
) {
476 /* Skip to the start of the signature block */
478 if (mar_buffer_seek(mar
, mar_position
, SIGNATURE_BLOCK_OFFSET
) != 0) {
482 /* Get the number of signatures */
483 if (mar_read_buffer(mar
, &signatureCount
, mar_position
,
484 sizeof(signatureCount
)) != 0) {
487 signatureCount
= ntohl(signatureCount
);
489 /* Check that we have less than the max amount of signatures so we don't
490 waste too much of either updater's or signmar's time. */
491 if (signatureCount
> MAX_SIGNATURES
) {
495 /* Skip past the whole signature block */
496 for (i
= 0; i
< signatureCount
; i
++) {
497 /* Skip past the signature algorithm ID */
498 if (mar_buffer_seek(mar
, mar_position
, sizeof(uint32_t)) != 0) {
502 /* Read the signature length and skip past the signature */
503 if (mar_read_buffer(mar
, &signatureLen
, mar_position
, sizeof(uint32_t)) !=
507 signatureLen
= ntohl(signatureLen
);
508 if (mar_buffer_seek(mar
, mar_position
, signatureLen
) != 0) {
513 if (*mar_position
<= (size_t)INT64_MAX
&&
514 (int64_t)mar_position
== (int64_t)offsetToContent
) {
515 *hasAdditionalBlocks
= 0;
517 if (numAdditionalBlocks
) {
518 /* We have an additional block, so read in the number of additional blocks
519 and set the offset. */
520 *hasAdditionalBlocks
= 1;
521 if (mar_read_buffer(mar
, numAdditionalBlocks
, mar_position
,
522 sizeof(uint32_t)) != 0) {
525 *numAdditionalBlocks
= ntohl(*numAdditionalBlocks
);
526 if (offsetAdditionalBlocks
) {
527 if (*mar_position
> (size_t)UINT32_MAX
) {
530 *offsetAdditionalBlocks
= (uint32_t)*mar_position
;
532 } else if (offsetAdditionalBlocks
) {
533 /* numAdditionalBlocks is not specified but offsetAdditionalBlocks
535 if (mar_buffer_seek(mar
, mar_position
, sizeof(uint32_t)) != 0) {
538 if (*mar_position
> (size_t)UINT32_MAX
) {
541 *offsetAdditionalBlocks
= (uint32_t)*mar_position
;
549 * Reads the product info block from the MAR file's additional block section.
550 * The caller is responsible for freeing the fields in infoBlock
551 * if the return is successful.
553 * @param infoBlock Out parameter for where to store the result to
554 * @return 0 on success, -1 on failure
556 int read_product_info_block(char* path
,
557 struct ProductInformationBlock
* infoBlock
) {
560 MarReadResult result
= mar_open(path
, &mar
);
561 if (result
!= MAR_READ_SUCCESS
) {
563 "ERROR: could not open file in read_product_info_block()\n");
566 rv
= mar_read_product_info_block(mar
, infoBlock
);
572 * Reads the product info block from the MAR file's additional block section.
573 * The caller is responsible for freeing the fields in infoBlock
574 * if the return is successful.
576 * @param infoBlock Out parameter for where to store the result to
577 * @return 0 on success, -1 on failure
579 int mar_read_product_info_block(MarFile
* mar
,
580 struct ProductInformationBlock
* infoBlock
) {
581 uint32_t offsetAdditionalBlocks
, numAdditionalBlocks
, additionalBlockSize
,
583 int hasAdditionalBlocks
;
584 size_t mar_position
= 0;
586 /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and
587 product version < 32 bytes + 3 NULL terminator bytes. */
588 char buf
[MAXADDITIONALBLOCKSIZE
+ 1] = {'\0'};
589 if (get_open_mar_file_info(mar
, &mar_position
, NULL
, NULL
,
590 &hasAdditionalBlocks
, &offsetAdditionalBlocks
,
591 &numAdditionalBlocks
) != 0) {
595 /* We only have the one additional block type and only one is expected to be
596 in a MAR file so check if any exist and process the first found */
597 if (numAdditionalBlocks
> 0) {
598 /* Read the additional block size */
599 if (mar_read_buffer(mar
, &additionalBlockSize
, &mar_position
,
600 sizeof(additionalBlockSize
)) != 0) {
603 additionalBlockSize
= ntohl(additionalBlockSize
) -
604 sizeof(additionalBlockSize
) -
605 sizeof(additionalBlockID
);
607 /* Additional Block sizes should only be 96 bytes long */
608 if (additionalBlockSize
> MAXADDITIONALBLOCKSIZE
) {
612 /* Read the additional block ID */
613 if (mar_read_buffer(mar
, &additionalBlockID
, &mar_position
,
614 sizeof(additionalBlockID
)) != 0) {
617 additionalBlockID
= ntohl(additionalBlockID
);
619 if (PRODUCT_INFO_BLOCK_ID
== additionalBlockID
) {
620 const char* location
;
623 if (mar_read_buffer(mar
, buf
, &mar_position
, additionalBlockSize
) != 0) {
627 /* Extract the MAR channel name from the buffer. For now we
628 point to the stack allocated buffer but we strdup this
629 if we are within bounds of each field's max length. */
631 len
= strlen(location
);
632 infoBlock
->MARChannelID
= location
;
635 infoBlock
->MARChannelID
= NULL
;
639 /* Extract the version from the buffer */
640 len
= strlen(location
);
641 infoBlock
->productVersion
= location
;
643 infoBlock
->MARChannelID
= NULL
;
644 infoBlock
->productVersion
= NULL
;
647 infoBlock
->MARChannelID
= strdup(infoBlock
->MARChannelID
);
648 infoBlock
->productVersion
= strdup(infoBlock
->productVersion
);
651 /* This is not the additional block you're looking for. Move along. */
652 if (mar_buffer_seek(mar
, &mar_position
, additionalBlockSize
) != 0) {
658 /* If we had a product info block we would have already returned */
662 const MarItem
* mar_find_item(MarFile
* mar
, const char* name
) {
666 if (!mar
->item_table_is_valid
) {
667 if (mar_read_index(mar
)) {
670 mar
->item_table_is_valid
= 1;
674 hash
= mar_hash_name(name
);
676 item
= mar
->item_table
[hash
];
677 while (item
&& strcmp(item
->name
, name
) != 0) {
681 /* If this is the first time seeing this item's indexes, return it */
682 if (mar_insert_offset(mar
, item
->offset
, item
->length
) == 1) {
685 fprintf(stderr
, "ERROR: file content collision in mar_find_item()\n");
690 int mar_enum_items(MarFile
* mar
, MarItemCallback callback
, void* closure
) {
694 if (!mar
->item_table_is_valid
) {
695 if (mar_read_index(mar
)) {
698 mar
->item_table_is_valid
= 1;
702 for (i
= 0; i
< TABLESIZE
; ++i
) {
703 item
= mar
->item_table
[i
];
705 /* if this is the first time seeing this item's indexes, process it */
706 if (mar_insert_offset(mar
, item
->offset
, item
->length
) == 1) {
707 rv
= callback(mar
, item
, closure
);
712 fprintf(stderr
, "ERROR: file content collision in mar_enum_items()\n");
722 int mar_read(MarFile
* mar
, const MarItem
* item
, int offset
, uint8_t* buf
,
725 size_t mar_position
= 0;
727 if (offset
== (int)item
->length
) {
730 if (offset
> (int)item
->length
) {
734 nr
= item
->length
- offset
;
739 // Avoid adding item->offset and offset directly, just in case of overflow.
740 if (mar_buffer_seek(mar
, &mar_position
, item
->offset
)) {
743 if (mar_buffer_seek(mar
, &mar_position
, offset
)) {
747 return mar_read_buffer_max(mar
, buf
, &mar_position
, nr
);
751 * Determines the MAR file information.
753 * @param path The path of the MAR file to check.
754 * @param hasSignatureBlock Optional out parameter specifying if the MAR
755 * file has a signature block or not.
756 * @param numSignatures Optional out parameter for storing the number
757 * of signatures in the MAR file.
758 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
759 * file has additional blocks or not.
760 * @param offsetAdditionalBlocks Optional out parameter for the offset to the
761 * first additional block. Value is only valid if
762 * hasAdditionalBlocks is not equal to 0.
763 * @param numAdditionalBlocks Optional out parameter for the number of
764 * additional blocks. Value is only valid if
765 * has_additional_blocks is not equal to 0.
766 * @return 0 on success and non-zero on failure.
768 int get_mar_file_info(const char* path
, int* hasSignatureBlock
,
769 uint32_t* numSignatures
, int* hasAdditionalBlocks
,
770 uint32_t* offsetAdditionalBlocks
,
771 uint32_t* numAdditionalBlocks
) {
774 size_t mar_position
= 0;
775 MarReadResult result
= mar_open(path
, &mar
);
776 if (result
!= MAR_READ_SUCCESS
) {
777 fprintf(stderr
, "ERROR: could not read file in get_mar_file_info()\n");
781 rv
= get_open_mar_file_info(mar
, &mar_position
, hasSignatureBlock
,
782 numSignatures
, hasAdditionalBlocks
,
783 offsetAdditionalBlocks
, numAdditionalBlocks
);