tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mar_create.c (11521B)


      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 
     23 struct 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 */
     34 static 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);
     43 
     44  if (stack->size_allocated - stack->size_used < size) {
     45    /* increase size of stack */
     46    size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE);
     47    stack->head = realloc(stack->head, size_needed);
     48    if (!stack->head) {
     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);
     57  n_length = htonl(length);
     58  n_flags = htonl(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 
     76 static int mar_concat_file(FILE* fp, const char* path) {
     77  FILE* in;
     78  char buf[BLOCKSIZE];
     79  size_t len;
     80  int rv = 0;
     81 
     82  in = fopen(path, "rb");
     83  if (!in) {
     84    fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n");
     85    perror(path);
     86    return -1;
     87  }
     88 
     89  while ((len = fread(buf, 1, BLOCKSIZE, 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 */
    109 static int mar_concat_product_info_block(
    110    FILE* fp, struct MarItemStack* stack,
    111    struct ProductInformationBlock* infoBlock) {
    112  char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE];
    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_SIZE) {
    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_SIZE) {
    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_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE +
    135                  2;
    136  if (stack) {
    137    stack->last_offset += infoBlockSize;
    138  }
    139 
    140  /* Write out the product info block size */
    141  infoBlockSize = htonl(infoBlockSize);
    142  if (fwrite(&infoBlockSize, sizeof(infoBlockSize), 1, fp) != 1) {
    143    return -1;
    144  }
    145  infoBlockSize = ntohl(infoBlockSize);
    146 
    147  /* Write out the product info block ID */
    148  additionalBlockID = htonl(additionalBlockID);
    149  if (fwrite(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) {
    150    return -1;
    151  }
    152  additionalBlockID = ntohl(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 */
    186 int 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(stderr, "ERROR: Could not obtain MAR information.\n");
    200    return -1;
    201  }
    202 
    203  if (hasSignatureBlock && numSignatures) {
    204    fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
    205    return -1;
    206  }
    207 
    208  fp = fopen(path, "r+b");
    209  if (!fp) {
    210    fprintf(stderr, "ERROR: could not open target file: %s\n", path);
    211    return -1;
    212  }
    213 
    214  if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
    215    fprintf(stderr, "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);
    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);
    237 
    238    if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
    239      if (fseeko(fp, oldPos, SEEK_SET)) {
    240        fprintf(stderr, "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, infoBlock)) {
    246        fprintf(stderr, "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_CUR)) {
    257      fprintf(stderr, "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(stderr, "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 */
    279 int 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) {
    293    fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
    294    return -1;
    295  }
    296 
    297  if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) {
    298    goto failure;
    299  }
    300  if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) {
    301    goto failure;
    302  }
    303 
    304  stack.last_offset = MAR_ID_SIZE + 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) {
    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) {
    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);
    322  if (fwrite(&numAdditionalSections, sizeof(numAdditionalSections), 1, fp) !=
    323      1) {
    324    goto failure;
    325  }
    326  numAdditionalSections = ntohl(numAdditionalSections);
    327 
    328  if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
    329    goto failure;
    330  }
    331 
    332  for (i = 0; i < num_files; ++i) {
    333    if (stat(files[i], &st)) {
    334      fprintf(stderr, "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])) {
    339      goto failure;
    340    }
    341 
    342    /* concatenate input file to archive */
    343    if (mar_concat_file(fp, files[i])) {
    344      goto failure;
    345    }
    346  }
    347 
    348  /* write out the index (prefixed with length of index) */
    349  size_of_index = htonl(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) {
    360    goto failure;
    361  }
    362 
    363  /* write out offset to index file in network byte order */
    364  offset_to_index = htonl(stack.last_offset);
    365  if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) {
    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);
    372 
    373  sizeOfEntireMAR =
    374      ((uint64_t)stack.last_offset) + stack.size_used + sizeof(size_of_index);
    375  sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
    376  if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
    377    goto failure;
    378  }
    379  sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
    380 
    381  rv = 0;
    382 failure:
    383  if (stack.head) {
    384    free(stack.head);
    385  }
    386  fclose(fp);
    387  if (rv) {
    388    remove(dest);
    389  }
    390  return rv;
    391 }