diff options
Diffstat (limited to 'modules/libjar/nsJAR.cpp')
-rw-r--r-- | modules/libjar/nsJAR.cpp | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/modules/libjar/nsJAR.cpp b/modules/libjar/nsJAR.cpp new file mode 100644 index 000000000..0b0444a81 --- /dev/null +++ b/modules/libjar/nsJAR.cpp @@ -0,0 +1,1401 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <string.h> +#include "nsJARInputStream.h" +#include "nsJAR.h" +#include "nsIFile.h" +#include "nsIX509Cert.h" +#include "nsIConsoleService.h" +#include "nsICryptoHash.h" +#include "nsIDataSignatureVerifier.h" +#include "prprf.h" +#include "mozilla/Omnijar.h" + +#ifdef XP_UNIX + #include <sys/stat.h> +#elif defined (XP_WIN) + #include <io.h> +#endif + +using namespace mozilla; + +//---------------------------------------------- +// nsJARManifestItem declaration +//---------------------------------------------- +/* + * nsJARManifestItem contains meta-information pertaining + * to an individual JAR entry, taken from the + * META-INF/MANIFEST.MF and META-INF/ *.SF files. + * This is security-critical information, defined here so it is not + * accessible from anywhere else. + */ +typedef enum +{ + JAR_INVALID = 1, + JAR_INTERNAL = 2, + JAR_EXTERNAL = 3 +} JARManifestItemType; + +class nsJARManifestItem +{ +public: + JARManifestItemType mType; + + // True if the second step of verification (VerifyEntry) + // has taken place: + bool entryVerified; + + // Not signed, valid, or failure code + int16_t status; + + // Internal storage of digests + nsCString calculatedSectionDigest; + nsCString storedEntryDigest; + + nsJARManifestItem(); + virtual ~nsJARManifestItem(); +}; + +//------------------------------------------------- +// nsJARManifestItem constructors and destructor +//------------------------------------------------- +nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL), + entryVerified(false), + status(JAR_NOT_SIGNED) +{ +} + +nsJARManifestItem::~nsJARManifestItem() +{ +} + +//---------------------------------------------- +// nsJAR constructor/destructor +//---------------------------------------------- + +// The following initialization makes a guess of 10 entries per jarfile. +nsJAR::nsJAR(): mZip(new nsZipArchive()), + mManifestData(8), + mParsedManifest(false), + mGlobalStatus(JAR_MANIFEST_NOT_PARSED), + mReleaseTime(PR_INTERVAL_NO_TIMEOUT), + mCache(nullptr), + mLock("nsJAR::mLock"), + mMtime(0), + mTotalItemsInManifest(0), + mOpened(false), + mIsOmnijar(false) +{ +} + +nsJAR::~nsJAR() +{ + Close(); +} + +NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader) +NS_IMPL_ADDREF(nsJAR) + +// Custom Release method works with nsZipReaderCache... +// Release might be called from multi-thread, we have to +// take this function carefully to avoid delete-after-use. +MozExternalRefCountType nsJAR::Release(void) +{ + nsrefcnt count; + NS_PRECONDITION(0 != mRefCnt, "dup release"); + + RefPtr<nsZipReaderCache> cache; + if (mRefCnt == 2) { // don't use a lock too frequently + // Use a mutex here to guarantee mCache is not racing and the target instance + // is still valid to increase ref-count. + MutexAutoLock lock(mLock); + cache = mCache; + mCache = nullptr; + } + if (cache) { + DebugOnly<nsresult> rv = cache->ReleaseZip(this); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file"); + } + + count = --mRefCnt; // don't access any member variable after this line + NS_LOG_RELEASE(this, count, "nsJAR"); + if (0 == count) { + mRefCnt = 1; /* stabilize */ + /* enable this to find non-threadsafe destructors: */ + /* NS_ASSERT_OWNINGTHREAD(nsJAR); */ + delete this; + return 0; + } + + return count; +} + +//---------------------------------------------- +// nsIZipReader implementation +//---------------------------------------------- + +NS_IMETHODIMP +nsJAR::Open(nsIFile* zipFile) +{ + NS_ENSURE_ARG_POINTER(zipFile); + if (mOpened) return NS_ERROR_FAILURE; // Already open! + + mZipFile = zipFile; + mOuterZipEntry.Truncate(); + mOpened = true; + + // The omnijar is special, it is opened early on and closed late + // this avoids reopening it + RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile); + if (zip) { + mZip = zip; + mIsOmnijar = true; + return NS_OK; + } + return mZip->OpenArchive(zipFile); +} + +NS_IMETHODIMP +nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry) +{ + NS_ENSURE_ARG_POINTER(aZipReader); + if (mOpened) return NS_ERROR_FAILURE; // Already open! + + bool exist; + nsresult rv = aZipReader->HasEntry(aZipEntry, &exist); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND); + + rv = aZipReader->GetFile(getter_AddRefs(mZipFile)); + NS_ENSURE_SUCCESS(rv, rv); + + mOpened = true; + + mOuterZipEntry.Assign(aZipEntry); + + RefPtr<nsZipHandle> handle; + rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(), + getter_AddRefs(handle)); + if (NS_FAILED(rv)) + return rv; + + return mZip->OpenArchive(handle); +} + +NS_IMETHODIMP +nsJAR::OpenMemory(void* aData, uint32_t aLength) +{ + NS_ENSURE_ARG_POINTER(aData); + if (mOpened) return NS_ERROR_FAILURE; // Already open! + + mOpened = true; + + RefPtr<nsZipHandle> handle; + nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength, + getter_AddRefs(handle)); + if (NS_FAILED(rv)) + return rv; + + return mZip->OpenArchive(handle); +} + +NS_IMETHODIMP +nsJAR::GetFile(nsIFile* *result) +{ + *result = mZipFile; + NS_IF_ADDREF(*result); + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::Close() +{ + if (!mOpened) { + return NS_ERROR_FAILURE; // Never opened or already closed. + } + + mOpened = false; + mParsedManifest = false; + mManifestData.Clear(); + mGlobalStatus = JAR_MANIFEST_NOT_PARSED; + mTotalItemsInManifest = 0; + + if (mIsOmnijar) { + // Reset state, but don't close the omnijar because we did not open it. + mIsOmnijar = false; + mZip = new nsZipArchive(); + return NS_OK; + } + + return mZip->CloseArchive(); +} + +NS_IMETHODIMP +nsJAR::Test(const nsACString &aEntryName) +{ + return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get()); +} + +NS_IMETHODIMP +nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile) +{ + // nsZipArchive and zlib are not thread safe + // we need to use a lock to prevent bug #51267 + MutexAutoLock lock(mLock); + + nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get()); + NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); + + // Remove existing file or directory so we set permissions correctly. + // If it's a directory that already exists and contains files, throw + // an exception and return. + + nsresult rv = outFile->Remove(false); + if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || + rv == NS_ERROR_FAILURE) + return rv; + + if (item->IsDirectory()) + { + rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode()); + //XXX Do this in nsZipArchive? It would be nice to keep extraction + //XXX code completely there, but that would require a way to get a + //XXX PRDir from outFile. + } + else + { + PRFileDesc* fd; + rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd); + if (NS_FAILED(rv)) return rv; + + // ExtractFile also closes the fd handle and resolves the symlink if needed + nsAutoCString path; + rv = outFile->GetNativePath(path); + if (NS_FAILED(rv)) return rv; + + rv = mZip->ExtractFile(item, path.get(), fd); + } + if (NS_FAILED(rv)) return rv; + + // nsIFile needs milliseconds, while prtime is in microseconds. + // non-fatal if this fails, ignore errors + outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC); + + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result) +{ + nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get()); + NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); + + nsJARItem* jarItem = new nsJARItem(zipItem); + + NS_ADDREF(*result = jarItem); + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::HasEntry(const nsACString &aEntryName, bool *result) +{ + *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result) +{ + NS_ENSURE_ARG_POINTER(result); + + nsZipFind *find; + nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find); + NS_ENSURE_SUCCESS(rv, rv); + + nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find); + + NS_ADDREF(*result = zipEnum); + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result) +{ + return GetInputStreamWithSpec(EmptyCString(), aFilename, result); +} + +NS_IMETHODIMP +nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec, + const nsACString &aEntryName, nsIInputStream** result) +{ + NS_ENSURE_ARG_POINTER(result); + + // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case! + nsZipItem *item = nullptr; + const nsCString& entry = PromiseFlatCString(aEntryName); + if (*entry.get()) { + // First check if item exists in jar + item = mZip->GetItem(entry.get()); + if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + } + nsJARInputStream* jis = new nsJARInputStream(); + // addref now so we can call InitFile/InitDirectory() + NS_ADDREF(*result = jis); + + nsresult rv = NS_OK; + if (!item || item->IsDirectory()) { + rv = jis->InitDirectory(this, aJarDirSpec, entry.get()); + } else { + rv = jis->InitFile(this, item); + } + if (NS_FAILED(rv)) { + NS_RELEASE(*result); + } + return rv; +} + +NS_IMETHODIMP +nsJAR::GetSigningCert(const nsACString& aFilename, nsIX509Cert** aSigningCert) +{ + //-- Parameter check + if (!aSigningCert) { + return NS_ERROR_NULL_POINTER; + } + *aSigningCert = nullptr; + + // Don't check signatures in the omnijar - this is only + // interesting for extensions/XPIs. + RefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + RefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + + if (mZip == greOmni || mZip == appOmni) + return NS_OK; + + //-- Parse the manifest + nsresult rv = ParseManifest(); + if (NS_FAILED(rv)) return rv; + if (mGlobalStatus == JAR_NO_MANIFEST) + return NS_OK; + + int16_t requestedStatus; + if (!aFilename.IsEmpty()) + { + //-- Find the item + nsJARManifestItem* manItem = mManifestData.Get(aFilename); + if (!manItem) + return NS_OK; + //-- Verify the item against the manifest + if (!manItem->entryVerified) + { + nsXPIDLCString entryData; + uint32_t entryDataLen; + rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen); + if (NS_FAILED(rv)) return rv; + rv = VerifyEntry(manItem, entryData, entryDataLen); + if (NS_FAILED(rv)) return rv; + } + requestedStatus = manItem->status; + } + else // User wants identity of signer w/o verifying any entries + requestedStatus = mGlobalStatus; + + if (requestedStatus != JAR_VALID_MANIFEST) { + ReportError(aFilename, requestedStatus); + } else { // Valid signature + *aSigningCert = mSigningCert; + NS_IF_ADDREF(*aSigningCert); + } + return NS_OK; +} + +NS_IMETHODIMP +nsJAR::GetManifestEntriesCount(uint32_t* count) +{ + *count = mTotalItemsInManifest; + return NS_OK; +} + +nsresult +nsJAR::GetJarPath(nsACString& aResult) +{ + NS_ENSURE_ARG_POINTER(mZipFile); + + return mZipFile->GetNativePath(aResult); +} + +nsresult +nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) +{ + if (!aNSPRFileDesc) { + return NS_ERROR_ILLEGAL_VALUE; + } + *aNSPRFileDesc = nullptr; + + if (!mZip) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsZipHandle> handle = mZip->GetFD(); + if (!handle) { + return NS_ERROR_FAILURE; + } + + return handle->GetNSPRFileDesc(aNSPRFileDesc); +} + +//---------------------------------------------- +// nsJAR private implementation +//---------------------------------------------- +nsresult +nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen) +{ + //-- Get a stream for reading the file + nsresult rv; + nsCOMPtr<nsIInputStream> manifestStream; + rv = GetInputStream(aFilename, getter_AddRefs(manifestStream)); + if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + + //-- Read the manifest file into memory + char* buf; + uint64_t len64; + rv = manifestStream->Available(&len64); + if (NS_FAILED(rv)) return rv; + if (len64 >= UINT32_MAX) { // bug 164695 + nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest size"; + return NS_ERROR_FILE_CORRUPTED; + } + uint32_t len = (uint32_t)len64; + buf = (char*)malloc(len+1); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; + uint32_t bytesRead; + rv = manifestStream->Read(buf, len, &bytesRead); + if (bytesRead != len) { + nsZipArchive::sFileCorruptedReason = "nsJAR: manifest too small"; + rv = NS_ERROR_FILE_CORRUPTED; + } + if (NS_FAILED(rv)) { + free(buf); + return rv; + } + buf[len] = '\0'; //Null-terminate the buffer + *aBuf = buf; + if (aBufLen) + *aBufLen = len; + return NS_OK; +} + + +int32_t +nsJAR::ReadLine(const char** src) +{ + if (!*src) { + return 0; + } + + //--Moves pointer to beginning of next line and returns line length + // not including CR/LF. + int32_t length; + char* eol = PL_strpbrk(*src, "\r\n"); + + if (eol == nullptr) // Probably reached end of file before newline + { + length = strlen(*src); + if (length == 0) // immediate end-of-file + *src = nullptr; + else // some data left on this line + *src += length; + } + else + { + length = eol - *src; + if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2 + *src = eol+2; + else // Either CR or LF, so skip 1 + *src = eol+1; + } + return length; +} + +//-- The following #defines are used by ParseManifest() +// and ParseOneFile(). The header strings are defined in the JAR specification. +#define JAR_MF 1 +#define JAR_SF 2 +#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" +#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" +#define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0" +#define JAR_SF_HEADER (const char*)"Signature-Version: 1.0" + +nsresult +nsJAR::ParseManifest() +{ + //-- Verification Step 1 + if (mParsedManifest) + return NS_OK; + //-- (1)Manifest (MF) file + nsCOMPtr<nsIUTF8StringEnumerator> files; + nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files)); + if (!files) rv = NS_ERROR_FAILURE; + if (NS_FAILED(rv)) return rv; + + //-- Load the file into memory + bool more; + rv = files->HasMore(&more); + NS_ENSURE_SUCCESS(rv, rv); + if (!more) + { + mGlobalStatus = JAR_NO_MANIFEST; + mParsedManifest = true; + return NS_OK; + } + + nsAutoCString manifestFilename; + rv = files->GetNext(manifestFilename); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if there is more than one manifest, if so then error! + rv = files->HasMore(&more); + if (NS_FAILED(rv)) return rv; + if (more) + { + mParsedManifest = true; + nsZipArchive::sFileCorruptedReason = "nsJAR: duplicate manifests"; + return NS_ERROR_FILE_CORRUPTED; // More than one MF file + } + + nsXPIDLCString manifestBuffer; + uint32_t manifestLen; + rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); + if (NS_FAILED(rv)) return rv; + + //-- Parse it + rv = ParseOneFile(manifestBuffer, JAR_MF); + if (NS_FAILED(rv)) return rv; + + //-- (2)Signature (SF) file + // If there are multiple signatures, we select one. + rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files)); + if (!files) rv = NS_ERROR_FAILURE; + if (NS_FAILED(rv)) return rv; + //-- Get an SF file + rv = files->HasMore(&more); + if (NS_FAILED(rv)) return rv; + if (!more) + { + mGlobalStatus = JAR_NO_MANIFEST; + mParsedManifest = true; + return NS_OK; + } + rv = files->GetNext(manifestFilename); + if (NS_FAILED(rv)) return rv; + + rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); + if (NS_FAILED(rv)) return rv; + + //-- Get its corresponding signature file + nsAutoCString sigFilename(manifestFilename); + int32_t extension = sigFilename.RFindChar('.') + 1; + NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension."); + (void)sigFilename.Cut(extension, 2); + nsXPIDLCString sigBuffer; + uint32_t sigLen; + { + nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3); + rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); + } + if (NS_FAILED(rv)) + { + nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3); + rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); + } + if (NS_FAILED(rv)) + { + mGlobalStatus = JAR_NO_MANIFEST; + mParsedManifest = true; + return NS_OK; + } + + //-- Get the signature verifier service + nsCOMPtr<nsIDataSignatureVerifier> verifier( + do_GetService("@mozilla.org/security/datasignatureverifier;1", &rv)); + if (NS_FAILED(rv)) // No signature verifier available + { + mGlobalStatus = JAR_NO_MANIFEST; + mParsedManifest = true; + return NS_OK; + } + + //-- Verify that the signature file is a valid signature of the SF file + int32_t verifyError; + rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen, + &verifyError, getter_AddRefs(mSigningCert)); + if (NS_FAILED(rv)) return rv; + if (mSigningCert && verifyError == nsIDataSignatureVerifier::VERIFY_OK) { + mGlobalStatus = JAR_VALID_MANIFEST; + } else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER) { + mGlobalStatus = JAR_INVALID_UNKNOWN_CA; + } else { + mGlobalStatus = JAR_INVALID_SIG; + } + + //-- Parse the SF file. If the verification above failed, principal + // is null, and ParseOneFile will mark the relevant entries as invalid. + // if ParseOneFile fails, then it has no effect, and we can safely + // continue to the next SF file, or return. + ParseOneFile(manifestBuffer, JAR_SF); + mParsedManifest = true; + + return NS_OK; +} + +nsresult +nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType) +{ + //-- Check file header + const char* nextLineStart = filebuf; + nsAutoCString curLine; + int32_t linelen; + linelen = ReadLine(&nextLineStart); + curLine.Assign(filebuf, linelen); + + if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) || + ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) { + nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest header"; + return NS_ERROR_FILE_CORRUPTED; + } + + //-- Skip header section + do { + linelen = ReadLine(&nextLineStart); + } while (linelen > 0); + + //-- Set up parsing variables + const char* curPos; + const char* sectionStart = nextLineStart; + + nsJARManifestItem* curItemMF = nullptr; + bool foundName = false; + if (aFileType == JAR_MF) { + curItemMF = new nsJARManifestItem(); + } + + nsAutoCString curItemName; + nsAutoCString storedSectionDigest; + + for(;;) + { + curPos = nextLineStart; + linelen = ReadLine(&nextLineStart); + curLine.Assign(curPos, linelen); + if (linelen == 0) + // end of section (blank line or end-of-file) + { + if (aFileType == JAR_MF) + { + mTotalItemsInManifest++; + if (curItemMF->mType != JAR_INVALID) + { + //-- Did this section have a name: line? + if(!foundName) + curItemMF->mType = JAR_INVALID; + else + { + //-- If it's an internal item, it must correspond + // to a valid jar entry + if (curItemMF->mType == JAR_INTERNAL) + { + bool exists; + nsresult rv = HasEntry(curItemName, &exists); + if (NS_FAILED(rv) || !exists) + curItemMF->mType = JAR_INVALID; + } + //-- Check for duplicates + if (mManifestData.Contains(curItemName)) { + curItemMF->mType = JAR_INVALID; + } + } + } + + if (curItemMF->mType == JAR_INVALID) + delete curItemMF; + else //-- calculate section digest + { + uint32_t sectionLength = curPos - sectionStart; + CalculateDigest(sectionStart, sectionLength, + curItemMF->calculatedSectionDigest); + //-- Save item in the hashtable + mManifestData.Put(curItemName, curItemMF); + } + if (nextLineStart == nullptr) // end-of-file + break; + + sectionStart = nextLineStart; + curItemMF = new nsJARManifestItem(); + } // (aFileType == JAR_MF) + else + //-- file type is SF, compare digest with calculated + // section digests from MF file. + { + if (foundName) + { + nsJARManifestItem* curItemSF = mManifestData.Get(curItemName); + if(curItemSF) + { + NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED, + "SECURITY ERROR: nsJARManifestItem not correctly initialized"); + curItemSF->status = mGlobalStatus; + if (curItemSF->status == JAR_VALID_MANIFEST) + { // Compare digests + if (storedSectionDigest.IsEmpty()) + curItemSF->status = JAR_NOT_SIGNED; + else + { + if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest)) + curItemSF->status = JAR_INVALID_MANIFEST; + curItemSF->calculatedSectionDigest.Truncate(); + storedSectionDigest.Truncate(); + } + } // (aPrincipal != nullptr) + } // if(curItemSF) + } // if(foundName) + + if(nextLineStart == nullptr) // end-of-file + break; + } // aFileType == JAR_SF + foundName = false; + continue; + } // if(linelen == 0) + + //-- Look for continuations (beginning with a space) on subsequent lines + // and append them to the current line. + while(*nextLineStart == ' ') + { + curPos = nextLineStart; + int32_t continuationLen = ReadLine(&nextLineStart) - 1; + nsAutoCString continuation(curPos+1, continuationLen); + curLine += continuation; + linelen += continuationLen; + } + + //-- Find colon in current line, this separates name from value + int32_t colonPos = curLine.FindChar(':'); + if (colonPos == -1) // No colon on line, ignore line + continue; + //-- Break down the line + nsAutoCString lineName; + curLine.Left(lineName, colonPos); + nsAutoCString lineData; + curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2)); + + //-- Lines to look for: + // (1) Digest: + if (lineName.LowerCaseEqualsLiteral("sha1-digest")) + //-- This is a digest line, save the data in the appropriate place + { + if(aFileType == JAR_MF) + curItemMF->storedEntryDigest = lineData; + else + storedSectionDigest = lineData; + continue; + } + + // (2) Name: associates this manifest section with a file in the jar. + if (!foundName && lineName.LowerCaseEqualsLiteral("name")) + { + curItemName = lineData; + foundName = true; + continue; + } + + // (3) Magic: this may be an inline Javascript. + // We can't do any other kind of magic. + if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic")) + { + if (lineData.LowerCaseEqualsLiteral("javascript")) + curItemMF->mType = JAR_EXTERNAL; + else + curItemMF->mType = JAR_INVALID; + continue; + } + + } // for (;;) + return NS_OK; +} //ParseOneFile() + +nsresult +nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData, + uint32_t aLen) +{ + if (aManItem->status == JAR_VALID_MANIFEST) + { + if (aManItem->storedEntryDigest.IsEmpty()) + // No entry digests in manifest file. Entry is unsigned. + aManItem->status = JAR_NOT_SIGNED; + else + { //-- Calculate and compare digests + nsCString calculatedEntryDigest; + nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest)) + aManItem->status = JAR_INVALID_ENTRY; + aManItem->storedEntryDigest.Truncate(); + } + } + aManItem->entryVerified = true; + return NS_OK; +} + +void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode) +{ + //-- Generate error message + nsAutoString message; + message.AssignLiteral("Signature Verification Error: the signature on "); + if (!aFilename.IsEmpty()) + AppendASCIItoUTF16(aFilename, message); + else + message.AppendLiteral("this .jar archive"); + message.AppendLiteral(" is invalid because "); + switch(errorCode) + { + case JAR_NOT_SIGNED: + message.AppendLiteral("the archive did not contain a valid PKCS7 signature."); + break; + case JAR_INVALID_SIG: + message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF)."); + break; + case JAR_INVALID_UNKNOWN_CA: + message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer."); + break; + case JAR_INVALID_MANIFEST: + message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file."); + break; + case JAR_INVALID_ENTRY: + message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified."); + break; + case JAR_NO_MANIFEST: + message.AppendLiteral("the archive did not contain a manifest."); + break; + default: + message.AppendLiteral("of an unknown problem."); + } + + // Report error in JS console + nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1")); + if (console) + { + console->LogStringMessage(message.get()); + } +#ifdef DEBUG + char* messageCstr = ToNewCString(message); + if (!messageCstr) return; + fprintf(stderr, "%s\n", messageCstr); + free(messageCstr); +#endif +} + + +nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen, + nsCString& digest) +{ + nsresult rv; + + nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_FAILED(rv)) return rv; + + rv = hasher->Init(nsICryptoHash::SHA1); + if (NS_FAILED(rv)) return rv; + + rv = hasher->Update((const uint8_t*) aInBuf, aLen); + if (NS_FAILED(rv)) return rv; + + return hasher->Finish(true, digest); +} + +NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator) + +//---------------------------------------------- +// nsJAREnumerator::HasMore +//---------------------------------------------- +NS_IMETHODIMP +nsJAREnumerator::HasMore(bool* aResult) +{ + // try to get the next element + if (!mName) { + NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind."); + nsresult rv = mFind->FindNext( &mName, &mNameLen ); + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + *aResult = false; // No more matches available + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation + } + + *aResult = true; + return NS_OK; +} + +//---------------------------------------------- +// nsJAREnumerator::GetNext +//---------------------------------------------- +NS_IMETHODIMP +nsJAREnumerator::GetNext(nsACString& aResult) +{ + // check if the current item is "stale" + if (!mName) { + bool bMore; + nsresult rv = HasMore(&bMore); + if (NS_FAILED(rv) || !bMore) + return NS_ERROR_FAILURE; // no error translation + } + aResult.Assign(mName, mNameLen); + mName = 0; // we just gave this one away + return NS_OK; +} + + +NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry) + +nsJARItem::nsJARItem(nsZipItem* aZipItem) + : mSize(aZipItem->Size()), + mRealsize(aZipItem->RealSize()), + mCrc32(aZipItem->CRC32()), + mLastModTime(aZipItem->LastModTime()), + mCompression(aZipItem->Compression()), + mPermissions(aZipItem->Mode()), + mIsDirectory(aZipItem->IsDirectory()), + mIsSynthetic(aZipItem->isSynthetic) +{ +} + +//------------------------------------------ +// nsJARItem::GetCompression +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetCompression(uint16_t *aCompression) +{ + NS_ENSURE_ARG_POINTER(aCompression); + + *aCompression = mCompression; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetSize +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetSize(uint32_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + + *aSize = mSize; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetRealSize +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetRealSize(uint32_t *aRealsize) +{ + NS_ENSURE_ARG_POINTER(aRealsize); + + *aRealsize = mRealsize; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetCrc32 +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetCRC32(uint32_t *aCrc32) +{ + NS_ENSURE_ARG_POINTER(aCrc32); + + *aCrc32 = mCrc32; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetIsDirectory +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetIsDirectory(bool *aIsDirectory) +{ + NS_ENSURE_ARG_POINTER(aIsDirectory); + + *aIsDirectory = mIsDirectory; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetIsSynthetic +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetIsSynthetic(bool *aIsSynthetic) +{ + NS_ENSURE_ARG_POINTER(aIsSynthetic); + + *aIsSynthetic = mIsSynthetic; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetLastModifiedTime +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) +{ + NS_ENSURE_ARG_POINTER(aLastModTime); + + *aLastModTime = mLastModTime; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetPermissions +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetPermissions(uint32_t* aPermissions) +{ + NS_ENSURE_ARG_POINTER(aPermissions); + + *aPermissions = mPermissions; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIZipReaderCache + +NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference) + +nsZipReaderCache::nsZipReaderCache() + : mLock("nsZipReaderCache.mLock") + , mCacheSize(0) + , mZips() +#ifdef ZIP_CACHE_HIT_RATE + , + mZipCacheLookups(0), + mZipCacheHits(0), + mZipCacheFlushes(0), + mZipSyncMisses(0) +#endif +{ +} + +NS_IMETHODIMP +nsZipReaderCache::Init(uint32_t cacheSize) +{ + mCacheSize = cacheSize; + +// Register as a memory pressure observer + nsCOMPtr<nsIObserverService> os = + do_GetService("@mozilla.org/observer-service;1"); + if (os) + { + os->AddObserver(this, "memory-pressure", true); + os->AddObserver(this, "chrome-flush-caches", true); + os->AddObserver(this, "flush-cache-entry", true); + } +// ignore failure of the observer registration. + + return NS_OK; +} + +nsZipReaderCache::~nsZipReaderCache() +{ + for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->SetZipReaderCache(nullptr); + } + +#ifdef ZIP_CACHE_HIT_RATE + printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n", + mCacheSize, mZipCacheHits, mZipCacheLookups, + (float)mZipCacheHits / mZipCacheLookups, + mZipCacheFlushes, mZipSyncMisses); +#endif +} + +NS_IMETHODIMP +nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(zipFile); + nsresult rv; + MutexAutoLock lock(mLock); + + nsAutoCString uri; + rv = zipFile->GetNativePath(uri); + if (NS_FAILED(rv)) + return rv; + + uri.Insert(NS_LITERAL_CSTRING("file:"), 0); + + *aResult = mZips.Contains(uri); + return NS_OK; +} + +NS_IMETHODIMP +nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result) +{ + NS_ENSURE_ARG_POINTER(zipFile); + nsresult rv; + MutexAutoLock lock(mLock); + +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheLookups++; +#endif + + nsAutoCString uri; + rv = zipFile->GetNativePath(uri); + if (NS_FAILED(rv)) return rv; + + uri.Insert(NS_LITERAL_CSTRING("file:"), 0); + + RefPtr<nsJAR> zip; + mZips.Get(uri, getter_AddRefs(zip)); + if (zip) { +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheHits++; +#endif + zip->ClearReleaseTime(); + } else { + zip = new nsJAR(); + zip->SetZipReaderCache(this); + rv = zip->Open(zipFile); + if (NS_FAILED(rv)) { + return rv; + } + + MOZ_ASSERT(!mZips.Contains(uri)); + mZips.Put(uri, zip); + } + zip.forget(result); + return rv; +} + +NS_IMETHODIMP +nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry, + nsIZipReader* *result) +{ + NS_ENSURE_ARG_POINTER(zipFile); + + nsCOMPtr<nsIZipReader> outerZipReader; + nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader)); + NS_ENSURE_SUCCESS(rv, rv); + + MutexAutoLock lock(mLock); + +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheLookups++; +#endif + + nsAutoCString uri; + rv = zipFile->GetNativePath(uri); + if (NS_FAILED(rv)) return rv; + + uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); + uri.AppendLiteral("!/"); + uri.Append(entry); + + RefPtr<nsJAR> zip; + mZips.Get(uri, getter_AddRefs(zip)); + if (zip) { +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheHits++; +#endif + zip->ClearReleaseTime(); + } else { + zip = new nsJAR(); + zip->SetZipReaderCache(this); + + rv = zip->OpenInner(outerZipReader, entry); + if (NS_FAILED(rv)) { + return rv; + } + + MOZ_ASSERT(!mZips.Contains(uri)); + mZips.Put(uri, zip); + } + zip.forget(result); + return rv; +} + +NS_IMETHODIMP +nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal) +{ +#if defined(XP_WIN) + MOZ_CRASH("Not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +#else + if (!zipFile) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsAutoCString uri; + rv = zipFile->GetNativePath(uri); + if (NS_FAILED(rv)) { + return rv; + } + uri.Insert(NS_LITERAL_CSTRING("file:"), 0); + + MutexAutoLock lock(mLock); + RefPtr<nsJAR> zip; + mZips.Get(uri, getter_AddRefs(zip)); + if (!zip) { + return NS_ERROR_FAILURE; + } + + zip->ClearReleaseTime(); + rv = zip->GetNSPRFileDesc(aRetVal); + // Do this to avoid possible deadlock on mLock with ReleaseZip(). + MutexAutoUnlock unlock(mLock); + RefPtr<nsJAR> zipTemp = zip.forget(); + return rv; +#endif /* XP_WIN */ +} + +nsresult +nsZipReaderCache::ReleaseZip(nsJAR* zip) +{ + nsresult rv; + MutexAutoLock lock(mLock); + + // It is possible that two thread compete for this zip. The dangerous + // case is where one thread Releases the zip and discovers that the ref + // count has gone to one. Before it can call this ReleaseZip method + // another thread calls our GetZip method. The ref count goes to two. That + // second thread then Releases the zip and the ref count goes to one. It + // then tries to enter this ReleaseZip method and blocks while the first + // thread is still here. The first thread continues and remove the zip from + // the cache and calls its Release method sending the ref count to 0 and + // deleting the zip. However, the second thread is still blocked at the + // start of ReleaseZip, but the 'zip' param now hold a reference to a + // deleted zip! + // + // So, we are going to try safeguarding here by searching our hashtable while + // locked here for the zip. We return fast if it is not found. + + bool found = false; + for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { + if (zip == iter.UserData()) { + found = true; + break; + } + } + + if (!found) { +#ifdef ZIP_CACHE_HIT_RATE + mZipSyncMisses++; +#endif + return NS_OK; + } + + zip->SetReleaseTime(); + + if (mZips.Count() <= mCacheSize) + return NS_OK; + + // Find the oldest zip. + nsJAR* oldest = nullptr; + for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { + nsJAR* current = iter.UserData(); + PRIntervalTime currentReleaseTime = current->GetReleaseTime(); + if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) { + if (oldest == nullptr || + currentReleaseTime < oldest->GetReleaseTime()) { + oldest = current; + } + } + } + + // Because of the craziness above it is possible that there is no zip that + // needs removing. + if (!oldest) + return NS_OK; + +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheFlushes++; +#endif + + // remove from hashtable + nsAutoCString uri; + rv = oldest->GetJarPath(uri); + if (NS_FAILED(rv)) + return rv; + + if (oldest->mOuterZipEntry.IsEmpty()) { + uri.Insert(NS_LITERAL_CSTRING("file:"), 0); + } else { + uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); + uri.AppendLiteral("!/"); + uri.Append(oldest->mOuterZipEntry); + } + + // Retrieving and removing the JAR must be done without an extra AddRef + // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case + // an extra time and trigger a deadlock. + RefPtr<nsJAR> removed; + mZips.Remove(uri, getter_AddRefs(removed)); + NS_ASSERTION(removed, "botched"); + NS_ASSERTION(oldest == removed, "removed wrong entry"); + + if (removed) + removed->SetZipReaderCache(nullptr); + + return NS_OK; +} + +NS_IMETHODIMP +nsZipReaderCache::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aSomeData) +{ + if (strcmp(aTopic, "memory-pressure") == 0) { + MutexAutoLock lock(mLock); + for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { + RefPtr<nsJAR>& current = iter.Data(); + if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) { + current->SetZipReaderCache(nullptr); + iter.Remove(); + } + } + } + else if (strcmp(aTopic, "chrome-flush-caches") == 0) { + MutexAutoLock lock(mLock); + for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->SetZipReaderCache(nullptr); + } + mZips.Clear(); + } + else if (strcmp(aTopic, "flush-cache-entry") == 0) { + nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject); + if (!file) + return NS_OK; + + nsAutoCString uri; + if (NS_FAILED(file->GetNativePath(uri))) + return NS_OK; + + uri.Insert(NS_LITERAL_CSTRING("file:"), 0); + + MutexAutoLock lock(mLock); + + RefPtr<nsJAR> zip; + mZips.Get(uri, getter_AddRefs(zip)); + if (!zip) + return NS_OK; + +#ifdef ZIP_CACHE_HIT_RATE + mZipCacheFlushes++; +#endif + + zip->SetZipReaderCache(nullptr); + + mZips.Remove(uri); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// |