1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
3 /* vim:set ts=2 sw=2 sts=2 et cindent: */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 #include <string.h>
9 #include <stdlib.h>
10 #include <fcntl.h>
11 #ifdef XP_WIN
12 # include <windows.h>
13 #endif
14 #include "archivereader.h"
15 #include "updatererrors.h"
16 #ifdef XP_WIN
17 # include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp
18 # include "updatehelper.h"
19 #endif
20 #define XZ_USE_CRC64
21 #include "xz.h"
22
23 // These are generated at compile time based on the DER file for the channel
24 // being used
25 #ifdef MOZ_VERIFY_MAR_SIGNATURE
26 # ifdef TEST_UPDATER
27 # include "../xpcshellCert.h"
28 # elif DEP_UPDATER
29 # include "../dep1Cert.h"
30 # include "../dep2Cert.h"
31 # else
32 # include "primaryCert.h"
33 # include "secondaryCert.h"
34 # endif
35 #endif
36
37 #define UPDATER_NO_STRING_GLUE_STL
38 #include "nsVersionComparator.cpp"
39 #undef UPDATER_NO_STRING_GLUE_STL
40
41 #if defined(XP_UNIX)
42 # include <sys/types.h>
43 #elif defined(XP_WIN)
44 # include <io.h>
45 #endif
46
47 /**
48 * Performs a verification on the opened MAR file with the passed in
49 * certificate name ID and type ID.
50 *
51 * @param archive The MAR file to verify the signature on.
52 * @param certData The certificate data.
53 * @return OK on success, CERT_VERIFY_ERROR on failure.
54 */
55 template <uint32_t SIZE>
VerifyLoadedCert(MarFile * archive,const uint8_t (& certData)[SIZE])56 int VerifyLoadedCert(MarFile* archive, const uint8_t (&certData)[SIZE]) {
57 (void)archive;
58 (void)certData;
59
60 #ifdef MOZ_VERIFY_MAR_SIGNATURE
61 const uint32_t size = SIZE;
62 const uint8_t* const data = &certData[0];
63 if (mar_verify_signatures(archive, &data, &size, 1)) {
64 return CERT_VERIFY_ERROR;
65 }
66 #endif
67
68 return OK;
69 }
70
71 /**
72 * Performs a verification on the opened MAR file. Both the primary and backup
73 * keys stored are stored in the current process and at least the primary key
74 * will be tried. Success will be returned as long as one of the two
75 * signatures verify.
76 *
77 * @return OK on success
78 */
VerifySignature()79 int ArchiveReader::VerifySignature() {
80 if (!mArchive) {
81 return ARCHIVE_NOT_OPEN;
82 }
83
84 #ifndef MOZ_VERIFY_MAR_SIGNATURE
85 return OK;
86 #else
87 # ifdef TEST_UPDATER
88 int rv = VerifyLoadedCert(mArchive, xpcshellCertData);
89 # elif DEP_UPDATER
90 int rv = VerifyLoadedCert(mArchive, dep1CertData);
91 if (rv != OK) {
92 rv = VerifyLoadedCert(mArchive, dep2CertData);
93 }
94 # else
95 int rv = VerifyLoadedCert(mArchive, primaryCertData);
96 if (rv != OK) {
97 rv = VerifyLoadedCert(mArchive, secondaryCertData);
98 }
99 # endif
100 return rv;
101 #endif
102 }
103
104 /**
105 * Verifies that the MAR file matches the current product, channel, and version
106 *
107 * @param MARChannelID The MAR channel name to use, only updates from MARs
108 * with a matching MAR channel name will succeed.
109 * If an empty string is passed, no check will be done
110 * for the channel name in the product information block.
111 * If a comma separated list of values is passed then
112 * one value must match.
113 * @param appVersion The application version to use, only MARs with an
114 * application version >= to appVersion will be applied.
115 * @return OK on success
116 * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block
117 * could not be read.
118 * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR
119 * channel ID doesn't match the MAR
120 * file's MAR channel ID.
121 * VERSION_DOWNGRADE_ERROR if the application version for
122 * this updater is newer than the
123 * one in the MAR.
124 */
VerifyProductInformation(const char * MARChannelID,const char * appVersion)125 int ArchiveReader::VerifyProductInformation(const char* MARChannelID,
126 const char* appVersion) {
127 if (!mArchive) {
128 return ARCHIVE_NOT_OPEN;
129 }
130
131 ProductInformationBlock productInfoBlock;
132 int rv = mar_read_product_info_block(mArchive, &productInfoBlock);
133 if (rv != OK) {
134 return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR;
135 }
136
137 // Only check the MAR channel name if specified, it should be passed in from
138 // the update-settings.ini file.
139 if (MARChannelID && strlen(MARChannelID)) {
140 // Check for at least one match in the comma separated list of values.
141 const char* delimiter = " ,\t";
142 // Make a copy of the string in case a read only memory buffer
143 // was specified. strtok modifies the input buffer.
144 char channelCopy[512] = {0};
145 strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1);
146 char* channel = strtok(channelCopy, delimiter);
147 rv = MAR_CHANNEL_MISMATCH_ERROR;
148 while (channel) {
149 if (!strcmp(channel, productInfoBlock.MARChannelID)) {
150 rv = OK;
151 break;
152 }
153 channel = strtok(nullptr, delimiter);
154 }
155 }
156
157 if (rv == OK) {
158 /* Compare both versions to ensure we don't have a downgrade
159 -1 if appVersion is older than productInfoBlock.productVersion
160 1 if appVersion is newer than productInfoBlock.productVersion
161 0 if appVersion is the same as productInfoBlock.productVersion
162 This even works with strings like:
163 - 12.0a1 being older than 12.0a2
164 - 12.0a2 being older than 12.0b1
165 - 12.0a1 being older than 12.0
166 - 12.0 being older than 12.1a1 */
167 int versionCompareResult =
168 mozilla::CompareVersions(appVersion, productInfoBlock.productVersion);
169 if (1 == versionCompareResult) {
170 rv = VERSION_DOWNGRADE_ERROR;
171 }
172 }
173
174 free((void*)productInfoBlock.MARChannelID);
175 free((void*)productInfoBlock.productVersion);
176 return rv;
177 }
178
Open(const NS_tchar * path)179 int ArchiveReader::Open(const NS_tchar* path) {
180 if (mArchive) {
181 Close();
182 }
183
184 if (!mInBuf) {
185 mInBuf = (uint8_t*)malloc(mInBufSize);
186 if (!mInBuf) {
187 // Try again with a smaller buffer.
188 mInBufSize = 1024;
189 mInBuf = (uint8_t*)malloc(mInBufSize);
190 if (!mInBuf) {
191 return ARCHIVE_READER_MEM_ERROR;
192 }
193 }
194 }
195
196 if (!mOutBuf) {
197 mOutBuf = (uint8_t*)malloc(mOutBufSize);
198 if (!mOutBuf) {
199 // Try again with a smaller buffer.
200 mOutBufSize = 1024;
201 mOutBuf = (uint8_t*)malloc(mOutBufSize);
202 if (!mOutBuf) {
203 return ARCHIVE_READER_MEM_ERROR;
204 }
205 }
206 }
207
208 #ifdef XP_WIN
209 mArchive = mar_wopen(path);
210 #else
211 mArchive = mar_open(path);
212 #endif
213 if (!mArchive) {
214 return READ_ERROR;
215 }
216
217 xz_crc32_init();
218 xz_crc64_init();
219
220 return OK;
221 }
222
Close()223 void ArchiveReader::Close() {
224 if (mArchive) {
225 mar_close(mArchive);
226 mArchive = nullptr;
227 }
228
229 if (mInBuf) {
230 free(mInBuf);
231 mInBuf = nullptr;
232 }
233
234 if (mOutBuf) {
235 free(mOutBuf);
236 mOutBuf = nullptr;
237 }
238 }
239
ExtractFile(const char * name,const NS_tchar * dest)240 int ArchiveReader::ExtractFile(const char* name, const NS_tchar* dest) {
241 const MarItem* item = mar_find_item(mArchive, name);
242 if (!item) {
243 return READ_ERROR;
244 }
245
246 #ifdef XP_WIN
247 FILE* fp = _wfopen(dest, L"wb+");
248 #else
249 int fd = creat(dest, item->flags);
250 if (fd == -1) {
251 return WRITE_ERROR;
252 }
253
254 FILE* fp = fdopen(fd, "wb");
255 #endif
256 if (!fp) {
257 return WRITE_ERROR;
258 }
259
260 int rv = ExtractItemToStream(item, fp);
261
262 fclose(fp);
263 return rv;
264 }
265
ExtractFileToStream(const char * name,FILE * fp)266 int ArchiveReader::ExtractFileToStream(const char* name, FILE* fp) {
267 const MarItem* item = mar_find_item(mArchive, name);
268 if (!item) {
269 return READ_ERROR;
270 }
271
272 return ExtractItemToStream(item, fp);
273 }
274
ExtractItemToStream(const MarItem * item,FILE * fp)275 int ArchiveReader::ExtractItemToStream(const MarItem* item, FILE* fp) {
276 /* decompress the data chunk by chunk */
277
278 int offset, inlen, ret = OK;
279 struct xz_buf strm = {0};
280 enum xz_ret xz_rv = XZ_OK;
281
282 struct xz_dec* dec = xz_dec_init(XZ_DYNALLOC, 64 * 1024 * 1024);
283 if (!dec) {
284 return UNEXPECTED_XZ_ERROR;
285 }
286
287 strm.in = mInBuf;
288 strm.in_pos = 0;
289 strm.in_size = 0;
290 strm.out = mOutBuf;
291 strm.out_pos = 0;
292 strm.out_size = mOutBufSize;
293
294 offset = 0;
295 for (;;) {
296 if (!item->length) {
297 ret = UNEXPECTED_MAR_ERROR;
298 break;
299 }
300
301 if (offset < (int)item->length && strm.in_pos == strm.in_size) {
302 inlen = mar_read(mArchive, item, offset, mInBuf, mInBufSize);
303 if (inlen <= 0) {
304 ret = READ_ERROR;
305 break;
306 }
307 offset += inlen;
308 strm.in_size = inlen;
309 strm.in_pos = 0;
310 }
311
312 xz_rv = xz_dec_run(dec, &strm);
313
314 if (strm.out_pos == mOutBufSize) {
315 if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) {
316 ret = WRITE_ERROR_EXTRACT;
317 break;
318 }
319
320 strm.out_pos = 0;
321 }
322
323 if (xz_rv == XZ_OK) {
324 // There is still more data to decompress.
325 continue;
326 }
327
328 // The return value of xz_dec_run is not XZ_OK and if it isn't XZ_STREAM_END
329 // an error has occured.
330 if (xz_rv != XZ_STREAM_END) {
331 ret = UNEXPECTED_XZ_ERROR;
332 break;
333 }
334
335 // Write out the remainder of the decompressed data. In the case of
336 // strm.out_pos == 0 this is needed to create empty files included in the
337 // mar file.
338 if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) {
339 ret = WRITE_ERROR_EXTRACT;
340 }
341 break;
342 }
343
344 xz_dec_end(dec);
345 return ret;
346 }
347