Bug 1288104 part 2 - Instrument SRICheckDataVerifier to load/save the computed hash from the bytecode cache. r=francois
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 20 Oct 2016 09:44:33 +0000
changeset 318826 3ae084908fc41b787076e13a5253cc61d912bbc4
parent 318825 4a8c5061f3b753cb34028b59c9ccc654027ba021
child 318827 e30472b2861ab0f353e59964018bbee6dcc63235
push id30854
push userryanvm@gmail.com
push dateFri, 21 Oct 2016 21:08:02 +0000
treeherdermozilla-central@806054dd12bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrancois
bugs1288104
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288104 part 2 - Instrument SRICheckDataVerifier to load/save the computed hash from the bytecode cache. r=francois
dom/security/SRICheck.cpp
dom/security/SRICheck.h
xpcom/base/ErrorList.h
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -18,16 +18,18 @@
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIIncrementalStreamLoader.h"
 #include "nsIUnicharStreamLoader.h"
 #include "nsIURI.h"
 #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)                                                         \
   MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args)
 
 namespace mozilla {
 namespace dom {
 
@@ -237,18 +239,17 @@ SRICheckDataVerifier::SRICheckDataVerifi
                                 nsContentUtils::eSECURITY_PROPERTIES,
                                 aSourceFileURI, 0, 0,
                                 NS_LITERAL_CSTRING("NoValidMetadata"),
                                 const_cast<const nsTArray<nsString>&>(params));
     mInvalidMetadata = true;
     return; // ignore invalid metadata for forward-compatibility
   }
 
-  uint32_t hashLength;
-  aMetadata.GetHashType(&mHashType, &hashLength);
+  aMetadata.GetHashType(&mHashType, &mHashLength);
 }
 
 nsresult
 SRICheckDataVerifier::EnsureCryptoHash()
 {
   MOZ_ASSERT(!mInvalidMetadata);
 
   if (mCryptoHash) {
@@ -403,10 +404,140 @@ SRICheckDataVerifier::Verify(const SRIMe
                               NS_LITERAL_CSTRING("Sub-resource Integrity"),
                               nsContentUtils::eSECURITY_PROPERTIES,
                               aSourceFileURI, 0, 0,
                               NS_LITERAL_CSTRING("IntegrityMismatch"),
                               const_cast<const nsTArray<nsString>&>(params));
   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<const decltype(mHashLength)*>(&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<const decltype(mHashType)*>(&aData[offset]) != mHashType) {
+    SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not match[%d]",
+            *reinterpret_cast<const decltype(mHashType)*>(&aData[offset]),
+             mHashType));
+    return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
+  }
+  offset += sizeof(mHashType);
+
+  if (*reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]) != mHashLength) {
+    SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not match[%d]",
+            *reinterpret_cast<const decltype(mHashLength)*>(&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<const char*>(&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<decltype(mHashType)*>(&aData[offset]) = mHashType;
+  offset += sizeof(mHashType);
+  *reinterpret_cast<decltype(mHashLength)*>(&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<char>::copy(reinterpret_cast<char*>(&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<decltype(mHashType)*>(&aData[offset]) = 0;
+  offset += sizeof(mHashType);
+  *reinterpret_cast<decltype(mHashLength)*>(&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
--- a/dom/security/SRICheck.h
+++ b/dom/security/SRICheck.h
@@ -40,32 +40,73 @@ public:
    */
   static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
                                   nsIUnicharStreamLoader* aLoader,
                                   const nsAString& aString,
                                   const nsACString& aSourceFileURI,
                                   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:
     SRICheckDataVerifier(const SRIMetadata& aMetadata,
                          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<nsICryptoHash> mCryptoHash;
     nsAutoCString           mComputedHash;
     size_t                  mBytesHashed;
+    uint32_t                mHashLength;
     int8_t                  mHashType;
     bool                    mInvalidMetadata;
     bool                    mComplete;
 
     nsresult EnsureCryptoHash();
     nsresult Finish();
     nsresult VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
                         const nsACString& aSourceFileURI,
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -691,16 +691,18 @@
   /* Error code for CSP */
   ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION,        FAILURE(98)),
   ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION,     FAILURE(99)),
 
   /* Error code for Sub-Resource Integrity */
   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. */
   ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED,            FAILURE(1024)),
   ERROR(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO,       FAILURE(1025)),
   ERROR(NS_ERROR_CMS_VERIFY_BAD_DIGEST,            FAILURE(1026)),
   ERROR(NS_ERROR_CMS_VERIFY_NOCERT,                FAILURE(1028)),
   ERROR(NS_ERROR_CMS_VERIFY_UNTRUSTED,             FAILURE(1029)),