Bug 1201796 (Part 1) - Treat ICOs with wrong widths and heights as corrupt. r=tn
☠☠ backed out by 577c248da8de ☠ ☠
authorSeth Fowler <mark.seth.fowler@gmail.com>
Sat, 19 Sep 2015 13:34:06 -0700
changeset 296033 4ce4da0007104e7e45c30bb7712ff55725ca0faf
parent 296032 3d9c627fc1c5f623e63e0ff7a3758f6975b9db9b
child 296034 7f20c039c994ae7c3d49ee1a3765c89fd349d462
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