diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /modules/libmar/src | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/libmar/src')
-rw-r--r-- | modules/libmar/src/mar.h | 260 | ||||
-rw-r--r-- | modules/libmar/src/mar_cmdline.h | 102 | ||||
-rw-r--r-- | modules/libmar/src/mar_create.c | 391 | ||||
-rw-r--r-- | modules/libmar/src/mar_extract.c | 87 | ||||
-rw-r--r-- | modules/libmar/src/mar_private.h | 78 | ||||
-rw-r--r-- | modules/libmar/src/mar_read.c | 787 | ||||
-rw-r--r-- | modules/libmar/src/moz.build | 39 |
7 files changed, 1744 insertions, 0 deletions
diff --git a/modules/libmar/src/mar.h b/modules/libmar/src/mar.h new file mode 100644 index 0000000000..74ab7657f8 --- /dev/null +++ b/modules/libmar/src/mar.h @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MAR_H__ +#define MAR_H__ + +#include <assert.h> // for C11 static_assert +#include <stdint.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* We have a MAX_SIGNATURES limit so that an invalid MAR will never + * waste too much of either updater's or signmar's time. + * It is also used at various places internally and will affect memory usage. + * If you want to increase this value above 9 then you need to adjust parsing + * code in tool/mar.c. + */ +#define MAX_SIGNATURES 8 +static_assert(MAX_SIGNATURES <= 9, "too many signatures"); + +struct ProductInformationBlock { + const char* MARChannelID; + const char* productVersion; +}; + +/** + * The MAR item data structure. + */ +typedef struct MarItem_ { + struct MarItem_* next; /* private field */ + uint32_t offset; /* offset into archive */ + uint32_t length; /* length of data in bytes */ + uint32_t flags; /* contains file mode bits */ + char name[1]; /* file path */ +} MarItem; + +/** + * File offset and length for tracking access of byte indexes + */ +typedef struct SeenIndex_ { + struct SeenIndex_* next; /* private field */ + uint32_t offset; /* offset into archive */ + uint32_t length; /* length of the data in bytes */ +} SeenIndex; + +#define TABLESIZE 256 + +/** + * Mozilla ARchive (MAR) file data structure + */ +struct MarFile_ { + unsigned char* buffer; /* file buffer containing the entire MAR */ + size_t data_len; /* byte count of the data in the buffer */ + MarItem* item_table[TABLESIZE]; /* hash table of files in the archive */ + SeenIndex* index_list; /* file indexes processed */ + int item_table_is_valid; /* header and index validation flag */ +}; + +typedef struct MarFile_ MarFile; + +/** + * Signature of callback function passed to mar_enum_items. + * @param mar The MAR file being visited. + * @param item The MAR item being visited. + * @param data The data parameter passed by the caller of mar_enum_items. + * @return A non-zero value to stop enumerating. + */ +typedef int (*MarItemCallback)(MarFile* mar, const MarItem* item, void* data); + +enum MarReadResult_ { + MAR_READ_SUCCESS, + MAR_IO_ERROR, + MAR_MEM_ERROR, + MAR_FILE_TOO_BIG_ERROR, +}; + +typedef enum MarReadResult_ MarReadResult; + +/** + * Open a MAR file for reading. + * @param path Specifies the path to the MAR file to open. This path must + * be compatible with fopen. + * @param out_mar Out-parameter through which the created MarFile structure is + * returned. Guaranteed to be a valid structure if + * MAR_READ_SUCCESS is returned. Otherwise NULL will be + * assigned. + * @return NULL if an error occurs. + */ +MarReadResult mar_open(const char* path, MarFile** out_mar); + +#ifdef XP_WIN +MarReadResult mar_wopen(const wchar_t* path, MarFile** out_mar); +#endif + +/** + * Close a MAR file that was opened using mar_open. + * @param mar The MarFile object to close. + */ +void mar_close(MarFile* mar); + +/** + * Reads the specified amount of data from the buffer in MarFile that contains + * the entirety of the MAR file data. + * @param mar The MAR file to read from. + * @param dest The buffer to read into. + * @param position The byte index to start reading from the MAR at. + * On success, position will be incremented by size. + * @param size The number of bytes to read. + * @return 0 If the specified amount of data was read. + * -1 If the buffer MAR is not large enough to read the + * specified amount of data at the specified position. + */ +int mar_read_buffer(MarFile* mar, void* dest, size_t* position, size_t size); + +/** + * Reads the specified amount of data from the buffer in MarFile that contains + * the entirety of the MAR file data. If there isn't that much data remaining, + * reads as much as possible. + * @param mar The MAR file to read from. + * @param dest The buffer to read into. + * @param position The byte index to start reading from the MAR at. + * This function will increment position by the number of bytes + * copied. + * @param size The maximum number of bytes to read. + * @return The number of bytes copied into dest. + */ +int mar_read_buffer_max(MarFile* mar, void* dest, size_t* position, + size_t size); + +/** + * Increments position by distance. Checks that the resulting position is still + * within the bounds of the buffer. Much like fseek, this will allow position to + * be successfully placed just after the end of the buffer. + * @param mar The MAR file to read from. + * @param position The byte index to start reading from the MAR at. + * On success, position will be incremented by size. + * @param distance The number of bytes to move forward by. + * @return 0 If position was successfully moved. + * -1 If moving position by distance would move it outside the + * bounds of the buffer. + */ +int mar_buffer_seek(MarFile* mar, size_t* position, size_t distance); + +/** + * Find an item in the MAR file by name. + * @param mar The MarFile object to query. + * @param item The name of the item to query. + * @return A const reference to a MAR item or NULL if not found. + */ +const MarItem* mar_find_item(MarFile* mar, const char* item); + +/** + * Enumerate all MAR items via callback function. + * @param mar The MAR file to enumerate. + * @param callback The function to call for each MAR item. + * @param data A caller specified value that is passed along to the + * callback function. + * @return 0 if the enumeration ran to completion. Otherwise, any + * non-zero return value from the callback is returned. + */ +int mar_enum_items(MarFile* mar, MarItemCallback callback, void* data); + +/** + * Read from MAR item at given offset up to bufsize bytes. + * @param mar The MAR file to read. + * @param item The MAR item to read. + * @param offset The byte offset relative to the start of the item. + * @param buf A pointer to a buffer to copy the data into. + * @param bufsize The length of the buffer to copy the data into. + * @return The number of bytes written or a negative value if an + * error occurs. + */ +int mar_read(MarFile* mar, const MarItem* item, int offset, uint8_t* buf, + int bufsize); + +/** + * Create a MAR file from a set of files. + * @param dest The path to the file to create. This path must be + * compatible with fopen. + * @param numfiles The number of files to store in the archive. + * @param files The list of null-terminated file paths. Each file + * path must be compatible with fopen. + * @param infoBlock The information to store in the product information block. + * @return A non-zero value if an error occurs. + */ +int mar_create(const char* dest, int numfiles, char** files, + struct ProductInformationBlock* infoBlock); + +/** + * Extract a MAR file to the current working directory. + * @param path The path to the MAR file to extract. This path must be + * compatible with fopen. + * @return A non-zero value if an error occurs. + */ +int mar_extract(const char* path); + +#define MAR_MAX_CERT_SIZE (16 * 1024) // Way larger than necessary + +/* Read the entire file (not a MAR file) into a newly-allocated buffer. + * This function does not write to stderr. Instead, the caller should + * write whatever error messages it sees fit. The caller must free the returned + * buffer using free(). + * + * @param filePath The path to the file that should be read. + * @param maxSize The maximum valid file size. + * @param data On success, *data will point to a newly-allocated buffer + * with the file's contents in it. + * @param size On success, *size will be the size of the created buffer. + * + * @return 0 on success, -1 on error + */ +int mar_read_entire_file(const char* filePath, uint32_t maxSize, + /*out*/ const uint8_t** data, + /*out*/ uint32_t* size); + +/** + * Verifies a MAR file by verifying each signature with the corresponding + * certificate. That is, the first signature will be verified using the first + * certificate given, the second signature will be verified using the second + * certificate given, etc. The signature count must exactly match the number of + * certificates given, and all signature verifications must succeed. + * We do not check that the certificate was issued by any trusted authority. + * We assume it to be self-signed. We do not check whether the certificate + * is valid for this usage. + * + * @param mar The already opened MAR file. + * @param certData Pointer to the first element in an array of certificate + * file data. + * @param certDataSizes Pointer to the first element in an array for size of + * the cert data. + * @param certCount The number of elements in certData and certDataSizes + * @return 0 on success + * a negative number if there was an error + * a positive number if the signature does not verify + */ +int mar_verify_signatures(MarFile* mar, const uint8_t* const* certData, + const uint32_t* certDataSizes, uint32_t certCount); + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int mar_read_product_info_block(MarFile* mar, + struct ProductInformationBlock* infoBlock); + +#ifdef __cplusplus +} +#endif + +#endif /* MAR_H__ */ diff --git a/modules/libmar/src/mar_cmdline.h b/modules/libmar/src/mar_cmdline.h new file mode 100644 index 0000000000..4b9302f0b1 --- /dev/null +++ b/modules/libmar/src/mar_cmdline.h @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MAR_CMDLINE_H__ +#define MAR_CMDLINE_H__ + +/* We use NSPR here just to import the definition of uint32_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ProductInformationBlock; + +/** + * Determines MAR file information. + * + * @param path The path of the MAR file to check. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * has_additional_blocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info(const char* path, int* hasSignatureBlock, + uint32_t* numSignatures, int* hasAdditionalBlocks, + uint32_t* offsetAdditionalBlocks, + uint32_t* numAdditionalBlocks); + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int read_product_info_block(char* path, + struct ProductInformationBlock* infoBlock); + +/** + * Refreshes the product information block with the new information. + * The input MAR must not be signed or the function call will fail. + * + * @param path The path to the MAR file whose product info block + * should be refreshed. + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int refresh_product_info_block(const char* path, + struct ProductInformationBlock* infoBlock); + +/** + * Writes out a copy of the MAR at src but with the signature block stripped. + * + * @param src The path of the source MAR file + * @param dest The path of the MAR file to write out that + has no signature block + * @return 0 on success + * -1 on error +*/ +int strip_signature_block(const char* src, const char* dest); + +/** + * Extracts a signature from a MAR file, base64 encodes it, and writes it out + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to extract + * @param dest The path of file to write the signature to + * @return 0 on success + * -1 on error + */ +int extract_signature(const char* src, uint32_t sigIndex, const char* dest); + +/** + * Imports a base64 encoded signature into a MAR file + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to import + * @param base64SigFile A file which contains the signature to import + * @param dest The path of the destination MAR file with replaced + * signature + * @return 0 on success + * -1 on error + */ +int import_signature(const char* src, uint32_t sigIndex, + const char* base64SigFile, const char* dest); + +#ifdef __cplusplus +} +#endif + +#endif /* MAR_CMDLINE_H__ */ diff --git a/modules/libmar/src/mar_create.c b/modules/libmar/src/mar_create.c new file mode 100644 index 0000000000..0ac2bf7b2c --- /dev/null +++ b/modules/libmar/src/mar_create.c @@ -0,0 +1,391 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include "mar_private.h" +#include "mar_cmdline.h" +#include "mar.h" + +#ifdef XP_WIN +# include <winsock2.h> +#else +# include <netinet/in.h> +# include <unistd.h> +#endif + +struct MarItemStack { + void* head; + uint32_t size_used; + uint32_t size_allocated; + uint32_t last_offset; +}; + +/** + * Push a new item onto the stack of items. The stack is a single block + * of memory. + */ +static int mar_push(struct MarItemStack* stack, uint32_t length, uint32_t flags, + const char* name) { + int namelen; + uint32_t n_offset, n_length, n_flags; + uint32_t size; + char* data; + + namelen = strlen(name); + size = MAR_ITEM_SIZE(namelen); + + if (stack->size_allocated - stack->size_used < size) { + /* increase size of stack */ + size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE); + stack->head = realloc(stack->head, size_needed); + if (!stack->head) { + return -1; + } + stack->size_allocated = size_needed; + } + + data = (((char*)stack->head) + stack->size_used); + + n_offset = htonl(stack->last_offset); + n_length = htonl(length); + n_flags = htonl(flags); + + memcpy(data, &n_offset, sizeof(n_offset)); + data += sizeof(n_offset); + + memcpy(data, &n_length, sizeof(n_length)); + data += sizeof(n_length); + + memcpy(data, &n_flags, sizeof(n_flags)); + data += sizeof(n_flags); + + memcpy(data, name, namelen + 1); + + stack->size_used += size; + stack->last_offset += length; + return 0; +} + +static int mar_concat_file(FILE* fp, const char* path) { + FILE* in; + char buf[BLOCKSIZE]; + size_t len; + int rv = 0; + + in = fopen(path, "rb"); + if (!in) { + fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n"); + perror(path); + return -1; + } + + while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) { + if (fwrite(buf, len, 1, fp) != 1) { + rv = -1; + break; + } + } + + fclose(in); + return rv; +} + +/** + * Writes out the product information block to the specified file. + * + * @param fp The opened MAR file being created. + * @param stack A pointer to the MAR item stack being used to create + * the MAR + * @param infoBlock The product info block to store in the file. + * @return 0 on success. + */ +static int mar_concat_product_info_block( + FILE* fp, struct MarItemStack* stack, + struct ProductInformationBlock* infoBlock) { + char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE]; + uint32_t additionalBlockID = 1, infoBlockSize, unused; + if (!fp || !infoBlock || !infoBlock->MARChannelID || + !infoBlock->productVersion) { + return -1; + } + + /* The MAR channel name must be < 64 bytes per the spec */ + if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) { + return -1; + } + + /* The product version must be < 32 bytes per the spec */ + if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) { + return -1; + } + + /* Although we don't need the product information block size to include the + maximum MAR channel name and product version, we allocate the maximum + amount to make it easier to modify the MAR file for repurposing MAR files + to different MAR channels. + 2 is for the NULL terminators. */ + infoBlockSize = sizeof(infoBlockSize) + sizeof(additionalBlockID) + + PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE + + 2; + if (stack) { + stack->last_offset += infoBlockSize; + } + + /* Write out the product info block size */ + infoBlockSize = htonl(infoBlockSize); + if (fwrite(&infoBlockSize, sizeof(infoBlockSize), 1, fp) != 1) { + return -1; + } + infoBlockSize = ntohl(infoBlockSize); + + /* Write out the product info block ID */ + additionalBlockID = htonl(additionalBlockID); + if (fwrite(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + /* Write out the channel name and NULL terminator */ + if (fwrite(infoBlock->MARChannelID, strlen(infoBlock->MARChannelID) + 1, 1, + fp) != 1) { + return -1; + } + + /* Write out the product version string and NULL terminator */ + if (fwrite(infoBlock->productVersion, strlen(infoBlock->productVersion) + 1, + 1, fp) != 1) { + return -1; + } + + /* Write out the rest of the block that is unused */ + unused = infoBlockSize - (sizeof(infoBlockSize) + sizeof(additionalBlockID) + + strlen(infoBlock->MARChannelID) + + strlen(infoBlock->productVersion) + 2); + memset(buf, 0, sizeof(buf)); + if (fwrite(buf, unused, 1, fp) != 1) { + return -1; + } + return 0; +} + +/** + * Refreshes the product information block with the new information. + * The input MAR must not be signed or the function call will fail. + * + * @param path The path to the MAR file whose product info block + * should be refreshed. + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int refresh_product_info_block(const char* path, + struct ProductInformationBlock* infoBlock) { + FILE* fp; + int rv; + uint32_t numSignatures, additionalBlockSize, additionalBlockID, + offsetAdditionalBlocks, numAdditionalBlocks, i; + int additionalBlocks, hasSignatureBlock; + int64_t oldPos; + + rv = get_mar_file_info(path, &hasSignatureBlock, &numSignatures, + &additionalBlocks, &offsetAdditionalBlocks, + &numAdditionalBlocks); + if (rv) { + fprintf(stderr, "ERROR: Could not obtain MAR information.\n"); + return -1; + } + + if (hasSignatureBlock && numSignatures) { + fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n"); + return -1; + } + + fp = fopen(path, "r+b"); + if (!fp) { + fprintf(stderr, "ERROR: could not open target file: %s\n", path); + return -1; + } + + if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) { + fprintf(stderr, "ERROR: could not seek to additional blocks\n"); + fclose(fp); + return -1; + } + + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Get the position of the start of this block */ + oldPos = ftello(fp); + + /* Read the additional block size */ + if (fread(&additionalBlockSize, sizeof(additionalBlockSize), 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + if (fseeko(fp, oldPos, SEEK_SET)) { + fprintf(stderr, "Could not seek back to Product Information Block\n"); + fclose(fp); + return -1; + } + + if (mar_concat_product_info_block(fp, NULL, infoBlock)) { + fprintf(stderr, "Could not concat Product Information Block\n"); + fclose(fp); + return -1; + } + + fclose(fp); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (fseek(fp, additionalBlockSize, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past current block.\n"); + fclose(fp); + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + fclose(fp); + fprintf(stderr, "ERROR: Could not refresh because block does not exist\n"); + return -1; +} + +/** + * Create a MAR file from a set of files. + * @param dest The path to the file to create. This path must be + * compatible with fopen. + * @param numfiles The number of files to store in the archive. + * @param files The list of null-terminated file paths. Each file + * path must be compatible with fopen. + * @param infoBlock The information to store in the product information block. + * @return A non-zero value if an error occurs. + */ +int mar_create(const char* dest, int num_files, char** files, + struct ProductInformationBlock* infoBlock) { + struct MarItemStack stack; + uint32_t offset_to_index = 0, size_of_index, numSignatures, + numAdditionalSections; + uint64_t sizeOfEntireMAR = 0; + struct stat st; + FILE* fp; + int i, rv = -1; + + memset(&stack, 0, sizeof(stack)); + + fp = fopen(dest, "wb"); + if (!fp) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + return -1; + } + + if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) { + goto failure; + } + if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) { + goto failure; + } + + stack.last_offset = MAR_ID_SIZE + sizeof(offset_to_index) + + sizeof(numSignatures) + sizeof(numAdditionalSections) + + sizeof(sizeOfEntireMAR); + + /* We will circle back on this at the end of the MAR creation to fill it */ + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of signatures, for now only at most 1 is supported */ + numSignatures = 0; + if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of additional sections, for now just 1 + for the product info block */ + numAdditionalSections = htonl(1); + if (fwrite(&numAdditionalSections, sizeof(numAdditionalSections), 1, fp) != + 1) { + goto failure; + } + numAdditionalSections = ntohl(numAdditionalSections); + + if (mar_concat_product_info_block(fp, &stack, infoBlock)) { + goto failure; + } + + for (i = 0; i < num_files; ++i) { + if (stat(files[i], &st)) { + fprintf(stderr, "ERROR: file not found: %s\n", files[i]); + goto failure; + } + + if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) { + goto failure; + } + + /* concatenate input file to archive */ + if (mar_concat_file(fp, files[i])) { + goto failure; + } + } + + /* write out the index (prefixed with length of index) */ + size_of_index = htonl(stack.size_used); + if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) { + goto failure; + } + if (fwrite(stack.head, stack.size_used, 1, fp) != 1) { + goto failure; + } + + /* To protect against invalid MAR files, we assumes that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + /* write out offset to index file in network byte order */ + offset_to_index = htonl(stack.last_offset); + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { + goto failure; + } + if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) { + goto failure; + } + offset_to_index = ntohl(stack.last_offset); + + sizeOfEntireMAR = + ((uint64_t)stack.last_offset) + stack.size_used + sizeof(size_of_index); + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + rv = 0; +failure: + if (stack.head) { + free(stack.head); + } + fclose(fp); + if (rv) { + remove(dest); + } + return rv; +} diff --git a/modules/libmar/src/mar_extract.c b/modules/libmar/src/mar_extract.c new file mode 100644 index 0000000000..9b4a3fa26d --- /dev/null +++ b/modules/libmar/src/mar_extract.c @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include "mar_private.h" +#include "mar.h" + +#ifdef XP_WIN +# include <io.h> +# include <direct.h> +# define fdopen _fdopen +#endif + +/* Ensure that the directory containing this file exists */ +static int mar_ensure_parent_dir(const char* path) { + char* slash = strrchr(path, '/'); + if (slash) { + *slash = '\0'; + mar_ensure_parent_dir(path); +#ifdef XP_WIN + _mkdir(path); +#else + mkdir(path, 0755); +#endif + *slash = '/'; + } + return 0; +} + +static int mar_test_callback(MarFile* mar, const MarItem* item, void* unused) { + FILE* fp; + uint8_t buf[BLOCKSIZE]; + int fd, len, offset = 0; + + if (mar_ensure_parent_dir(item->name)) { + return -1; + } + +#ifdef XP_WIN + fd = _open(item->name, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, + item->flags); +#else + fd = creat(item->name, item->flags); +#endif + if (fd == -1) { + fprintf(stderr, "ERROR: could not create file in mar_test_callback()\n"); + perror(item->name); + return -1; + } + + fp = fdopen(fd, "wb"); + if (!fp) { + return -1; + } + + while ((len = mar_read(mar, item, offset, buf, sizeof(buf))) > 0) { + if (fwrite(buf, len, 1, fp) != 1) { + break; + } + offset += len; + } + + fclose(fp); + return len == 0 ? 0 : -1; +} + +int mar_extract(const char* path) { + MarFile* mar; + int rv; + + MarReadResult result = mar_open(path, &mar); + if (result != MAR_READ_SUCCESS) { + return -1; + } + + rv = mar_enum_items(mar, mar_test_callback, NULL); + + mar_close(mar); + return rv; +} diff --git a/modules/libmar/src/mar_private.h b/modules/libmar/src/mar_private.h new file mode 100644 index 0000000000..bd9d4386fe --- /dev/null +++ b/modules/libmar/src/mar_private.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MAR_PRIVATE_H__ +#define MAR_PRIVATE_H__ + +#include <assert.h> // for C11 static_assert +#include "limits.h" +#include <stdint.h> + +#define BLOCKSIZE 4096 +#define ROUND_UP(n, incr) (((n) / (incr) + 1) * (incr)) + +#define MAR_ID "MAR1" +#define MAR_ID_SIZE 4 + +/* The signature block comes directly after the header block + which is 16 bytes */ +#define SIGNATURE_BLOCK_OFFSET 16 + +/* Make sure the file is less than 500MB. We do this to protect against + invalid MAR files. */ +#define MAX_SIZE_OF_MAR_FILE ((int64_t)524288000) + +/* Existing code makes assumptions that the file size is + smaller than LONG_MAX. */ +static_assert(MAX_SIZE_OF_MAR_FILE < ((int64_t)LONG_MAX), + "max mar file size is too big"); + +/* We store at most the size up to the signature block + 4 + bytes per BLOCKSIZE bytes */ +static_assert(sizeof(BLOCKSIZE) < (SIGNATURE_BLOCK_OFFSET + sizeof(uint32_t)), + "BLOCKSIZE is too big"); + +/* The maximum size of any signature supported by current and future + implementations of the signmar program. */ +#define MAX_SIGNATURE_LENGTH 2048 + +/* Each additional block has a unique ID. + The product information block has an ID of 1. */ +#define PRODUCT_INFO_BLOCK_ID 1 + +#define MAR_ITEM_SIZE(namelen) (3 * sizeof(uint32_t) + (namelen) + 1) + +/* Product Information Block (PIB) constants */ +#define PIB_MAX_MAR_CHANNEL_ID_SIZE 63 +#define PIB_MAX_PRODUCT_VERSION_SIZE 31 + +/* The mar program is compiled as a host bin so we don't have access to NSPR at + runtime. For that reason we use ntohl, htonl, and define HOST_TO_NETWORK64 + instead of the NSPR equivalents. */ +#ifdef XP_WIN +# include <winsock2.h> +/* Include stdio.h before redefining ftello and fseeko to avoid clobbering + * the ftello() and fseeko() function declarations in MinGW's stdio.h. */ +# include <stdio.h> +# define ftello _ftelli64 +# define fseeko _fseeki64 +#else +# define _FILE_OFFSET_BITS 64 +# include <netinet/in.h> +# include <unistd.h> +# include <stdio.h> +#endif + +#define HOST_TO_NETWORK64(x) \ + (((((uint64_t)x) & 0xFF) << 56) | ((((uint64_t)x) >> 8) & 0xFF) << 48) | \ + (((((uint64_t)x) >> 16) & 0xFF) << 40) | \ + (((((uint64_t)x) >> 24) & 0xFF) << 32) | \ + (((((uint64_t)x) >> 32) & 0xFF) << 24) | \ + (((((uint64_t)x) >> 40) & 0xFF) << 16) | \ + (((((uint64_t)x) >> 48) & 0xFF) << 8) | (((uint64_t)x) >> 56) +#define NETWORK_TO_HOST64 HOST_TO_NETWORK64 + +#endif /* MAR_PRIVATE_H__ */ diff --git a/modules/libmar/src/mar_read.c b/modules/libmar/src/mar_read.c new file mode 100644 index 0000000000..679dc258d6 --- /dev/null +++ b/modules/libmar/src/mar_read.c @@ -0,0 +1,787 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include "city.h" +#include "mar_private.h" +#include "mar.h" +#ifdef XP_WIN +# define strdup _strdup +#endif + +/* This block must be at most 104 bytes. + MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL + terminator bytes. We only check for 96 though because we remove 8 + bytes above from the additionalBlockSize: We subtract + sizeof(additionalBlockSize) and sizeof(additionalBlockID) */ +#define MAXADDITIONALBLOCKSIZE 96 + +static uint32_t mar_hash_name(const char* name) { + return CityHash64(name, strlen(name)) % TABLESIZE; +} + +static int mar_insert_item(MarFile* mar, const char* name, uint32_t namelen, + uint32_t offset, uint32_t length, uint32_t flags) { + MarItem *item, *root; + uint32_t hash; + + item = (MarItem*)malloc(sizeof(MarItem) + namelen); + if (!item) { + return -1; + } + item->next = NULL; + item->offset = offset; + item->length = length; + item->flags = flags; + memcpy(item->name, name, namelen + 1); + + hash = mar_hash_name(name); + + root = mar->item_table[hash]; + if (!root) { + mar->item_table[hash] = item; + } else { + /* append item */ + while (root->next) root = root->next; + root->next = item; + } + return 0; +} + +static int mar_consume_index(MarFile* mar, char** buf, const char* buf_end) { + /* + * Each item has the following structure: + * uint32_t offset (network byte order) + * uint32_t length (network byte order) + * uint32_t flags (network byte order) + * char name[N] (where N >= 1) + * char null_byte; + */ + uint32_t offset; + uint32_t length; + uint32_t flags; + const char* name; + int namelen; + + if ((buf_end - *buf) < (int)(3 * sizeof(uint32_t) + 2)) { + return -1; + } + + memcpy(&offset, *buf, sizeof(offset)); + *buf += sizeof(offset); + + memcpy(&length, *buf, sizeof(length)); + *buf += sizeof(length); + + memcpy(&flags, *buf, sizeof(flags)); + *buf += sizeof(flags); + + offset = ntohl(offset); + length = ntohl(length); + flags = ntohl(flags); + + name = *buf; + /* find namelen; must take care not to read beyond buf_end */ + while (**buf) { + /* buf_end points one byte past the end of buf's allocation */ + if (*buf == (buf_end - 1)) { + return -1; + } + ++(*buf); + } + namelen = (*buf - name); + /* must ensure that namelen is valid */ + if (namelen < 0) { + return -1; + } + /* consume null byte */ + if (*buf == buf_end) { + return -1; + } + ++(*buf); + + return mar_insert_item(mar, name, namelen, offset, length, flags); +} + +static int mar_read_index(MarFile* mar) { + char id[MAR_ID_SIZE], *buf, *bufptr, *bufend; + uint32_t offset_to_index, size_of_index; + size_t mar_position = 0; + + /* verify MAR ID */ + if (mar_read_buffer(mar, id, &mar_position, MAR_ID_SIZE) != 0) { + return -1; + } + if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) { + return -1; + } + + if (mar_read_buffer(mar, &offset_to_index, &mar_position, sizeof(uint32_t)) != + 0) { + return -1; + } + offset_to_index = ntohl(offset_to_index); + + mar_position = 0; + if (mar_buffer_seek(mar, &mar_position, offset_to_index) != 0) { + return -1; + } + if (mar_read_buffer(mar, &size_of_index, &mar_position, sizeof(uint32_t)) != + 0) { + return -1; + } + size_of_index = ntohl(size_of_index); + + buf = (char*)malloc(size_of_index); + if (!buf) { + return -1; + } + if (mar_read_buffer(mar, buf, &mar_position, size_of_index) != 0) { + free(buf); + return -1; + } + + bufptr = buf; + bufend = buf + size_of_index; + while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0) + ; + + free(buf); + return (bufptr == bufend) ? 0 : -1; +} + +/** + * Adds an offset and length to the MarFile's index_list + * @param mar The MarFile that owns this offset length pair + * @param offset The byte offset in the archive to be marked as processed + * @param length The length corresponding to this byte offset + * @return int 1 on success, 0 if offset has been previously processed + * -1 if unable to allocate space for the SeenIndexes + */ +static int mar_insert_offset(MarFile* mar, uint32_t offset, uint32_t length) { + /* Ignore files with no length */ + if (length == 0) { + return 1; + } + + SeenIndex* index = (SeenIndex*)malloc(sizeof(SeenIndex)); + if (!index) { + return -1; + } + index->next = NULL; + index->offset = offset; + index->length = length; + uint32_t index_end = index->offset + index->length - 1; + + /* If this is our first index store it at the front */ + if (mar->index_list == NULL) { + mar->index_list = index; + return 1; + } + + /* Search for matching indexes in the list of those previously visited */ + SeenIndex* previous; + SeenIndex* current = mar->index_list; + while (current != NULL) { + uint32_t current_end = current->offset + current->length - 1; + + /* If index has collided with the front or end of current or if current has + collided with the front or end of index return false */ + if ((index->offset >= current->offset && index->offset <= current_end) || + (index_end >= current->offset && index_end <= current_end) || + (current->offset >= index->offset && current->offset <= index_end) || + (current_end >= index->offset && current_end <= index_end)) { + free(index); + return 0; + } + + /* else move to the next in the list */ + previous = current; + current = current->next; + } + + /* These indexes are valid, track them */ + previous->next = index; + return 1; +} + +/** + * Internal shared code for mar_open and mar_wopen. + * Reads the entire MAR into memory. Fails if it is bigger than + * MAX_SIZE_OF_MAR_FILE bytes. + */ +static MarReadResult mar_fpopen(FILE* fp, MarFile** out_mar) { + *out_mar = NULL; + MarFile* mar; + + mar = (MarFile*)malloc(sizeof(*mar)); + if (!mar) { + return MAR_MEM_ERROR; + } + + off_t buffer_size = -1; + if (fseeko(fp, 0, SEEK_END) == 0) { + buffer_size = ftello(fp); + } + rewind(fp); + if (buffer_size < 0) { + fprintf(stderr, "Warning: MAR size could not be determined\n"); + buffer_size = MAX_SIZE_OF_MAR_FILE; + } + if (buffer_size > MAX_SIZE_OF_MAR_FILE) { + fprintf(stderr, "ERROR: MAR exceeds maximum size (%lli)\n", + (long long int)buffer_size); + free(mar); + return MAR_FILE_TOO_BIG_ERROR; + } + + mar->buffer = malloc(buffer_size); + if (!mar->buffer) { + fprintf(stderr, "ERROR: MAR buffer could not be allocated\n"); + free(mar); + return MAR_MEM_ERROR; + } + mar->data_len = fread(mar->buffer, 1, buffer_size, fp); + if (fgetc(fp) != EOF) { + fprintf(stderr, "ERROR: File is larger than buffer (%lli)\n", + (long long int)buffer_size); + free(mar->buffer); + free(mar); + return MAR_IO_ERROR; + } + if (ferror(fp)) { + fprintf(stderr, "ERROR: Failed to read MAR\n"); + free(mar->buffer); + free(mar); + return MAR_IO_ERROR; + } + + mar->item_table_is_valid = 0; + memset(mar->item_table, 0, sizeof(mar->item_table)); + mar->index_list = NULL; + + *out_mar = mar; + return MAR_READ_SUCCESS; +} + +MarReadResult mar_open(const char* path, MarFile** out_mar) { + *out_mar = NULL; + + FILE* fp; + + fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_open()\n"); + perror(path); + return MAR_IO_ERROR; + } + + MarReadResult result = mar_fpopen(fp, out_mar); + fclose(fp); + return result; +} + +#ifdef XP_WIN +MarReadResult mar_wopen(const wchar_t* path, MarFile** out_mar) { + *out_mar = NULL; + + FILE* fp; + + _wfopen_s(&fp, path, L"rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_wopen()\n"); + _wperror(path); + return MAR_IO_ERROR; + } + + MarReadResult result = mar_fpopen(fp, out_mar); + fclose(fp); + return result; +} +#endif + +void mar_close(MarFile* mar) { + MarItem* item; + SeenIndex* index; + int i; + + free(mar->buffer); + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + MarItem* temp = item; + item = item->next; + free(temp); + } + } + + while (mar->index_list != NULL) { + index = mar->index_list; + mar->index_list = index->next; + free(index); + } + + free(mar); +} + +int mar_read_buffer(MarFile* mar, void* dest, size_t* position, size_t size) { + // size may be provided by the MAR, which we may not have finished validating + // the signature on yet. Make sure not to trust it in a way that could + // cause an overflow. + if (size > mar->data_len) { + return -1; + } + if (*position > mar->data_len - size) { + return -1; + } + memcpy(dest, mar->buffer + *position, size); + *position += size; + return 0; +} + +int mar_read_buffer_max(MarFile* mar, void* dest, size_t* position, + size_t size) { + // size may be provided by the MAR, which we may not have finished validating + // the signature on yet. Make sure not to trust it in a way that could + // cause an overflow. + if (mar->data_len <= *position) { + return 0; + } + size_t read_count = mar->data_len - *position; + if (read_count > size) { + read_count = size; + } + memcpy(dest, mar->buffer + *position, read_count); + *position += read_count; + return read_count; +} + +int mar_buffer_seek(MarFile* mar, size_t* position, size_t distance) { + // distance may be provided by the MAR, which we may not have finished + // validating the signature on yet. Make sure not to trust it in a way that + // could cause an overflow. + if (distance > mar->data_len) { + return -1; + } + if (*position > mar->data_len - distance) { + return -1; + } + *position += distance; + return 0; +} + +/** + * Determines the MAR file information. + * + * @param mar An open MAR file. + * @param mar_position The current position in the MAR. + * Its value will be updated to the current + * position in the MAR after the function exits. + * Since its initial value will never actually be + * used, this is effectively an outparam. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_open_mar_file_info(MarFile* mar, size_t* mar_position, + int* hasSignatureBlock, uint32_t* numSignatures, + int* hasAdditionalBlocks, + uint32_t* offsetAdditionalBlocks, + uint32_t* numAdditionalBlocks) { + uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i; + + /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ + if (!hasSignatureBlock && !hasAdditionalBlocks) { + return -1; + } + + /* Skip to the start of the offset index */ + *mar_position = 0; + if (mar_buffer_seek(mar, mar_position, MAR_ID_SIZE) != 0) { + return -1; + } + + /* Read the offset to the index. */ + if (mar_read_buffer(mar, &offsetToIndex, mar_position, + sizeof(offsetToIndex)) != 0) { + return -1; + } + offsetToIndex = ntohl(offsetToIndex); + + if (numSignatures) { + /* Skip past the MAR file size field */ + if (mar_buffer_seek(mar, mar_position, sizeof(uint64_t)) != 0) { + return -1; + } + + /* Read the number of signatures field */ + if (mar_read_buffer(mar, numSignatures, mar_position, + sizeof(*numSignatures)) != 0) { + return -1; + } + *numSignatures = ntohl(*numSignatures); + } + + /* Skip to the first index entry past the index size field + We do it in 2 calls because offsetToIndex + sizeof(uint32_t) + could overflow in theory. */ + *mar_position = 0; + if (mar_buffer_seek(mar, mar_position, offsetToIndex) != 0) { + return -1; + } + + if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) { + return -1; + } + + /* Read the first offset to content field. */ + if (mar_read_buffer(mar, &offsetToContent, mar_position, + sizeof(offsetToContent)) != 0) { + return -1; + } + offsetToContent = ntohl(offsetToContent); + + /* Check if we have a new or old MAR file */ + if (hasSignatureBlock) { + if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) { + *hasSignatureBlock = 0; + } else { + *hasSignatureBlock = 1; + } + } + + /* If the caller doesn't care about the product info block + value, then just return */ + if (!hasAdditionalBlocks) { + return 0; + } + + /* Skip to the start of the signature block */ + *mar_position = 0; + if (mar_buffer_seek(mar, mar_position, SIGNATURE_BLOCK_OFFSET) != 0) { + return -1; + } + + /* Get the number of signatures */ + if (mar_read_buffer(mar, &signatureCount, mar_position, + sizeof(signatureCount)) != 0) { + return -1; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + return -1; + } + + /* Skip past the whole signature block */ + for (i = 0; i < signatureCount; i++) { + /* Skip past the signature algorithm ID */ + if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) { + return -1; + } + + /* Read the signature length and skip past the signature */ + if (mar_read_buffer(mar, &signatureLen, mar_position, sizeof(uint32_t)) != + 0) { + return -1; + } + signatureLen = ntohl(signatureLen); + if (mar_buffer_seek(mar, mar_position, signatureLen) != 0) { + return -1; + } + } + + if (*mar_position <= (size_t)INT64_MAX && + (int64_t)mar_position == (int64_t)offsetToContent) { + *hasAdditionalBlocks = 0; + } else { + if (numAdditionalBlocks) { + /* We have an additional block, so read in the number of additional blocks + and set the offset. */ + *hasAdditionalBlocks = 1; + if (mar_read_buffer(mar, numAdditionalBlocks, mar_position, + sizeof(uint32_t)) != 0) { + return -1; + } + *numAdditionalBlocks = ntohl(*numAdditionalBlocks); + if (offsetAdditionalBlocks) { + if (*mar_position > (size_t)UINT32_MAX) { + return -1; + } + *offsetAdditionalBlocks = (uint32_t)*mar_position; + } + } else if (offsetAdditionalBlocks) { + /* numAdditionalBlocks is not specified but offsetAdditionalBlocks + is, so fill it! */ + if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) { + return -1; + } + if (*mar_position > (size_t)UINT32_MAX) { + return -1; + } + *offsetAdditionalBlocks = (uint32_t)*mar_position; + } + } + + return 0; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int read_product_info_block(char* path, + struct ProductInformationBlock* infoBlock) { + int rv; + MarFile* mar; + MarReadResult result = mar_open(path, &mar); + if (result != MAR_READ_SUCCESS) { + fprintf(stderr, + "ERROR: could not open file in read_product_info_block()\n"); + return -1; + } + rv = mar_read_product_info_block(mar, infoBlock); + mar_close(mar); + return rv; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure + */ +int mar_read_product_info_block(MarFile* mar, + struct ProductInformationBlock* infoBlock) { + uint32_t offsetAdditionalBlocks, numAdditionalBlocks, additionalBlockSize, + additionalBlockID; + int hasAdditionalBlocks; + size_t mar_position = 0; + + /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and + product version < 32 bytes + 3 NULL terminator bytes. */ + char buf[MAXADDITIONALBLOCKSIZE + 1] = {'\0'}; + if (get_open_mar_file_info(mar, &mar_position, NULL, NULL, + &hasAdditionalBlocks, &offsetAdditionalBlocks, + &numAdditionalBlocks) != 0) { + return -1; + } + + /* We only have the one additional block type and only one is expected to be + in a MAR file so check if any exist and process the first found */ + if (numAdditionalBlocks > 0) { + /* Read the additional block size */ + if (mar_read_buffer(mar, &additionalBlockSize, &mar_position, + sizeof(additionalBlockSize)) != 0) { + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize) - + sizeof(additionalBlockSize) - + sizeof(additionalBlockID); + + /* Additional Block sizes should only be 96 bytes long */ + if (additionalBlockSize > MAXADDITIONALBLOCKSIZE) { + return -1; + } + + /* Read the additional block ID */ + if (mar_read_buffer(mar, &additionalBlockID, &mar_position, + sizeof(additionalBlockID)) != 0) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + const char* location; + int len; + + if (mar_read_buffer(mar, buf, &mar_position, additionalBlockSize) != 0) { + return -1; + } + + /* Extract the MAR channel name from the buffer. For now we + point to the stack allocated buffer but we strdup this + if we are within bounds of each field's max length. */ + location = buf; + len = strlen(location); + infoBlock->MARChannelID = location; + location += len + 1; + if (len >= 64) { + infoBlock->MARChannelID = NULL; + return -1; + } + + /* Extract the version from the buffer */ + len = strlen(location); + infoBlock->productVersion = location; + if (len >= 32) { + infoBlock->MARChannelID = NULL; + infoBlock->productVersion = NULL; + return -1; + } + infoBlock->MARChannelID = strdup(infoBlock->MARChannelID); + infoBlock->productVersion = strdup(infoBlock->productVersion); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (mar_buffer_seek(mar, &mar_position, additionalBlockSize) != 0) { + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + return -1; +} + +const MarItem* mar_find_item(MarFile* mar, const char* name) { + uint32_t hash; + const MarItem* item; + + if (!mar->item_table_is_valid) { + if (mar_read_index(mar)) { + return NULL; + } else { + mar->item_table_is_valid = 1; + } + } + + hash = mar_hash_name(name); + + item = mar->item_table[hash]; + while (item && strcmp(item->name, name) != 0) { + item = item->next; + } + + /* If this is the first time seeing this item's indexes, return it */ + if (mar_insert_offset(mar, item->offset, item->length) == 1) { + return item; + } else { + fprintf(stderr, "ERROR: file content collision in mar_find_item()\n"); + return NULL; + } +} + +int mar_enum_items(MarFile* mar, MarItemCallback callback, void* closure) { + MarItem* item; + int i, rv; + + if (!mar->item_table_is_valid) { + if (mar_read_index(mar)) { + return -1; + } else { + mar->item_table_is_valid = 1; + } + } + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + /* if this is the first time seeing this item's indexes, process it */ + if (mar_insert_offset(mar, item->offset, item->length) == 1) { + rv = callback(mar, item, closure); + if (rv) { + return rv; + } + } else { + fprintf(stderr, "ERROR: file content collision in mar_enum_items()\n"); + return 1; + } + item = item->next; + } + } + + return 0; +} + +int mar_read(MarFile* mar, const MarItem* item, int offset, uint8_t* buf, + int bufsize) { + int nr; + size_t mar_position = 0; + + if (offset == (int)item->length) { + return 0; + } + if (offset > (int)item->length) { + return -1; + } + + nr = item->length - offset; + if (nr > bufsize) { + nr = bufsize; + } + + // Avoid adding item->offset and offset directly, just in case of overflow. + if (mar_buffer_seek(mar, &mar_position, item->offset)) { + return -1; + } + if (mar_buffer_seek(mar, &mar_position, offset)) { + return -1; + } + + return mar_read_buffer_max(mar, buf, &mar_position, nr); +} + +/** + * Determines the MAR file information. + * + * @param path The path of the MAR file to check. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * has_additional_blocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info(const char* path, int* hasSignatureBlock, + uint32_t* numSignatures, int* hasAdditionalBlocks, + uint32_t* offsetAdditionalBlocks, + uint32_t* numAdditionalBlocks) { + int rv; + MarFile* mar; + size_t mar_position = 0; + MarReadResult result = mar_open(path, &mar); + if (result != MAR_READ_SUCCESS) { + fprintf(stderr, "ERROR: could not read file in get_mar_file_info()\n"); + return -1; + } + + rv = get_open_mar_file_info(mar, &mar_position, hasSignatureBlock, + numSignatures, hasAdditionalBlocks, + offsetAdditionalBlocks, numAdditionalBlocks); + + mar_close(mar); + return rv; +} diff --git a/modules/libmar/src/moz.build b/modules/libmar/src/moz.build new file mode 100644 index 0000000000..5c40291e92 --- /dev/null +++ b/modules/libmar/src/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + "mar.h", + "mar_cmdline.h", +] + +HOST_SOURCES += [ + "mar_create.c", + "mar_extract.c", + "mar_read.c", +] +HostLibrary("hostmar") + +# C11 for static_assert +c11_flags = ["-std=gnu11"] +if CONFIG["CC_TYPE"] == "clang-cl": + c11_flags.insert(0, "-Xclang") +HOST_CFLAGS += c11_flags + +LOCAL_INCLUDES += [ + "../../../other-licenses/nsis/Contrib/CityHash/cityhash", +] + +if CONFIG["MOZ_BUILD_APP"] != "tools/update-packaging": + Library("mar") + + UNIFIED_SOURCES += HOST_SOURCES + + CFLAGS += c11_flags + + FORCE_STATIC_LIB = True + + if CONFIG["OS_ARCH"] == "WINNT": + USE_STATIC_LIBS = True |