diff options
Diffstat (limited to 'toolkit/mozapps/update/updater/archivereader.cpp')
-rw-r--r-- | toolkit/mozapps/update/updater/archivereader.cpp | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/updater/archivereader.cpp b/toolkit/mozapps/update/updater/archivereader.cpp new file mode 100644 index 0000000000..e166310b13 --- /dev/null +++ b/toolkit/mozapps/update/updater/archivereader.cpp @@ -0,0 +1,346 @@ +/* -*- 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 <string.h> +#include <stdlib.h> +#include <fcntl.h> +#ifdef XP_WIN +# include <windows.h> +#endif +#include "archivereader.h" +#include "updatererrors.h" +#ifdef XP_WIN +# include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp +# include "updatehelper.h" +#endif +#define XZ_USE_CRC64 +#include "xz.h" + +// These are generated at compile time based on the DER file for the channel +// being used +#ifdef MOZ_VERIFY_MAR_SIGNATURE +# ifdef TEST_UPDATER +# include "../xpcshellCert.h" +# elif DEP_UPDATER +# include "../dep1Cert.h" +# include "../dep2Cert.h" +# else +# include "primaryCert.h" +# include "secondaryCert.h" +# endif +#endif + +#define UPDATER_NO_STRING_GLUE_STL +#include "nsVersionComparator.cpp" +#undef UPDATER_NO_STRING_GLUE_STL + +#if defined(XP_UNIX) +# include <sys/types.h> +#elif defined(XP_WIN) +# include <io.h> +#endif + +/** + * Performs a verification on the opened MAR file with the passed in + * certificate name ID and type ID. + * + * @param archive The MAR file to verify the signature on. + * @param certData The certificate data. + * @return OK on success, CERT_VERIFY_ERROR on failure. + */ +template <uint32_t SIZE> +int VerifyLoadedCert(MarFile* archive, const uint8_t (&certData)[SIZE]) { + (void)archive; + (void)certData; + +#ifdef MOZ_VERIFY_MAR_SIGNATURE + const uint32_t size = SIZE; + const uint8_t* const data = &certData[0]; + if (mar_verify_signatures(archive, &data, &size, 1)) { + return CERT_VERIFY_ERROR; + } +#endif + + return OK; +} + +/** + * Performs a verification on the opened MAR file. Both the primary and backup + * keys stored are stored in the current process and at least the primary key + * will be tried. Success will be returned as long as one of the two + * signatures verify. + * + * @return OK on success + */ +int ArchiveReader::VerifySignature() { + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + +#ifndef MOZ_VERIFY_MAR_SIGNATURE + return OK; +#else +# ifdef TEST_UPDATER + int rv = VerifyLoadedCert(mArchive, xpcshellCertData); +# elif DEP_UPDATER + int rv = VerifyLoadedCert(mArchive, dep1CertData); + if (rv != OK) { + rv = VerifyLoadedCert(mArchive, dep2CertData); + } +# else + int rv = VerifyLoadedCert(mArchive, primaryCertData); + if (rv != OK) { + rv = VerifyLoadedCert(mArchive, secondaryCertData); + } +# endif + return rv; +#endif +} + +/** + * Verifies that the MAR file matches the current product, channel, and version + * + * @param MARChannelID The MAR channel name to use, only updates from MARs + * with a matching MAR channel name will succeed. + * If an empty string is passed, no check will be done + * for the channel name in the product information block. + * If a comma separated list of values is passed then + * one value must match. + * @param appVersion The application version to use, only MARs with an + * application version >= to appVersion will be applied. + * @return OK on success + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block + * could not be read. + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR + * channel ID doesn't match the MAR + * file's MAR channel ID. + * VERSION_DOWNGRADE_ERROR if the application version for + * this updater is newer than the + * one in the MAR. + */ +int ArchiveReader::VerifyProductInformation(const char* MARChannelID, + const char* appVersion) { + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + + ProductInformationBlock productInfoBlock; + int rv = mar_read_product_info_block(mArchive, &productInfoBlock); + if (rv != OK) { + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; + } + + // Only check the MAR channel name if specified, it should be passed in from + // the update-settings.ini file. + if (MARChannelID && strlen(MARChannelID)) { + // Check for at least one match in the comma separated list of values. + const char* delimiter = " ,\t"; + // Make a copy of the string in case a read only memory buffer + // was specified. strtok modifies the input buffer. + char channelCopy[512] = {0}; + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); + char* channel = strtok(channelCopy, delimiter); + rv = MAR_CHANNEL_MISMATCH_ERROR; + while (channel) { + if (!strcmp(channel, productInfoBlock.MARChannelID)) { + rv = OK; + break; + } + channel = strtok(nullptr, delimiter); + } + } + + if (rv == OK) { + /* Compare both versions to ensure we don't have a downgrade + -1 if appVersion is older than productInfoBlock.productVersion + 1 if appVersion is newer than productInfoBlock.productVersion + 0 if appVersion is the same as productInfoBlock.productVersion + This even works with strings like: + - 12.0a1 being older than 12.0a2 + - 12.0a2 being older than 12.0b1 + - 12.0a1 being older than 12.0 + - 12.0 being older than 12.1a1 */ + int versionCompareResult = + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); + if (1 == versionCompareResult) { + rv = VERSION_DOWNGRADE_ERROR; + } + } + + free((void*)productInfoBlock.MARChannelID); + free((void*)productInfoBlock.productVersion); + return rv; +} + +int ArchiveReader::Open(const NS_tchar* path) { + if (mArchive) { + Close(); + } + + if (!mInBuf) { + mInBuf = (uint8_t*)malloc(mInBufSize); + if (!mInBuf) { + // Try again with a smaller buffer. + mInBufSize = 1024; + mInBuf = (uint8_t*)malloc(mInBufSize); + if (!mInBuf) { + return ARCHIVE_READER_MEM_ERROR; + } + } + } + + if (!mOutBuf) { + mOutBuf = (uint8_t*)malloc(mOutBufSize); + if (!mOutBuf) { + // Try again with a smaller buffer. + mOutBufSize = 1024; + mOutBuf = (uint8_t*)malloc(mOutBufSize); + if (!mOutBuf) { + return ARCHIVE_READER_MEM_ERROR; + } + } + } + +#ifdef XP_WIN + mArchive = mar_wopen(path); +#else + mArchive = mar_open(path); +#endif + if (!mArchive) { + return READ_ERROR; + } + + xz_crc32_init(); + xz_crc64_init(); + + return OK; +} + +void ArchiveReader::Close() { + if (mArchive) { + mar_close(mArchive); + mArchive = nullptr; + } + + if (mInBuf) { + free(mInBuf); + mInBuf = nullptr; + } + + if (mOutBuf) { + free(mOutBuf); + mOutBuf = nullptr; + } +} + +int ArchiveReader::ExtractFile(const char* name, const NS_tchar* dest) { + const MarItem* item = mar_find_item(mArchive, name); + if (!item) { + return READ_ERROR; + } + +#ifdef XP_WIN + FILE* fp = _wfopen(dest, L"wb+"); +#else + int fd = creat(dest, item->flags); + if (fd == -1) { + return WRITE_ERROR; + } + + FILE* fp = fdopen(fd, "wb"); +#endif + if (!fp) { + return WRITE_ERROR; + } + + int rv = ExtractItemToStream(item, fp); + + fclose(fp); + return rv; +} + +int ArchiveReader::ExtractFileToStream(const char* name, FILE* fp) { + const MarItem* item = mar_find_item(mArchive, name); + if (!item) { + return READ_ERROR; + } + + return ExtractItemToStream(item, fp); +} + +int ArchiveReader::ExtractItemToStream(const MarItem* item, FILE* fp) { + /* decompress the data chunk by chunk */ + + int offset, inlen, ret = OK; + struct xz_buf strm = {0}; + enum xz_ret xz_rv = XZ_OK; + + struct xz_dec* dec = xz_dec_init(XZ_DYNALLOC, 64 * 1024 * 1024); + if (!dec) { + return UNEXPECTED_XZ_ERROR; + } + + strm.in = mInBuf; + strm.in_pos = 0; + strm.in_size = 0; + strm.out = mOutBuf; + strm.out_pos = 0; + strm.out_size = mOutBufSize; + + offset = 0; + for (;;) { + if (!item->length) { + ret = UNEXPECTED_MAR_ERROR; + break; + } + + if (offset < (int)item->length && strm.in_pos == strm.in_size) { + inlen = mar_read(mArchive, item, offset, mInBuf, mInBufSize); + if (inlen <= 0) { + ret = READ_ERROR; + break; + } + offset += inlen; + strm.in_size = inlen; + strm.in_pos = 0; + } + + xz_rv = xz_dec_run(dec, &strm); + + if (strm.out_pos == mOutBufSize) { + if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) { + ret = WRITE_ERROR_EXTRACT; + break; + } + + strm.out_pos = 0; + } + + if (xz_rv == XZ_OK) { + // There is still more data to decompress. + continue; + } + + // The return value of xz_dec_run is not XZ_OK and if it isn't XZ_STREAM_END + // an error has occured. + if (xz_rv != XZ_STREAM_END) { + ret = UNEXPECTED_XZ_ERROR; + break; + } + + // Write out the remainder of the decompressed data. In the case of + // strm.out_pos == 0 this is needed to create empty files included in the + // mar file. + if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) { + ret = WRITE_ERROR_EXTRACT; + } + break; + } + + xz_dec_end(dec); + return ret; +} |