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