1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/update/updater/archivereader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,358 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <string.h> 1.11 +#include <stdlib.h> 1.12 +#include <fcntl.h> 1.13 +#include "bzlib.h" 1.14 +#include "archivereader.h" 1.15 +#include "errors.h" 1.16 +#ifdef XP_WIN 1.17 +#include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp 1.18 +#include "updatehelper.h" 1.19 +#endif 1.20 + 1.21 +#define UPDATER_NO_STRING_GLUE_STL 1.22 +#include "nsVersionComparator.cpp" 1.23 +#undef UPDATER_NO_STRING_GLUE_STL 1.24 + 1.25 +#if defined(XP_UNIX) 1.26 +# include <sys/types.h> 1.27 +#elif defined(XP_WIN) 1.28 +# include <io.h> 1.29 +#endif 1.30 + 1.31 +static int inbuf_size = 262144; 1.32 +static int outbuf_size = 262144; 1.33 +static char *inbuf = nullptr; 1.34 +static char *outbuf = nullptr; 1.35 + 1.36 +#ifdef XP_WIN 1.37 +#include "resource.h" 1.38 + 1.39 +/** 1.40 + * Obtains the data of the specified resource name and type. 1.41 + * 1.42 + * @param name The name ID of the resource 1.43 + * @param type The type ID of the resource 1.44 + * @param data Out parameter which sets the pointer to a buffer containing 1.45 + * the needed data. 1.46 + * @param size Out parameter which sets the size of the returned data buffer 1.47 + * @return TRUE on success 1.48 +*/ 1.49 +BOOL 1.50 +LoadFileInResource(int name, int type, const uint8_t *&data, uint32_t& size) 1.51 +{ 1.52 + HMODULE handle = GetModuleHandle(nullptr); 1.53 + if (!handle) { 1.54 + return FALSE; 1.55 + } 1.56 + 1.57 + HRSRC resourceInfoBlockHandle = FindResource(handle, 1.58 + MAKEINTRESOURCE(name), 1.59 + MAKEINTRESOURCE(type)); 1.60 + if (!resourceInfoBlockHandle) { 1.61 + FreeLibrary(handle); 1.62 + return FALSE; 1.63 + } 1.64 + 1.65 + HGLOBAL resourceHandle = LoadResource(handle, resourceInfoBlockHandle); 1.66 + if (!resourceHandle) { 1.67 + FreeLibrary(handle); 1.68 + return FALSE; 1.69 + } 1.70 + 1.71 + size = SizeofResource(handle, resourceInfoBlockHandle); 1.72 + data = static_cast<const uint8_t*>(::LockResource(resourceHandle)); 1.73 + FreeLibrary(handle); 1.74 + return TRUE; 1.75 +} 1.76 + 1.77 +/** 1.78 + * Performs a verification on the opened MAR file with the passed in 1.79 + * certificate name ID and type ID. 1.80 + * 1.81 + * @param archive The MAR file to verify the signature on 1.82 + * @param name The name ID of the resource 1.83 + * @param type THe type ID of the resource 1.84 + * @return OK on success, CERT_LOAD_ERROR or CERT_VERIFY_ERROR on failure. 1.85 +*/ 1.86 +int 1.87 +VerifyLoadedCert(MarFile *archive, int name, int type) 1.88 +{ 1.89 + uint32_t size = 0; 1.90 + const uint8_t *data = nullptr; 1.91 + if (!LoadFileInResource(name, type, data, size) || !data || !size) { 1.92 + return CERT_LOAD_ERROR; 1.93 + } 1.94 + 1.95 + if (mar_verify_signaturesW(archive, &data, &size, 1)) { 1.96 + return CERT_VERIFY_ERROR; 1.97 + } 1.98 + 1.99 + return OK; 1.100 +} 1.101 +#endif 1.102 + 1.103 + 1.104 +/** 1.105 + * Performs a verification on the opened MAR file. Both the primary and backup 1.106 + * keys stored are stored in the current process and at least the primary key 1.107 + * will be tried. Success will be returned as long as one of the two 1.108 + * signatures verify. 1.109 + * 1.110 + * @return OK on success 1.111 +*/ 1.112 +int 1.113 +ArchiveReader::VerifySignature() 1.114 +{ 1.115 + if (!mArchive) { 1.116 + return ARCHIVE_NOT_OPEN; 1.117 + } 1.118 + 1.119 +#ifdef XP_WIN 1.120 + // If the fallback key exists we're running an XPCShell test and we should 1.121 + // use the XPCShell specific cert for the signed MAR. 1.122 + int rv; 1.123 + if (DoesFallbackKeyExist()) { 1.124 + rv = VerifyLoadedCert(mArchive, IDR_XPCSHELL_CERT, TYPE_CERT); 1.125 + } else { 1.126 + rv = VerifyLoadedCert(mArchive, IDR_PRIMARY_CERT, TYPE_CERT); 1.127 + if (rv != OK) { 1.128 + rv = VerifyLoadedCert(mArchive, IDR_BACKUP_CERT, TYPE_CERT); 1.129 + } 1.130 + } 1.131 + return rv; 1.132 +#else 1.133 + return OK; 1.134 +#endif 1.135 +} 1.136 + 1.137 +/** 1.138 + * Verifies that the MAR file matches the current product, channel, and version 1.139 + * 1.140 + * @param MARChannelID The MAR channel name to use, only updates from MARs 1.141 + * with a matching MAR channel name will succeed. 1.142 + * If an empty string is passed, no check will be done 1.143 + * for the channel name in the product information block. 1.144 + * If a comma separated list of values is passed then 1.145 + * one value must match. 1.146 + * @param appVersion The application version to use, only MARs with an 1.147 + * application version >= to appVersion will be applied. 1.148 + * @return OK on success 1.149 + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block 1.150 + * could not be read. 1.151 + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR 1.152 + * channel ID doesn't match the MAR 1.153 + * file's MAR channel ID. 1.154 + * VERSION_DOWNGRADE_ERROR if the application version for 1.155 + * this updater is newer than the 1.156 + * one in the MAR. 1.157 + */ 1.158 +int 1.159 +ArchiveReader::VerifyProductInformation(const char *MARChannelID, 1.160 + const char *appVersion) 1.161 +{ 1.162 + if (!mArchive) { 1.163 + return ARCHIVE_NOT_OPEN; 1.164 + } 1.165 + 1.166 + ProductInformationBlock productInfoBlock; 1.167 + int rv = mar_read_product_info_block(mArchive, 1.168 + &productInfoBlock); 1.169 + if (rv != OK) { 1.170 + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; 1.171 + } 1.172 + 1.173 + // Only check the MAR channel name if specified, it should be passed in from 1.174 + // the update-settings.ini file. 1.175 + if (MARChannelID && strlen(MARChannelID)) { 1.176 + // Check for at least one match in the comma separated list of values. 1.177 + const char *delimiter = " ,\t"; 1.178 + // Make a copy of the string in case a read only memory buffer 1.179 + // was specified. strtok modifies the input buffer. 1.180 + char channelCopy[512] = { 0 }; 1.181 + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); 1.182 + char *channel = strtok(channelCopy, delimiter); 1.183 + rv = MAR_CHANNEL_MISMATCH_ERROR; 1.184 + while(channel) { 1.185 + if (!strcmp(channel, productInfoBlock.MARChannelID)) { 1.186 + rv = OK; 1.187 + break; 1.188 + } 1.189 + channel = strtok(nullptr, delimiter); 1.190 + } 1.191 + } 1.192 + 1.193 + if (rv == OK) { 1.194 + /* Compare both versions to ensure we don't have a downgrade 1.195 + -1 if appVersion is older than productInfoBlock.productVersion 1.196 + 1 if appVersion is newer than productInfoBlock.productVersion 1.197 + 0 if appVersion is the same as productInfoBlock.productVersion 1.198 + This even works with strings like: 1.199 + - 12.0a1 being older than 12.0a2 1.200 + - 12.0a2 being older than 12.0b1 1.201 + - 12.0a1 being older than 12.0 1.202 + - 12.0 being older than 12.1a1 */ 1.203 + int versionCompareResult = 1.204 + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); 1.205 + if (1 == versionCompareResult) { 1.206 + rv = VERSION_DOWNGRADE_ERROR; 1.207 + } 1.208 + } 1.209 + 1.210 + free((void *)productInfoBlock.MARChannelID); 1.211 + free((void *)productInfoBlock.productVersion); 1.212 + return rv; 1.213 +} 1.214 + 1.215 +int 1.216 +ArchiveReader::Open(const NS_tchar *path) 1.217 +{ 1.218 + if (mArchive) 1.219 + Close(); 1.220 + 1.221 + if (!inbuf) { 1.222 + inbuf = (char *)malloc(inbuf_size); 1.223 + if (!inbuf) { 1.224 + // Try again with a smaller buffer. 1.225 + inbuf_size = 1024; 1.226 + inbuf = (char *)malloc(inbuf_size); 1.227 + if (!inbuf) 1.228 + return ARCHIVE_READER_MEM_ERROR; 1.229 + } 1.230 + } 1.231 + 1.232 + if (!outbuf) { 1.233 + outbuf = (char *)malloc(outbuf_size); 1.234 + if (!outbuf) { 1.235 + // Try again with a smaller buffer. 1.236 + outbuf_size = 1024; 1.237 + outbuf = (char *)malloc(outbuf_size); 1.238 + if (!outbuf) 1.239 + return ARCHIVE_READER_MEM_ERROR; 1.240 + } 1.241 + } 1.242 + 1.243 +#ifdef XP_WIN 1.244 + mArchive = mar_wopen(path); 1.245 +#else 1.246 + mArchive = mar_open(path); 1.247 +#endif 1.248 + if (!mArchive) 1.249 + return READ_ERROR; 1.250 + 1.251 + return OK; 1.252 +} 1.253 + 1.254 +void 1.255 +ArchiveReader::Close() 1.256 +{ 1.257 + if (mArchive) { 1.258 + mar_close(mArchive); 1.259 + mArchive = nullptr; 1.260 + } 1.261 + 1.262 + if (inbuf) { 1.263 + free(inbuf); 1.264 + inbuf = nullptr; 1.265 + } 1.266 + 1.267 + if (outbuf) { 1.268 + free(outbuf); 1.269 + outbuf = nullptr; 1.270 + } 1.271 +} 1.272 + 1.273 +int 1.274 +ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) 1.275 +{ 1.276 + const MarItem *item = mar_find_item(mArchive, name); 1.277 + if (!item) 1.278 + return READ_ERROR; 1.279 + 1.280 +#ifdef XP_WIN 1.281 + FILE* fp = _wfopen(dest, L"wb+"); 1.282 +#else 1.283 + int fd = creat(dest, item->flags); 1.284 + if (fd == -1) 1.285 + return WRITE_ERROR; 1.286 + 1.287 + FILE *fp = fdopen(fd, "wb"); 1.288 +#endif 1.289 + if (!fp) 1.290 + return WRITE_ERROR; 1.291 + 1.292 + int rv = ExtractItemToStream(item, fp); 1.293 + 1.294 + fclose(fp); 1.295 + return rv; 1.296 +} 1.297 + 1.298 +int 1.299 +ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) 1.300 +{ 1.301 + const MarItem *item = mar_find_item(mArchive, name); 1.302 + if (!item) 1.303 + return READ_ERROR; 1.304 + 1.305 + return ExtractItemToStream(item, fp); 1.306 +} 1.307 + 1.308 +int 1.309 +ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) 1.310 +{ 1.311 + /* decompress the data chunk by chunk */ 1.312 + 1.313 + bz_stream strm; 1.314 + int offset, inlen, outlen, ret = OK; 1.315 + 1.316 + memset(&strm, 0, sizeof(strm)); 1.317 + if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) 1.318 + return UNEXPECTED_BZIP_ERROR; 1.319 + 1.320 + offset = 0; 1.321 + for (;;) { 1.322 + if (!item->length) { 1.323 + ret = UNEXPECTED_MAR_ERROR; 1.324 + break; 1.325 + } 1.326 + 1.327 + if (offset < (int) item->length && strm.avail_in == 0) { 1.328 + inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); 1.329 + if (inlen <= 0) 1.330 + return READ_ERROR; 1.331 + offset += inlen; 1.332 + strm.next_in = inbuf; 1.333 + strm.avail_in = inlen; 1.334 + } 1.335 + 1.336 + strm.next_out = outbuf; 1.337 + strm.avail_out = outbuf_size; 1.338 + 1.339 + ret = BZ2_bzDecompress(&strm); 1.340 + if (ret != BZ_OK && ret != BZ_STREAM_END) { 1.341 + ret = UNEXPECTED_BZIP_ERROR; 1.342 + break; 1.343 + } 1.344 + 1.345 + outlen = outbuf_size - strm.avail_out; 1.346 + if (outlen) { 1.347 + if (fwrite(outbuf, outlen, 1, fp) != 1) { 1.348 + ret = WRITE_ERROR; 1.349 + break; 1.350 + } 1.351 + } 1.352 + 1.353 + if (ret == BZ_STREAM_END) { 1.354 + ret = OK; 1.355 + break; 1.356 + } 1.357 + } 1.358 + 1.359 + BZ2_bzDecompressEnd(&strm); 1.360 + return ret; 1.361 +}