Bug 1201796 (Part 1) - Treat ICOs with wrong widths and heights as corrupt. r=tn
☠☠ backed out by 9244da13f5e8 ☠ ☠
authorSeth Fowler <mark.seth.fowler@gmail.com>
Fri, 18 Sep 2015 10:54:32 -0700
changeset 295914 d58149411b7df9eb757f4b6089b6f73b90fa886a
parent 295913 35bd769b49f8cabf9dff6d3b2ec0976fe01f85cf
child 295915 494e7553d6419f9dc7be7003a2448eb508469691
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1201796
milestone43.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 1201796 (Part 1) - Treat ICOs with wrong widths and heights as corrupt. r=tn
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/test/crashtests/crashtests.list
image/test/crashtests/invalid_ico_height.ico
image/test/crashtests/invalid_ico_width.ico
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
image/test/reftest/ico/ico-bmp-corrupted/reftest.list
image/test/reftest/ico/ico-png/ico-size-256x256-png.ico
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -307,16 +307,21 @@ nsICODecoder::ReadDirEntry(const char* a
       return Transition::Terminate(ICOState::FAILURE);
     }
 
     mBestResourceColorDepth = e.mBitCount;
     mDirEntry = e;
   }
 
   if (mCurrIcon == mNumIcons) {
+    PostSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry));
+    if (IsMetadataDecode()) {
+      return Transition::Terminate(ICOState::SUCCESS);
+    }
+
     size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
     return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
                                     ICOState::SKIP_TO_RESOURCE,
                                     offsetToResource);
   }
 
   return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
 }
@@ -377,25 +382,16 @@ nsICODecoder::SniffResource(const char* 
 
 LexerTransition<ICOState>
 nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
 {
   if (!WriteToContainedDecoder(aData, aLen)) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
-  if (!HasSize() && mContainedDecoder->HasSize()) {
-    nsIntSize size = mContainedDecoder->GetSize();
-    PostSize(size.width, size.height);
-
-    if (IsMetadataDecode()) {
-      return Transition::Terminate(ICOState::SUCCESS);
-    }
-  }
-
   // Raymond Chen says that 32bpp only are valid PNG ICOs
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   if (!IsMetadataDecode() &&
       !static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   return Transition::ContinueUnbuffered(ICOState::READ_PNG);
@@ -439,24 +435,16 @@ nsICODecoder::ReadBIH(const char* aData)
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   // Write out the BMP's bitmap info header.
   if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
-  nsIntSize size = mContainedDecoder->GetSize();
-  PostSize(size.width, size.height);
-
-  // We have the size. If we're doing a metadata decode, we're done.
-  if (IsMetadataDecode()) {
-    return Transition::Terminate(ICOState::SUCCESS);
-  }
-
   // Sometimes the ICO BPP header field is not filled out so we should trust the
   // contained resource over our own information.
   // XXX(seth): Is this ever different than the value we obtained from
   // ReadBPP() above?
   nsRefPtr<nsBMPDecoder> bmpDecoder =
     static_cast<nsBMPDecoder*>(mContainedDecoder.get());
   mBPP = bmpDecoder->GetBitsPerPixel();
 
@@ -569,16 +557,30 @@ nsICODecoder::ReadMaskRow(const char* aD
 
   if (mCurrMaskLine == 0) {
     return Transition::To(ICOState::FINISHED_RESOURCE, 0);
   }
 
   return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
 }
 
+LexerTransition<ICOState>
+nsICODecoder::FinishResource()
+{
+  // Make sure the actual size of the resource matches the size in the directory
+  // entry. If not, we consider the image corrupt.
+  IntSize expectedSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry));
+  if (mContainedDecoder->HasSize() &&
+      mContainedDecoder->GetSize() != expectedSize) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  return Transition::Terminate(ICOState::SUCCESS);
+}
+
 void
 nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
   MOZ_ASSERT(aBuffer);
   MOZ_ASSERT(aCount > 0);
 
   Maybe<ICOState> terminalState =
@@ -603,17 +605,17 @@ nsICODecoder::WriteInternal(const char* 
           return ReadBMP(aData, aLength);
         case ICOState::PREPARE_FOR_MASK:
           return PrepareForMask();
         case ICOState::READ_MASK_ROW:
           return ReadMaskRow(aData);
         case ICOState::SKIP_MASK:
           return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
         case ICOState::FINISHED_RESOURCE:
-          return Transition::Terminate(ICOState::SUCCESS);
+          return FinishResource();
         default:
           MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
           return Transition::Terminate(ICOState::FAILURE);
       }
     });
 
   if (!terminalState) {
     return;  // Need more data.
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -110,16 +110,17 @@ private:
   LexerTransition<ICOState> ReadHeader(const char* aData);
   LexerTransition<ICOState> ReadDirEntry(const char* aData);
   LexerTransition<ICOState> SniffResource(const char* aData);
   LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> ReadBIH(const char* aData);
   LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> PrepareForMask();
   LexerTransition<ICOState> ReadMaskRow(const char* aData);
+  LexerTransition<ICOState> FinishResource();
 
   StreamingLexer<ICOState, 32> mLexer; // The lexer.
   nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
   gfx::IntSize mResolution;            // The requested -moz-resolution.
   char mBIHraw[40];                    // The bitmap information header.
   IconDirEntry mDirEntry;              // The dir entry for the selected resource.
   int32_t mBestResourceDelta;          // Used to select the best resource.
   uint16_t mBestResourceColorDepth;    // Used to select the best resource.
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -47,8 +47,13 @@ load multiple-png-hassize.ico
 load 856616.gif
 
 skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg
 
 # Bug 1160801: Ensure that we handle invalid disposal types.
 load invalid-disposal-method-1.gif
 load invalid-disposal-method-2.gif
 load invalid-disposal-method-3.gif
+
+# Ensure we handle ICO directory entries which specify the wrong size for the
+# contained resource.
+load invalid_ico_height.ico
+load invalid_ico_width.ico
rename from image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
rename to image/test/crashtests/invalid_ico_height.ico
rename from image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
rename to image/test/crashtests/invalid_ico_width.ico
--- a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list
+++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list
@@ -3,13 +3,8 @@
 # Invalid value for bits per pixel (BPP) - detected when decoding the header.
 == wrapper.html?invalid-bpp.ico about:blank
 # Invalid BPP values for RLE4 - detected when decoding the image data.
 == wrapper.html?invalid-compression-RLE4.ico about:blank
 # Invalid BPP values for RLE8 - detected when decoding the image data.
 == wrapper.html?invalid-compression-RLE8.ico about:blank
 # Invalid compression value - detected when decoding the image data.
 == wrapper.html?invalid-compression.ico about:blank
-
-# Invalid ICO width and heigth should be ignored if the
-# contained BMP is correct.
-== invalid_ico_height.ico 16x16.png
-== invalid_ico_width.ico 16x16.png
index edbaf544349242589fc73db180b49af95c00f229..ecb88edf3ca8ff586d009cac9d2b1cb40aff8999
GIT binary patch
literal 5934
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
Kj8R5+;{FG3NOFPz