From 395abf823fc9c5988d357729c4d921b7610a6bdf Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 20 Oct 2016 09:44:33 +0000 Subject: [PATCH] Bug 1288104 part 2 - Instrument SRICheckDataVerifier to load/save the computed hash from the bytecode cache. r=francois --- dom/security/SRICheck.cpp | 135 +++++++++++++++++++++++++++++++++++++- dom/security/SRICheck.h | 41 ++++++++++++ xpcom/base/ErrorList.h | 2 + 3 files changed, 176 insertions(+), 2 deletions(-) diff --git a/dom/security/SRICheck.cpp b/dom/security/SRICheck.cpp index a4f81d7745ea..534909f81aa1 100644 --- a/dom/security/SRICheck.cpp +++ b/dom/security/SRICheck.cpp @@ -23,6 +23,8 @@ #include "nsNetUtil.h" #include "nsWhitespaceTokenizer.h" +#define SRIVERBOSE(args) \ + MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Verbose, args) #define SRILOG(args) \ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args) #define SRIERROR(args) \ @@ -242,8 +244,7 @@ SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata, return; // ignore invalid metadata for forward-compatibility } - uint32_t hashLength; - aMetadata.GetHashType(&mHashType, &hashLength); + aMetadata.GetHashType(&mHashType, &mHashLength); } nsresult @@ -408,5 +409,135 @@ SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata, return NS_ERROR_SRI_CORRUPT; } +uint32_t +SRICheckDataVerifier::DataSummaryLength() +{ + MOZ_ASSERT(!mInvalidMetadata); + return sizeof(mHashType) + sizeof(mHashLength) + mHashLength; +} + +uint32_t +SRICheckDataVerifier::EmptyDataSummaryLength() +{ + return sizeof(int8_t) + sizeof(uint32_t); +} + +nsresult +SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length) +{ + *length = 0; + NS_ENSURE_ARG_POINTER(aData); + + // we expect to always encode an SRI, even if it is empty or incomplete + if (aDataLen < EmptyDataSummaryLength()) { + SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too small", aDataLen)); + return NS_ERROR_SRI_IMPORT; + } + + // decode the content of the buffer + size_t offset = sizeof(mHashType); + size_t len = *reinterpret_cast(&aData[offset]); + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + if (offset + len > aDataLen) { + SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow the buffer size", aDataLen)); + SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]", + uint32_t(offset), uint32_t(len))); + return NS_ERROR_SRI_IMPORT; + } + *length = uint32_t(offset + len); + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen, const uint8_t* aData) +{ + MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid + MOZ_ASSERT(!mCryptoHash); // EnsureCryptoHash should not have been called + NS_ENSURE_ARG_POINTER(aData); + if (mInvalidMetadata) { + return NS_OK; // ignoring any data updates, see mInvalidMetadata usage + } + + // we expect to always encode an SRI, even if it is empty or incomplete + if (aDataLen < DataSummaryLength()) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too small", aDataLen)); + return NS_ERROR_SRI_IMPORT; + } + + SRIVERBOSE(("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + // decode the content of the buffer + size_t offset = 0; + if (*reinterpret_cast(&aData[offset]) != mHashType) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not match[%d]", + *reinterpret_cast(&aData[offset]), + mHashType)); + return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE; + } + offset += sizeof(mHashType); + + if (*reinterpret_cast(&aData[offset]) != mHashLength) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not match[%d]", + *reinterpret_cast(&aData[offset]), + mHashLength)); + return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE; + } + offset += sizeof(mHashLength); + + // copy the hash to mComputedHash, as-if we had finished streaming the bytes + mComputedHash.Assign(reinterpret_cast(&aData[offset]), mHashLength); + mCryptoHash = nullptr; + mComplete = true; + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen, uint8_t* aData) +{ + MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid + MOZ_ASSERT(mComplete); // finished streaming + NS_ENSURE_ARG_POINTER(aData); + NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG); + + // serialize the hash in the buffer + size_t offset = 0; + *reinterpret_cast(&aData[offset]) = mHashType; + offset += sizeof(mHashType); + *reinterpret_cast(&aData[offset]) = mHashLength; + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + // copy the hash to mComputedHash, as-if we had finished streaming the bytes + nsCharTraits::copy(reinterpret_cast(&aData[offset]), + mComputedHash.get(), mHashLength); + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData) +{ + NS_ENSURE_ARG_POINTER(aData); + NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG); + + // serialize an unknown hash in the buffer, to be able to skip it later + size_t offset = 0; + *reinterpret_cast(&aData[offset]) = 0; + offset += sizeof(mHashType); + *reinterpret_cast(&aData[offset]) = 0; + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/security/SRICheck.h b/dom/security/SRICheck.h index 88aefa91039f..82929fe36a89 100644 --- a/dom/security/SRICheck.h +++ b/dom/security/SRICheck.h @@ -45,6 +45,20 @@ public: nsIConsoleReportCollector* aReporter); }; +// The SRICheckDataVerifier can be used in 2 different mode: +// +// 1. The streaming mode involves reading bytes from an input, and to use +// the |Update| function to stream new bytes, and to use the |Verify| +// function to check the hash of the content with the hash provided by +// the metadata. +// +// Optionally, one can serialize the verified hash with |ExportDataSummary|, +// in a buffer in order to rely on the second mode the next time. +// +// 2. The pre-computed mode, involves reading a hash with |ImportDataSummary|, +// which got exported by the SRICheckDataVerifier and potentially cached, and +// then use the |Verify| function to check against the hash provided by the +// metadata. class SRICheckDataVerifier final { public: @@ -52,15 +66,42 @@ class SRICheckDataVerifier final const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter); + // Append the following bytes to the content used to compute the hash. Once + // all bytes are streamed, use the Verify function to check the integrity. nsresult Update(uint32_t aStringLen, const uint8_t* aString); + + // Verify that the computed hash corresponds to the metadata. nsresult Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel, const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter); + bool IsComplete() const { + return mComplete; + } + + // Report the length of the computed hash and its type, such that we can + // reserve the space for encoding it in a vector. + uint32_t DataSummaryLength(); + static uint32_t EmptyDataSummaryLength(); + + // Write the computed hash and its type in a pre-allocated buffer. + nsresult ExportDataSummary(uint32_t aDataLen, uint8_t* aData); + static nsresult ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData); + + // Report the length of the computed hash and its type, such that we can + // skip these data while reading a buffer. + static nsresult DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length); + + // Extract the computed hash and its type, such that we can |Verify| if it + // matches the metadata. The buffer should be at least the same size or + // larger than the value returned by |DataSummaryLength|. + nsresult ImportDataSummary(uint32_t aDataLen, const uint8_t* aData); + private: nsCOMPtr mCryptoHash; nsAutoCString mComputedHash; size_t mBytesHashed; + uint32_t mHashLength; int8_t mHashType; bool mInvalidMetadata; bool mComplete; diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 533c0d01d709..04b357ab6f71 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -696,6 +696,8 @@ ERROR(NS_ERROR_SRI_CORRUPT, FAILURE(200)), ERROR(NS_ERROR_SRI_DISABLED, FAILURE(201)), ERROR(NS_ERROR_SRI_NOT_ELIGIBLE, FAILURE(202)), + ERROR(NS_ERROR_SRI_UNEXPECTED_HASH_TYPE, FAILURE(203)), + ERROR(NS_ERROR_SRI_IMPORT, FAILURE(204)), /* CMS specific nsresult error codes. Note: the numbers used here correspond * to the values in nsICMSMessageErrors.idl. */