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