Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include <string.h>
6 #include "nsJARInputStream.h"
7 #include "nsJAR.h"
8 #include "nsIFile.h"
9 #include "nsIConsoleService.h"
10 #include "nsICryptoHash.h"
11 #include "prprf.h"
12 #include "mozilla/Omnijar.h"
14 #ifdef XP_UNIX
15 #include <sys/stat.h>
16 #elif defined (XP_WIN)
17 #include <io.h>
18 #endif
20 using namespace mozilla;
22 //----------------------------------------------
23 // nsJARManifestItem declaration
24 //----------------------------------------------
25 /*
26 * nsJARManifestItem contains meta-information pertaining
27 * to an individual JAR entry, taken from the
28 * META-INF/MANIFEST.MF and META-INF/ *.SF files.
29 * This is security-critical information, defined here so it is not
30 * accessible from anywhere else.
31 */
32 typedef enum
33 {
34 JAR_INVALID = 1,
35 JAR_INTERNAL = 2,
36 JAR_EXTERNAL = 3
37 } JARManifestItemType;
39 class nsJARManifestItem
40 {
41 public:
42 JARManifestItemType mType;
44 // True if the second step of verification (VerifyEntry)
45 // has taken place:
46 bool entryVerified;
48 // Not signed, valid, or failure code
49 int16_t status;
51 // Internal storage of digests
52 nsCString calculatedSectionDigest;
53 nsCString storedEntryDigest;
55 nsJARManifestItem();
56 virtual ~nsJARManifestItem();
57 };
59 //-------------------------------------------------
60 // nsJARManifestItem constructors and destructor
61 //-------------------------------------------------
62 nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
63 entryVerified(false),
64 status(JAR_NOT_SIGNED)
65 {
66 }
68 nsJARManifestItem::~nsJARManifestItem()
69 {
70 }
72 //----------------------------------------------
73 // nsJAR constructor/destructor
74 //----------------------------------------------
76 // The following initialization makes a guess of 10 entries per jarfile.
77 nsJAR::nsJAR(): mZip(new nsZipArchive()),
78 mManifestData(10),
79 mParsedManifest(false),
80 mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
81 mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
82 mCache(nullptr),
83 mLock("nsJAR::mLock"),
84 mTotalItemsInManifest(0),
85 mOpened(false)
86 {
87 }
89 nsJAR::~nsJAR()
90 {
91 Close();
92 }
94 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
95 NS_IMPL_ADDREF(nsJAR)
97 // Custom Release method works with nsZipReaderCache...
98 MozExternalRefCountType nsJAR::Release(void)
99 {
100 nsrefcnt count;
101 NS_PRECONDITION(0 != mRefCnt, "dup release");
102 count = --mRefCnt;
103 NS_LOG_RELEASE(this, count, "nsJAR");
104 if (0 == count) {
105 mRefCnt = 1; /* stabilize */
106 /* enable this to find non-threadsafe destructors: */
107 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
108 delete this;
109 return 0;
110 }
111 else if (1 == count && mCache) {
112 #ifdef DEBUG
113 nsresult rv =
114 #endif
115 mCache->ReleaseZip(this);
116 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
117 }
118 return count;
119 }
121 //----------------------------------------------
122 // nsIZipReader implementation
123 //----------------------------------------------
125 NS_IMETHODIMP
126 nsJAR::Open(nsIFile* zipFile)
127 {
128 NS_ENSURE_ARG_POINTER(zipFile);
129 if (mOpened) return NS_ERROR_FAILURE; // Already open!
131 mZipFile = zipFile;
132 mOuterZipEntry.Truncate();
133 mOpened = true;
135 // The omnijar is special, it is opened early on and closed late
136 // this avoids reopening it
137 nsRefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
138 if (zip) {
139 mZip = zip;
140 return NS_OK;
141 }
142 return mZip->OpenArchive(zipFile);
143 }
145 NS_IMETHODIMP
146 nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
147 {
148 NS_ENSURE_ARG_POINTER(aZipReader);
149 if (mOpened) return NS_ERROR_FAILURE; // Already open!
151 bool exist;
152 nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
153 NS_ENSURE_SUCCESS(rv, rv);
154 NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
156 rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
157 NS_ENSURE_SUCCESS(rv, rv);
159 mOpened = true;
161 mOuterZipEntry.Assign(aZipEntry);
163 nsRefPtr<nsZipHandle> handle;
164 rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
165 getter_AddRefs(handle));
166 if (NS_FAILED(rv))
167 return rv;
169 return mZip->OpenArchive(handle);
170 }
172 NS_IMETHODIMP
173 nsJAR::GetFile(nsIFile* *result)
174 {
175 *result = mZipFile;
176 NS_IF_ADDREF(*result);
177 return NS_OK;
178 }
180 NS_IMETHODIMP
181 nsJAR::Close()
182 {
183 mOpened = false;
184 mParsedManifest = false;
185 mManifestData.Clear();
186 mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
187 mTotalItemsInManifest = 0;
189 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
190 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
192 if (mZip == greOmni || mZip == appOmni) {
193 mZip = new nsZipArchive();
194 return NS_OK;
195 }
196 return mZip->CloseArchive();
197 }
199 NS_IMETHODIMP
200 nsJAR::Test(const nsACString &aEntryName)
201 {
202 return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
203 }
205 NS_IMETHODIMP
206 nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
207 {
208 // nsZipArchive and zlib are not thread safe
209 // we need to use a lock to prevent bug #51267
210 MutexAutoLock lock(mLock);
212 nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
213 NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
215 // Remove existing file or directory so we set permissions correctly.
216 // If it's a directory that already exists and contains files, throw
217 // an exception and return.
219 nsresult rv = outFile->Remove(false);
220 if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
221 rv == NS_ERROR_FAILURE)
222 return rv;
224 if (item->IsDirectory())
225 {
226 rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
227 //XXX Do this in nsZipArchive? It would be nice to keep extraction
228 //XXX code completely there, but that would require a way to get a
229 //XXX PRDir from outFile.
230 }
231 else
232 {
233 PRFileDesc* fd;
234 rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
235 if (NS_FAILED(rv)) return rv;
237 // ExtractFile also closes the fd handle and resolves the symlink if needed
238 nsAutoCString path;
239 rv = outFile->GetNativePath(path);
240 if (NS_FAILED(rv)) return rv;
242 rv = mZip->ExtractFile(item, path.get(), fd);
243 }
244 if (NS_FAILED(rv)) return rv;
246 // nsIFile needs milliseconds, while prtime is in microseconds.
247 // non-fatal if this fails, ignore errors
248 outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
250 return NS_OK;
251 }
253 NS_IMETHODIMP
254 nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
255 {
256 nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
257 NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
259 nsJARItem* jarItem = new nsJARItem(zipItem);
261 NS_ADDREF(*result = jarItem);
262 return NS_OK;
263 }
265 NS_IMETHODIMP
266 nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
267 {
268 *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
269 return NS_OK;
270 }
272 NS_IMETHODIMP
273 nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
274 {
275 NS_ENSURE_ARG_POINTER(result);
277 nsZipFind *find;
278 nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
279 NS_ENSURE_SUCCESS(rv, rv);
281 nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
283 NS_ADDREF(*result = zipEnum);
284 return NS_OK;
285 }
287 NS_IMETHODIMP
288 nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
289 {
290 return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
291 }
293 NS_IMETHODIMP
294 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
295 const nsACString &aEntryName, nsIInputStream** result)
296 {
297 NS_ENSURE_ARG_POINTER(result);
299 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
300 nsZipItem *item = nullptr;
301 const char *entry = PromiseFlatCString(aEntryName).get();
302 if (*entry) {
303 // First check if item exists in jar
304 item = mZip->GetItem(entry);
305 if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
306 }
307 nsJARInputStream* jis = new nsJARInputStream();
308 // addref now so we can call InitFile/InitDirectory()
309 NS_ADDREF(*result = jis);
311 nsresult rv = NS_OK;
312 if (!item || item->IsDirectory()) {
313 rv = jis->InitDirectory(this, aJarDirSpec, entry);
314 } else {
315 rv = jis->InitFile(this, item);
316 }
317 if (NS_FAILED(rv)) {
318 NS_RELEASE(*result);
319 }
320 return rv;
321 }
323 NS_IMETHODIMP
324 nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal)
325 {
326 //-- Parameter check
327 if (!aPrincipal)
328 return NS_ERROR_NULL_POINTER;
329 *aPrincipal = nullptr;
331 // Don't check signatures in the omnijar - this is only
332 // interesting for extensions/XPIs.
333 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
334 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
336 if (mZip == greOmni || mZip == appOmni)
337 return NS_OK;
339 //-- Parse the manifest
340 nsresult rv = ParseManifest();
341 if (NS_FAILED(rv)) return rv;
342 if (mGlobalStatus == JAR_NO_MANIFEST)
343 return NS_OK;
345 int16_t requestedStatus;
346 if (!aFilename.IsEmpty())
347 {
348 //-- Find the item
349 nsJARManifestItem* manItem = mManifestData.Get(aFilename);
350 if (!manItem)
351 return NS_OK;
352 //-- Verify the item against the manifest
353 if (!manItem->entryVerified)
354 {
355 nsXPIDLCString entryData;
356 uint32_t entryDataLen;
357 rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
358 if (NS_FAILED(rv)) return rv;
359 rv = VerifyEntry(manItem, entryData, entryDataLen);
360 if (NS_FAILED(rv)) return rv;
361 }
362 requestedStatus = manItem->status;
363 }
364 else // User wants identity of signer w/o verifying any entries
365 requestedStatus = mGlobalStatus;
367 if (requestedStatus != JAR_VALID_MANIFEST)
368 ReportError(aFilename, requestedStatus);
369 else // Valid signature
370 {
371 *aPrincipal = mPrincipal;
372 NS_IF_ADDREF(*aPrincipal);
373 }
374 return NS_OK;
375 }
377 NS_IMETHODIMP
378 nsJAR::GetManifestEntriesCount(uint32_t* count)
379 {
380 *count = mTotalItemsInManifest;
381 return NS_OK;
382 }
384 nsresult
385 nsJAR::GetJarPath(nsACString& aResult)
386 {
387 NS_ENSURE_ARG_POINTER(mZipFile);
389 return mZipFile->GetNativePath(aResult);
390 }
392 //----------------------------------------------
393 // nsJAR private implementation
394 //----------------------------------------------
395 nsresult
396 nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen)
397 {
398 //-- Get a stream for reading the file
399 nsresult rv;
400 nsCOMPtr<nsIInputStream> manifestStream;
401 rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
402 if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
404 //-- Read the manifest file into memory
405 char* buf;
406 uint64_t len64;
407 rv = manifestStream->Available(&len64);
408 if (NS_FAILED(rv)) return rv;
409 NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
410 uint32_t len = (uint32_t)len64;
411 buf = (char*)malloc(len+1);
412 if (!buf) return NS_ERROR_OUT_OF_MEMORY;
413 uint32_t bytesRead;
414 rv = manifestStream->Read(buf, len, &bytesRead);
415 if (bytesRead != len)
416 rv = NS_ERROR_FILE_CORRUPTED;
417 if (NS_FAILED(rv)) {
418 free(buf);
419 return rv;
420 }
421 buf[len] = '\0'; //Null-terminate the buffer
422 *aBuf = buf;
423 if (aBufLen)
424 *aBufLen = len;
425 return NS_OK;
426 }
429 int32_t
430 nsJAR::ReadLine(const char** src)
431 {
432 if (!*src) {
433 return 0;
434 }
436 //--Moves pointer to beginning of next line and returns line length
437 // not including CR/LF.
438 int32_t length;
439 char* eol = PL_strpbrk(*src, "\r\n");
441 if (eol == nullptr) // Probably reached end of file before newline
442 {
443 length = strlen(*src);
444 if (length == 0) // immediate end-of-file
445 *src = nullptr;
446 else // some data left on this line
447 *src += length;
448 }
449 else
450 {
451 length = eol - *src;
452 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
453 *src = eol+2;
454 else // Either CR or LF, so skip 1
455 *src = eol+1;
456 }
457 return length;
458 }
460 //-- The following #defines are used by ParseManifest()
461 // and ParseOneFile(). The header strings are defined in the JAR specification.
462 #define JAR_MF 1
463 #define JAR_SF 2
464 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
465 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
466 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
467 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
469 nsresult
470 nsJAR::ParseManifest()
471 {
472 //-- Verification Step 1
473 if (mParsedManifest)
474 return NS_OK;
475 //-- (1)Manifest (MF) file
476 nsCOMPtr<nsIUTF8StringEnumerator> files;
477 nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
478 if (!files) rv = NS_ERROR_FAILURE;
479 if (NS_FAILED(rv)) return rv;
481 //-- Load the file into memory
482 bool more;
483 rv = files->HasMore(&more);
484 NS_ENSURE_SUCCESS(rv, rv);
485 if (!more)
486 {
487 mGlobalStatus = JAR_NO_MANIFEST;
488 mParsedManifest = true;
489 return NS_OK;
490 }
492 nsAutoCString manifestFilename;
493 rv = files->GetNext(manifestFilename);
494 NS_ENSURE_SUCCESS(rv, rv);
496 // Check if there is more than one manifest, if so then error!
497 rv = files->HasMore(&more);
498 if (NS_FAILED(rv)) return rv;
499 if (more)
500 {
501 mParsedManifest = true;
502 return NS_ERROR_FILE_CORRUPTED; // More than one MF file
503 }
505 nsXPIDLCString manifestBuffer;
506 uint32_t manifestLen;
507 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
508 if (NS_FAILED(rv)) return rv;
510 //-- Parse it
511 rv = ParseOneFile(manifestBuffer, JAR_MF);
512 if (NS_FAILED(rv)) return rv;
514 //-- (2)Signature (SF) file
515 // If there are multiple signatures, we select one.
516 rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
517 if (!files) rv = NS_ERROR_FAILURE;
518 if (NS_FAILED(rv)) return rv;
519 //-- Get an SF file
520 rv = files->HasMore(&more);
521 if (NS_FAILED(rv)) return rv;
522 if (!more)
523 {
524 mGlobalStatus = JAR_NO_MANIFEST;
525 mParsedManifest = true;
526 return NS_OK;
527 }
528 rv = files->GetNext(manifestFilename);
529 if (NS_FAILED(rv)) return rv;
531 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
532 if (NS_FAILED(rv)) return rv;
534 //-- Get its corresponding signature file
535 nsAutoCString sigFilename(manifestFilename);
536 int32_t extension = sigFilename.RFindChar('.') + 1;
537 NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
538 (void)sigFilename.Cut(extension, 2);
539 nsXPIDLCString sigBuffer;
540 uint32_t sigLen;
541 {
542 nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
543 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
544 }
545 if (NS_FAILED(rv))
546 {
547 nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
548 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
549 }
550 if (NS_FAILED(rv))
551 {
552 mGlobalStatus = JAR_NO_MANIFEST;
553 mParsedManifest = true;
554 return NS_OK;
555 }
557 //-- Get the signature verifier service
558 nsCOMPtr<nsISignatureVerifier> verifier =
559 do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv);
560 if (NS_FAILED(rv)) // No signature verifier available
561 {
562 mGlobalStatus = JAR_NO_MANIFEST;
563 mParsedManifest = true;
564 return NS_OK;
565 }
567 //-- Verify that the signature file is a valid signature of the SF file
568 int32_t verifyError;
569 rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
570 &verifyError, getter_AddRefs(mPrincipal));
571 if (NS_FAILED(rv)) return rv;
572 if (mPrincipal && verifyError == 0)
573 mGlobalStatus = JAR_VALID_MANIFEST;
574 else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
575 mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
576 else
577 mGlobalStatus = JAR_INVALID_SIG;
579 //-- Parse the SF file. If the verification above failed, principal
580 // is null, and ParseOneFile will mark the relevant entries as invalid.
581 // if ParseOneFile fails, then it has no effect, and we can safely
582 // continue to the next SF file, or return.
583 ParseOneFile(manifestBuffer, JAR_SF);
584 mParsedManifest = true;
586 return NS_OK;
587 }
589 nsresult
590 nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
591 {
592 //-- Check file header
593 const char* nextLineStart = filebuf;
594 nsAutoCString curLine;
595 int32_t linelen;
596 linelen = ReadLine(&nextLineStart);
597 curLine.Assign(filebuf, linelen);
599 if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
600 ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) )
601 return NS_ERROR_FILE_CORRUPTED;
603 //-- Skip header section
604 do {
605 linelen = ReadLine(&nextLineStart);
606 } while (linelen > 0);
608 //-- Set up parsing variables
609 const char* curPos;
610 const char* sectionStart = nextLineStart;
612 nsJARManifestItem* curItemMF = nullptr;
613 bool foundName = false;
614 if (aFileType == JAR_MF) {
615 curItemMF = new nsJARManifestItem();
616 }
618 nsAutoCString curItemName;
619 nsAutoCString storedSectionDigest;
621 for(;;)
622 {
623 curPos = nextLineStart;
624 linelen = ReadLine(&nextLineStart);
625 curLine.Assign(curPos, linelen);
626 if (linelen == 0)
627 // end of section (blank line or end-of-file)
628 {
629 if (aFileType == JAR_MF)
630 {
631 mTotalItemsInManifest++;
632 if (curItemMF->mType != JAR_INVALID)
633 {
634 //-- Did this section have a name: line?
635 if(!foundName)
636 curItemMF->mType = JAR_INVALID;
637 else
638 {
639 //-- If it's an internal item, it must correspond
640 // to a valid jar entry
641 if (curItemMF->mType == JAR_INTERNAL)
642 {
643 bool exists;
644 nsresult rv = HasEntry(curItemName, &exists);
645 if (NS_FAILED(rv) || !exists)
646 curItemMF->mType = JAR_INVALID;
647 }
648 //-- Check for duplicates
649 if (mManifestData.Contains(curItemName)) {
650 curItemMF->mType = JAR_INVALID;
651 }
652 }
653 }
655 if (curItemMF->mType == JAR_INVALID)
656 delete curItemMF;
657 else //-- calculate section digest
658 {
659 uint32_t sectionLength = curPos - sectionStart;
660 CalculateDigest(sectionStart, sectionLength,
661 curItemMF->calculatedSectionDigest);
662 //-- Save item in the hashtable
663 mManifestData.Put(curItemName, curItemMF);
664 }
665 if (nextLineStart == nullptr) // end-of-file
666 break;
668 sectionStart = nextLineStart;
669 curItemMF = new nsJARManifestItem();
670 } // (aFileType == JAR_MF)
671 else
672 //-- file type is SF, compare digest with calculated
673 // section digests from MF file.
674 {
675 if (foundName)
676 {
677 nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
678 if(curItemSF)
679 {
680 NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
681 "SECURITY ERROR: nsJARManifestItem not correctly initialized");
682 curItemSF->status = mGlobalStatus;
683 if (curItemSF->status == JAR_VALID_MANIFEST)
684 { // Compare digests
685 if (storedSectionDigest.IsEmpty())
686 curItemSF->status = JAR_NOT_SIGNED;
687 else
688 {
689 if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
690 curItemSF->status = JAR_INVALID_MANIFEST;
691 curItemSF->calculatedSectionDigest.Truncate();
692 storedSectionDigest.Truncate();
693 }
694 } // (aPrincipal != nullptr)
695 } // if(curItemSF)
696 } // if(foundName)
698 if(nextLineStart == nullptr) // end-of-file
699 break;
700 } // aFileType == JAR_SF
701 foundName = false;
702 continue;
703 } // if(linelen == 0)
705 //-- Look for continuations (beginning with a space) on subsequent lines
706 // and append them to the current line.
707 while(*nextLineStart == ' ')
708 {
709 curPos = nextLineStart;
710 int32_t continuationLen = ReadLine(&nextLineStart) - 1;
711 nsAutoCString continuation(curPos+1, continuationLen);
712 curLine += continuation;
713 linelen += continuationLen;
714 }
716 //-- Find colon in current line, this separates name from value
717 int32_t colonPos = curLine.FindChar(':');
718 if (colonPos == -1) // No colon on line, ignore line
719 continue;
720 //-- Break down the line
721 nsAutoCString lineName;
722 curLine.Left(lineName, colonPos);
723 nsAutoCString lineData;
724 curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
726 //-- Lines to look for:
727 // (1) Digest:
728 if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
729 //-- This is a digest line, save the data in the appropriate place
730 {
731 if(aFileType == JAR_MF)
732 curItemMF->storedEntryDigest = lineData;
733 else
734 storedSectionDigest = lineData;
735 continue;
736 }
738 // (2) Name: associates this manifest section with a file in the jar.
739 if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
740 {
741 curItemName = lineData;
742 foundName = true;
743 continue;
744 }
746 // (3) Magic: this may be an inline Javascript.
747 // We can't do any other kind of magic.
748 if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
749 {
750 if (lineData.LowerCaseEqualsLiteral("javascript"))
751 curItemMF->mType = JAR_EXTERNAL;
752 else
753 curItemMF->mType = JAR_INVALID;
754 continue;
755 }
757 } // for (;;)
758 return NS_OK;
759 } //ParseOneFile()
761 nsresult
762 nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
763 uint32_t aLen)
764 {
765 if (aManItem->status == JAR_VALID_MANIFEST)
766 {
767 if (aManItem->storedEntryDigest.IsEmpty())
768 // No entry digests in manifest file. Entry is unsigned.
769 aManItem->status = JAR_NOT_SIGNED;
770 else
771 { //-- Calculate and compare digests
772 nsCString calculatedEntryDigest;
773 nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
774 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
775 if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
776 aManItem->status = JAR_INVALID_ENTRY;
777 aManItem->storedEntryDigest.Truncate();
778 }
779 }
780 aManItem->entryVerified = true;
781 return NS_OK;
782 }
784 void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
785 {
786 //-- Generate error message
787 nsAutoString message;
788 message.AssignLiteral("Signature Verification Error: the signature on ");
789 if (!aFilename.IsEmpty())
790 AppendASCIItoUTF16(aFilename, message);
791 else
792 message.AppendLiteral("this .jar archive");
793 message.AppendLiteral(" is invalid because ");
794 switch(errorCode)
795 {
796 case JAR_NOT_SIGNED:
797 message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
798 break;
799 case JAR_INVALID_SIG:
800 message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
801 break;
802 case JAR_INVALID_UNKNOWN_CA:
803 message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
804 break;
805 case JAR_INVALID_MANIFEST:
806 message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
807 break;
808 case JAR_INVALID_ENTRY:
809 message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
810 break;
811 case JAR_NO_MANIFEST:
812 message.AppendLiteral("the archive did not contain a manifest.");
813 break;
814 default:
815 message.AppendLiteral("of an unknown problem.");
816 }
818 // Report error in JS console
819 nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
820 if (console)
821 {
822 console->LogStringMessage(message.get());
823 }
824 #ifdef DEBUG
825 char* messageCstr = ToNewCString(message);
826 if (!messageCstr) return;
827 fprintf(stderr, "%s\n", messageCstr);
828 nsMemory::Free(messageCstr);
829 #endif
830 }
833 nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
834 nsCString& digest)
835 {
836 nsresult rv;
838 nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
839 if (NS_FAILED(rv)) return rv;
841 rv = hasher->Init(nsICryptoHash::SHA1);
842 if (NS_FAILED(rv)) return rv;
844 rv = hasher->Update((const uint8_t*) aInBuf, aLen);
845 if (NS_FAILED(rv)) return rv;
847 return hasher->Finish(true, digest);
848 }
850 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
852 //----------------------------------------------
853 // nsJAREnumerator::HasMore
854 //----------------------------------------------
855 NS_IMETHODIMP
856 nsJAREnumerator::HasMore(bool* aResult)
857 {
858 // try to get the next element
859 if (!mName) {
860 NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
861 nsresult rv = mFind->FindNext( &mName, &mNameLen );
862 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
863 *aResult = false; // No more matches available
864 return NS_OK;
865 }
866 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
867 }
869 *aResult = true;
870 return NS_OK;
871 }
873 //----------------------------------------------
874 // nsJAREnumerator::GetNext
875 //----------------------------------------------
876 NS_IMETHODIMP
877 nsJAREnumerator::GetNext(nsACString& aResult)
878 {
879 // check if the current item is "stale"
880 if (!mName) {
881 bool bMore;
882 nsresult rv = HasMore(&bMore);
883 if (NS_FAILED(rv) || !bMore)
884 return NS_ERROR_FAILURE; // no error translation
885 }
886 aResult.Assign(mName, mNameLen);
887 mName = 0; // we just gave this one away
888 return NS_OK;
889 }
892 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
894 nsJARItem::nsJARItem(nsZipItem* aZipItem)
895 : mSize(aZipItem->Size()),
896 mRealsize(aZipItem->RealSize()),
897 mCrc32(aZipItem->CRC32()),
898 mLastModTime(aZipItem->LastModTime()),
899 mCompression(aZipItem->Compression()),
900 mPermissions(aZipItem->Mode()),
901 mIsDirectory(aZipItem->IsDirectory()),
902 mIsSynthetic(aZipItem->isSynthetic)
903 {
904 }
906 //------------------------------------------
907 // nsJARItem::GetCompression
908 //------------------------------------------
909 NS_IMETHODIMP
910 nsJARItem::GetCompression(uint16_t *aCompression)
911 {
912 NS_ENSURE_ARG_POINTER(aCompression);
914 *aCompression = mCompression;
915 return NS_OK;
916 }
918 //------------------------------------------
919 // nsJARItem::GetSize
920 //------------------------------------------
921 NS_IMETHODIMP
922 nsJARItem::GetSize(uint32_t *aSize)
923 {
924 NS_ENSURE_ARG_POINTER(aSize);
926 *aSize = mSize;
927 return NS_OK;
928 }
930 //------------------------------------------
931 // nsJARItem::GetRealSize
932 //------------------------------------------
933 NS_IMETHODIMP
934 nsJARItem::GetRealSize(uint32_t *aRealsize)
935 {
936 NS_ENSURE_ARG_POINTER(aRealsize);
938 *aRealsize = mRealsize;
939 return NS_OK;
940 }
942 //------------------------------------------
943 // nsJARItem::GetCrc32
944 //------------------------------------------
945 NS_IMETHODIMP
946 nsJARItem::GetCRC32(uint32_t *aCrc32)
947 {
948 NS_ENSURE_ARG_POINTER(aCrc32);
950 *aCrc32 = mCrc32;
951 return NS_OK;
952 }
954 //------------------------------------------
955 // nsJARItem::GetIsDirectory
956 //------------------------------------------
957 NS_IMETHODIMP
958 nsJARItem::GetIsDirectory(bool *aIsDirectory)
959 {
960 NS_ENSURE_ARG_POINTER(aIsDirectory);
962 *aIsDirectory = mIsDirectory;
963 return NS_OK;
964 }
966 //------------------------------------------
967 // nsJARItem::GetIsSynthetic
968 //------------------------------------------
969 NS_IMETHODIMP
970 nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
971 {
972 NS_ENSURE_ARG_POINTER(aIsSynthetic);
974 *aIsSynthetic = mIsSynthetic;
975 return NS_OK;
976 }
978 //------------------------------------------
979 // nsJARItem::GetLastModifiedTime
980 //------------------------------------------
981 NS_IMETHODIMP
982 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
983 {
984 NS_ENSURE_ARG_POINTER(aLastModTime);
986 *aLastModTime = mLastModTime;
987 return NS_OK;
988 }
990 //------------------------------------------
991 // nsJARItem::GetPermissions
992 //------------------------------------------
993 NS_IMETHODIMP
994 nsJARItem::GetPermissions(uint32_t* aPermissions)
995 {
996 NS_ENSURE_ARG_POINTER(aPermissions);
998 *aPermissions = mPermissions;
999 return NS_OK;
1000 }
1002 ////////////////////////////////////////////////////////////////////////////////
1003 // nsIZipReaderCache
1005 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
1007 nsZipReaderCache::nsZipReaderCache()
1008 : mLock("nsZipReaderCache.mLock")
1009 , mZips(16)
1010 #ifdef ZIP_CACHE_HIT_RATE
1011 ,
1012 mZipCacheLookups(0),
1013 mZipCacheHits(0),
1014 mZipCacheFlushes(0),
1015 mZipSyncMisses(0)
1016 #endif
1017 {
1018 }
1020 NS_IMETHODIMP
1021 nsZipReaderCache::Init(uint32_t cacheSize)
1022 {
1023 mCacheSize = cacheSize;
1025 // Register as a memory pressure observer
1026 nsCOMPtr<nsIObserverService> os =
1027 do_GetService("@mozilla.org/observer-service;1");
1028 if (os)
1029 {
1030 os->AddObserver(this, "memory-pressure", true);
1031 os->AddObserver(this, "chrome-flush-caches", true);
1032 os->AddObserver(this, "flush-cache-entry", true);
1033 }
1034 // ignore failure of the observer registration.
1036 return NS_OK;
1037 }
1039 static PLDHashOperator
1040 DropZipReaderCache(const nsACString &aKey, nsJAR* aZip, void*)
1041 {
1042 aZip->SetZipReaderCache(nullptr);
1043 return PL_DHASH_NEXT;
1044 }
1046 nsZipReaderCache::~nsZipReaderCache()
1047 {
1048 mZips.EnumerateRead(DropZipReaderCache, nullptr);
1050 #ifdef ZIP_CACHE_HIT_RATE
1051 printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
1052 mCacheSize, mZipCacheHits, mZipCacheLookups,
1053 (float)mZipCacheHits / mZipCacheLookups,
1054 mZipCacheFlushes, mZipSyncMisses);
1055 #endif
1056 }
1058 NS_IMETHODIMP
1059 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
1060 {
1061 NS_ENSURE_ARG_POINTER(zipFile);
1062 nsresult rv;
1063 nsCOMPtr<nsIZipReader> antiLockZipGrip;
1064 MutexAutoLock lock(mLock);
1066 nsAutoCString uri;
1067 rv = zipFile->GetNativePath(uri);
1068 if (NS_FAILED(rv))
1069 return rv;
1071 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1073 *aResult = mZips.Contains(uri);
1074 return NS_OK;
1075 }
1077 NS_IMETHODIMP
1078 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
1079 {
1080 NS_ENSURE_ARG_POINTER(zipFile);
1081 nsresult rv;
1082 nsCOMPtr<nsIZipReader> antiLockZipGrip;
1083 MutexAutoLock lock(mLock);
1085 #ifdef ZIP_CACHE_HIT_RATE
1086 mZipCacheLookups++;
1087 #endif
1089 nsAutoCString uri;
1090 rv = zipFile->GetNativePath(uri);
1091 if (NS_FAILED(rv)) return rv;
1093 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1095 nsRefPtr<nsJAR> zip;
1096 mZips.Get(uri, getter_AddRefs(zip));
1097 if (zip) {
1098 #ifdef ZIP_CACHE_HIT_RATE
1099 mZipCacheHits++;
1100 #endif
1101 zip->ClearReleaseTime();
1102 } else {
1103 zip = new nsJAR();
1104 zip->SetZipReaderCache(this);
1106 rv = zip->Open(zipFile);
1107 if (NS_FAILED(rv)) {
1108 return rv;
1109 }
1111 MOZ_ASSERT(!mZips.Contains(uri));
1112 mZips.Put(uri, zip);
1113 }
1114 zip.forget(result);
1115 return rv;
1116 }
1118 NS_IMETHODIMP
1119 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
1120 nsIZipReader* *result)
1121 {
1122 NS_ENSURE_ARG_POINTER(zipFile);
1124 nsCOMPtr<nsIZipReader> outerZipReader;
1125 nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
1126 NS_ENSURE_SUCCESS(rv, rv);
1128 #ifdef ZIP_CACHE_HIT_RATE
1129 mZipCacheLookups++;
1130 #endif
1132 nsAutoCString uri;
1133 rv = zipFile->GetNativePath(uri);
1134 if (NS_FAILED(rv)) return rv;
1136 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
1137 uri.AppendLiteral("!/");
1138 uri.Append(entry);
1140 nsRefPtr<nsJAR> zip;
1141 mZips.Get(uri, getter_AddRefs(zip));
1142 if (zip) {
1143 #ifdef ZIP_CACHE_HIT_RATE
1144 mZipCacheHits++;
1145 #endif
1146 zip->ClearReleaseTime();
1147 } else {
1148 zip = new nsJAR();
1149 zip->SetZipReaderCache(this);
1151 rv = zip->OpenInner(outerZipReader, entry);
1152 if (NS_FAILED(rv)) {
1153 return rv;
1154 }
1156 MOZ_ASSERT(!mZips.Contains(uri));
1157 mZips.Put(uri, zip);
1158 }
1159 zip.forget(result);
1160 return rv;
1161 }
1163 static PLDHashOperator
1164 FindOldestZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
1165 {
1166 nsJAR** oldestPtr = static_cast<nsJAR**>(aClosure);
1167 nsJAR* oldest = *oldestPtr;
1168 nsJAR* current = aZip;
1169 PRIntervalTime currentReleaseTime = current->GetReleaseTime();
1170 if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
1171 if (oldest == nullptr ||
1172 currentReleaseTime < oldest->GetReleaseTime()) {
1173 *oldestPtr = current;
1174 }
1175 }
1176 return PL_DHASH_NEXT;
1177 }
1179 struct ZipFindData {nsJAR* zip; bool found;};
1181 static PLDHashOperator
1182 FindZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
1183 {
1184 ZipFindData* find_data = static_cast<ZipFindData*>(aClosure);
1186 if (find_data->zip == aZip) {
1187 find_data->found = true;
1188 return PL_DHASH_STOP;
1189 }
1190 return PL_DHASH_NEXT;
1191 }
1193 nsresult
1194 nsZipReaderCache::ReleaseZip(nsJAR* zip)
1195 {
1196 nsresult rv;
1197 MutexAutoLock lock(mLock);
1199 // It is possible that two thread compete for this zip. The dangerous
1200 // case is where one thread Releases the zip and discovers that the ref
1201 // count has gone to one. Before it can call this ReleaseZip method
1202 // another thread calls our GetZip method. The ref count goes to two. That
1203 // second thread then Releases the zip and the ref count goes to one. It
1204 // then tries to enter this ReleaseZip method and blocks while the first
1205 // thread is still here. The first thread continues and remove the zip from
1206 // the cache and calls its Release method sending the ref count to 0 and
1207 // deleting the zip. However, the second thread is still blocked at the
1208 // start of ReleaseZip, but the 'zip' param now hold a reference to a
1209 // deleted zip!
1210 //
1211 // So, we are going to try safeguarding here by searching our hashtable while
1212 // locked here for the zip. We return fast if it is not found.
1214 ZipFindData find_data = {zip, false};
1215 mZips.EnumerateRead(FindZip, &find_data);
1216 if (!find_data.found) {
1217 #ifdef ZIP_CACHE_HIT_RATE
1218 mZipSyncMisses++;
1219 #endif
1220 return NS_OK;
1221 }
1223 zip->SetReleaseTime();
1225 if (mZips.Count() <= mCacheSize)
1226 return NS_OK;
1228 nsJAR* oldest = nullptr;
1229 mZips.EnumerateRead(FindOldestZip, &oldest);
1231 // Because of the craziness above it is possible that there is no zip that
1232 // needs removing.
1233 if (!oldest)
1234 return NS_OK;
1236 #ifdef ZIP_CACHE_HIT_RATE
1237 mZipCacheFlushes++;
1238 #endif
1240 // remove from hashtable
1241 nsAutoCString uri;
1242 rv = oldest->GetJarPath(uri);
1243 if (NS_FAILED(rv))
1244 return rv;
1246 if (oldest->mOuterZipEntry.IsEmpty()) {
1247 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1248 } else {
1249 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
1250 uri.AppendLiteral("!/");
1251 uri.Append(oldest->mOuterZipEntry);
1252 }
1254 // Retrieving and removing the JAR must be done without an extra AddRef
1255 // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
1256 // an extra time and trigger a deadlock.
1257 nsRefPtr<nsJAR> removed;
1258 mZips.Remove(uri, getter_AddRefs(removed));
1259 NS_ASSERTION(removed, "botched");
1260 NS_ASSERTION(oldest == removed, "removed wrong entry");
1262 if (removed)
1263 removed->SetZipReaderCache(nullptr);
1265 return NS_OK;
1266 }
1268 static PLDHashOperator
1269 FindFlushableZip(const nsACString &aKey, nsRefPtr<nsJAR>& aCurrent, void*)
1270 {
1271 if (aCurrent->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
1272 aCurrent->SetZipReaderCache(nullptr);
1273 return PL_DHASH_REMOVE;
1274 }
1275 return PL_DHASH_NEXT;
1276 }
1278 NS_IMETHODIMP
1279 nsZipReaderCache::Observe(nsISupports *aSubject,
1280 const char *aTopic,
1281 const char16_t *aSomeData)
1282 {
1283 if (strcmp(aTopic, "memory-pressure") == 0) {
1284 MutexAutoLock lock(mLock);
1285 mZips.Enumerate(FindFlushableZip, nullptr);
1286 }
1287 else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1288 mZips.EnumerateRead(DropZipReaderCache, nullptr);
1289 mZips.Clear();
1290 }
1291 else if (strcmp(aTopic, "flush-cache-entry") == 0) {
1292 nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
1293 if (!file)
1294 return NS_OK;
1296 nsAutoCString uri;
1297 if (NS_FAILED(file->GetNativePath(uri)))
1298 return NS_OK;
1300 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1302 MutexAutoLock lock(mLock);
1304 nsRefPtr<nsJAR> zip;
1305 mZips.Get(uri, getter_AddRefs(zip));
1306 if (!zip)
1307 return NS_OK;
1309 #ifdef ZIP_CACHE_HIT_RATE
1310 mZipCacheFlushes++;
1311 #endif
1313 zip->SetZipReaderCache(nullptr);
1315 mZips.Remove(uri);
1316 }
1317 return NS_OK;
1318 }
1320 ////////////////////////////////////////////////////////////////////////////////