1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <string.h>
8 #include <stdlib.h>
9 #include <fcntl.h>
10 #include "bzlib.h"
11 #include "archivereader.h"
12 #include "errors.h"
13 #ifdef _WIN32
14 #include "updatehelper.h"
15 #endif
16 
17 // These are generated at compile time based on the DER file for the channel
18 // being used
19 #ifdef VERIFY_MAR_SIGNATURE
20 #ifdef TEST_UPDATER
21 #include "../xpcshellCert.h"
22 #else
23 #include "onlineupdate/primaryCert.h"
24 #include "onlineupdate/secondaryCert.h"
25 #endif
26 #endif
27 
28 #if defined(_WIN32)
29 #define UPDATER_NO_STRING_GLUE_STL
30 #endif
31 #include "nsVersionComparator.h"
32 #undef UPDATER_NO_STRING_GLUE_STL
33 
34 #if defined(UNIX)
35 # include <sys/types.h>
36 #elif defined(_WIN32)
37 # include <io.h>
38 #endif
39 
40 static int inbuf_size  = 262144;
41 static int outbuf_size = 262144;
42 static char *inbuf  = nullptr;
43 static char *outbuf = nullptr;
44 
45 /**
46  * Performs a verification on the opened MAR file with the passed in
47  * certificate name ID and type ID.
48  *
49  * @param  archive   The MAR file to verify the signature on.
50  * @param  certData  The certificate data.
51  * @return OK on success, CERT_VERIFY_ERROR on failure.
52 */
53 template<uint32_t SIZE>
54 int
VerifyLoadedCert(MarFile * archive,const uint8_t (& certData)[SIZE])55 VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE])
56 {
57     (void)archive;
58     (void)certData;
59 
60 #ifdef 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     {
65         return CERT_VERIFY_ERROR;
66     }
67 #endif
68 
69     return OK;
70 }
71 
72 /**
73  * Performs a verification on the opened MAR file.  Both the primary and backup
74  * keys stored are stored in the current process and at least the primary key
75  * will be tried.  Success will be returned as long as one of the two
76  * signatures verify.
77  *
78  * @return OK on success
79 */
80 int
VerifySignature()81 ArchiveReader::VerifySignature()
82 {
83     if (!mArchive)
84     {
85         return ARCHIVE_NOT_OPEN;
86     }
87 
88 #ifndef VERIFY_MAR_SIGNATURE
89     return OK;
90 #else
91 #ifdef TEST_UPDATER
92     int rv = VerifyLoadedCert(mArchive, xpcshellCertData);
93 #else
94     int rv = VerifyLoadedCert(mArchive, primaryCertData);
95     if (rv != OK)
96     {
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  */
125 int
VerifyProductInformation(const char * MARChannelID,const char * appVersion)126 ArchiveReader::VerifyProductInformation(const char *MARChannelID,
127                                         const char *appVersion)
128 {
129     if (!mArchive)
130     {
131         return ARCHIVE_NOT_OPEN;
132     }
133 
134     ProductInformationBlock productInfoBlock;
135     int rv = mar_read_product_info_block(mArchive,
136                                          &productInfoBlock);
137     if (rv != OK)
138     {
139         return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR;
140     }
141 
142     // Only check the MAR channel name if specified, it should be passed in from
143     // the update-settings.ini file.
144     if (MARChannelID && strlen(MARChannelID))
145     {
146         // Check for at least one match in the comma separated list of values.
147         const char *delimiter = " ,\t";
148         // Make a copy of the string in case a read only memory buffer
149         // was specified.  strtok modifies the input buffer.
150         char channelCopy[512] = { 0 };
151         strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1);
152         char *channel = strtok(channelCopy, delimiter);
153         rv = MAR_CHANNEL_MISMATCH_ERROR;
154         while (channel)
155         {
156             if (!strcmp(channel, productInfoBlock.MARChannelID))
157             {
158                 rv = OK;
159                 break;
160             }
161             channel = strtok(nullptr, delimiter);
162         }
163     }
164 
165     if (rv == OK)
166     {
167         /* Compare both versions to ensure we don't have a downgrade
168             -1 if appVersion is older than productInfoBlock.productVersion
169             1 if appVersion is newer than productInfoBlock.productVersion
170             0 if appVersion is the same as productInfoBlock.productVersion
171            This even works with strings like:
172             - 12.0a1 being older than 12.0a2
173             - 12.0a2 being older than 12.0b1
174             - 12.0a1 being older than 12.0
175             - 12.0 being older than 12.1a1 */
176         int versionCompareResult =
177             mozilla::CompareVersions(appVersion, productInfoBlock.productVersion);
178         if (1 == versionCompareResult)
179         {
180             rv = VERSION_DOWNGRADE_ERROR;
181         }
182     }
183 
184     free((void *)productInfoBlock.MARChannelID);
185     free((void *)productInfoBlock.productVersion);
186     return rv;
187 }
188 
189 int
Open(const NS_tchar * path)190 ArchiveReader::Open(const NS_tchar *path)
191 {
192     if (mArchive)
193         Close();
194 
195     if (!inbuf)
196     {
197         inbuf = (char *)malloc(inbuf_size);
198         if (!inbuf)
199         {
200             // Try again with a smaller buffer.
201             inbuf_size = 1024;
202             inbuf = (char *)malloc(inbuf_size);
203             if (!inbuf)
204                 return ARCHIVE_READER_MEM_ERROR;
205         }
206     }
207 
208     if (!outbuf)
209     {
210         outbuf = (char *)malloc(outbuf_size);
211         if (!outbuf)
212         {
213             // Try again with a smaller buffer.
214             outbuf_size = 1024;
215             outbuf = (char *)malloc(outbuf_size);
216             if (!outbuf)
217                 return ARCHIVE_READER_MEM_ERROR;
218         }
219     }
220 
221 #ifdef _WIN32
222     mArchive = mar_wopen(path);
223 #else
224     mArchive = mar_open(path);
225 #endif
226     if (!mArchive)
227         return READ_ERROR;
228 
229     return OK;
230 }
231 
232 void
Close()233 ArchiveReader::Close()
234 {
235     if (mArchive)
236     {
237         mar_close(mArchive);
238         mArchive = nullptr;
239     }
240 
241     if (inbuf)
242     {
243         free(inbuf);
244         inbuf = nullptr;
245     }
246 
247     if (outbuf)
248     {
249         free(outbuf);
250         outbuf = nullptr;
251     }
252 }
253 
254 int
ExtractFile(const char * name,const NS_tchar * dest)255 ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest)
256 {
257     const MarItem *item = mar_find_item(mArchive, name);
258     if (!item)
259         return READ_ERROR;
260 
261 #ifdef _WIN32
262     FILE* fp = _wfopen(dest, L"wb+");
263 #else
264     int fd = creat(dest, item->flags);
265     if (fd == -1)
266         return WRITE_ERROR;
267 
268     FILE *fp = fdopen(fd, "wb");
269 #endif
270     if (!fp)
271         return WRITE_ERROR;
272 
273     int rv = ExtractItemToStream(item, fp);
274 
275     fclose(fp);
276     return rv;
277 }
278 
279 int
ExtractFileToStream(const char * name,FILE * fp)280 ArchiveReader::ExtractFileToStream(const char *name, FILE *fp)
281 {
282     const MarItem *item = mar_find_item(mArchive, name);
283     if (!item)
284         return READ_ERROR;
285 
286     return ExtractItemToStream(item, fp);
287 }
288 
289 int
ExtractItemToStream(const MarItem * item,FILE * fp)290 ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp)
291 {
292     /* decompress the data chunk by chunk */
293 
294     bz_stream strm;
295     int offset, inlen, ret = OK;
296 
297     memset(&strm, 0, sizeof(strm));
298     if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK)
299         return UNEXPECTED_BZIP_ERROR;
300 
301     offset = 0;
302     for (;;)
303     {
304         if (!item->length)
305         {
306             ret = UNEXPECTED_MAR_ERROR;
307             break;
308         }
309 
310         if (offset < (int) item->length && strm.avail_in == 0)
311         {
312             inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size);
313             if (inlen <= 0)
314                 return READ_ERROR;
315             offset += inlen;
316             strm.next_in = inbuf;
317             strm.avail_in = inlen;
318         }
319 
320         strm.next_out = outbuf;
321         strm.avail_out = outbuf_size;
322 
323         ret = BZ2_bzDecompress(&strm);
324         if (ret != BZ_OK && ret != BZ_STREAM_END)
325         {
326             ret = UNEXPECTED_BZIP_ERROR;
327             break;
328         }
329 
330         int outlen = outbuf_size - strm.avail_out;
331         if (outlen)
332         {
333             if (fwrite(outbuf, outlen, 1, fp) != 1)
334             {
335                 ret = WRITE_ERROR_EXTRACT;
336                 break;
337             }
338         }
339 
340         if (ret == BZ_STREAM_END)
341         {
342             ret = OK;
343             break;
344         }
345     }
346 
347     BZ2_bzDecompressEnd(&strm);
348     return ret;
349 }
350