Time channel attack on SVG Filters
authorsaid@apple.com <said@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Mar 2017 21:51:03 +0000 (21:51 +0000)
committersaid@apple.com <said@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Mar 2017 21:51:03 +0000 (21:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=118689

Reviewed by Simon Fraser.

Source/WebCore:

The time channel attack can happen if the attacker applies FEColorMatrix
or FEConvolveMatrix and provides a matrix which is filled with subnormal
floating point values. Performing floating-point operations on subnormals
is very expensive unless the pixel in the source graphics is black (or
zero). By measuring the time a filter takes to be applied, the attacker
can know whether the pixel he wants to steal from  an iframe is black or
white. By repeating the same process on all the pixels in the iframe, the
attacker can reconstruct the whole page of the iframe.

To fix this issue, the values in the matrices of these filters will clamped
to FLT_MIN. We do not want to consume too much time calculating filtered
pixels because of such tiny values. The difference between applying FLT_MIN
and applying a subnormal should not be even noticeable. Normalizing the
floating-point matrices should happen only at the beginning of the filter
platformApplySoftware().

* platform/graphics/filters/FEColorMatrix.cpp:
(WebCore::FEColorMatrix::platformApplySoftware):
* platform/graphics/filters/FEConvolveMatrix.cpp:
(WebCore::FEConvolveMatrix::fastSetInteriorPixels):
(WebCore::FEConvolveMatrix::fastSetOuterPixels):
(WebCore::FEConvolveMatrix::platformApplySoftware):
* platform/graphics/filters/FEConvolveMatrix.h:
* platform/graphics/filters/FilterEffect.h:
(WebCore::FilterEffect::normalizedFloats):

Source/WTF:

Performing arithmetic operations on subnormal floating-point numbers is
very expensive. Normalizing the floating-point number to the minimum normal
value should accelerate the calculations and there won't be a noticeable
difference in the result since all the subnormal values and the minimum
normal value are all very close to zero.

* wtf/MathExtras.h:
(normalizedFloat):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@214125 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WTF/ChangeLog
Source/WTF/wtf/MathExtras.h
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/filters/FEColorMatrix.cpp
Source/WebCore/platform/graphics/filters/FEConvolveMatrix.cpp
Source/WebCore/platform/graphics/filters/FEConvolveMatrix.h
Source/WebCore/platform/graphics/filters/FilterEffect.h

index acf0967..59a9c69 100644 (file)
@@ -1,3 +1,19 @@
+2017-03-17  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Time channel attack on SVG Filters
+        https://bugs.webkit.org/show_bug.cgi?id=118689
+
+        Reviewed by Simon Fraser.
+
+        Performing arithmetic operations on subnormal floating-point numbers is
+        very expensive. Normalizing the floating-point number to the minimum normal
+        value should accelerate the calculations and there won't be a noticeable
+        difference in the result since all the subnormal values and the minimum
+        normal value are all very close to zero.
+
+        * wtf/MathExtras.h:
+        (normalizedFloat):
+
 2017-03-11  Filip Pizlo  <fpizlo@apple.com>
 
         Air should be powerful enough to support Tmp-splitting
index 2c7b148..78daeaf 100644 (file)
@@ -209,6 +209,15 @@ inline bool isWithinIntRange(float x)
     return x > static_cast<float>(std::numeric_limits<int>::min()) && x < static_cast<float>(std::numeric_limits<int>::max());
 }
 
+inline float normalizedFloat(float value)
+{
+    if (value > 0 && value < std::numeric_limits<float>::min())
+        return std::numeric_limits<float>::min();
+    if (value < 0 && value > -std::numeric_limits<float>::min())
+        return -std::numeric_limits<float>::min();
+    return value;
+}
+
 template<typename T> inline bool hasOneBitSet(T value)
 {
     return !((value - 1) & value) && value;
index 17f3f2e..a8a4a27 100644 (file)
@@ -1,3 +1,36 @@
+2017-03-17  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Time channel attack on SVG Filters
+        https://bugs.webkit.org/show_bug.cgi?id=118689
+
+        Reviewed by Simon Fraser.
+
+        The time channel attack can happen if the attacker applies FEColorMatrix
+        or FEConvolveMatrix and provides a matrix which is filled with subnormal
+        floating point values. Performing floating-point operations on subnormals
+        is very expensive unless the pixel in the source graphics is black (or
+        zero). By measuring the time a filter takes to be applied, the attacker
+        can know whether the pixel he wants to steal from  an iframe is black or
+        white. By repeating the same process on all the pixels in the iframe, the
+        attacker can reconstruct the whole page of the iframe.
+
+        To fix this issue, the values in the matrices of these filters will clamped
+        to FLT_MIN. We do not want to consume too much time calculating filtered
+        pixels because of such tiny values. The difference between applying FLT_MIN
+        and applying a subnormal should not be even noticeable. Normalizing the
+        floating-point matrices should happen only at the beginning of the filter
+        platformApplySoftware().
+
+        * platform/graphics/filters/FEColorMatrix.cpp:
+        (WebCore::FEColorMatrix::platformApplySoftware):
+        * platform/graphics/filters/FEConvolveMatrix.cpp:
+        (WebCore::FEConvolveMatrix::fastSetInteriorPixels):
+        (WebCore::FEConvolveMatrix::fastSetOuterPixels):
+        (WebCore::FEConvolveMatrix::platformApplySoftware):
+        * platform/graphics/filters/FEConvolveMatrix.h:
+        * platform/graphics/filters/FilterEffect.h:
+        (WebCore::FilterEffect::normalizedFloats):
+
 2017-03-17  Jer Noble  <jer.noble@apple.com>
 
         Explicitly resize the audio buffer in RealtimeOutgoingAudioSource.
index 6860d7d..2320d52 100644 (file)
@@ -153,21 +153,22 @@ void FEColorMatrix::platformApplySoftware()
 
     IntRect imageRect(IntPoint(), resultImage->logicalSize());
     RefPtr<Uint8ClampedArray> pixelArray = resultImage->getUnmultipliedImageData(imageRect);
+    Vector<float> values = normalizedFloats(m_values);
 
     switch (m_type) {
     case FECOLORMATRIX_TYPE_UNKNOWN:
         break;
     case FECOLORMATRIX_TYPE_MATRIX:
-        effectType<FECOLORMATRIX_TYPE_MATRIX>(pixelArray.get(), m_values);
+        effectType<FECOLORMATRIX_TYPE_MATRIX>(pixelArray.get(), values);
         break;
     case FECOLORMATRIX_TYPE_SATURATE: 
-        effectType<FECOLORMATRIX_TYPE_SATURATE>(pixelArray.get(), m_values);
+        effectType<FECOLORMATRIX_TYPE_SATURATE>(pixelArray.get(), values);
         break;
     case FECOLORMATRIX_TYPE_HUEROTATE:
-        effectType<FECOLORMATRIX_TYPE_HUEROTATE>(pixelArray.get(), m_values);
+        effectType<FECOLORMATRIX_TYPE_HUEROTATE>(pixelArray.get(), values);
         break;
     case FECOLORMATRIX_TYPE_LUMINANCETOALPHA:
-        effectType<FECOLORMATRIX_TYPE_LUMINANCETOALPHA>(pixelArray.get(), m_values);
+        effectType<FECOLORMATRIX_TYPE_LUMINANCETOALPHA>(pixelArray.get(), values);
         setIsAlphaImage(true);
         break;
     }
index 6340107..7d4800d 100644 (file)
@@ -267,7 +267,7 @@ ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintin
 
     for (int y = yEnd + 1; y > yStart; --y) {
         for (int x = clipRight + 1; x > 0; --x) {
-            int kernelValue = m_kernelMatrix.size() - 1;
+            int kernelValue = paintingData.kernelMatrix.size() - 1;
             int kernelPixel = startKernelPixel;
             int width = m_kernelSize.width();
 
@@ -278,11 +278,11 @@ ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintin
                 totals[3] = 0;
 
             while (kernelValue >= 0) {
-                totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
-                totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
-                totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
+                totals[0] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
+                totals[1] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
+                totals[2] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
                 if (!preserveAlphaValues)
-                    totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel));
+                    totals[3] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel));
                 ++kernelPixel;
                 --kernelValue;
                 if (!--width) {
@@ -347,7 +347,7 @@ void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, in
 
     for (int y = height; y > 0; --y) {
         for (int x = width; x > 0; --x) {
-            int kernelValue = m_kernelMatrix.size() - 1;
+            int kernelValue = paintingData.kernelMatrix.size() - 1;
             int kernelPixelX = startKernelPixelX;
             int kernelPixelY = startKernelPixelY;
             int width = m_kernelSize.width();
@@ -361,12 +361,12 @@ void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, in
             while (kernelValue >= 0) {
                 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY);
                 if (pixelIndex >= 0) {
-                    totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex));
-                    totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 1));
-                    totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 2));
+                    totals[0] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex));
+                    totals[1] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 1));
+                    totals[2] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 2));
                 }
                 if (!preserveAlphaValues && pixelIndex >= 0)
-                    totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 3));
+                    totals[3] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 3));
                 ++kernelPixelX;
                 --kernelValue;
                 if (!--width) {
@@ -436,6 +436,7 @@ void FEConvolveMatrix::platformApplySoftware()
     paintingData.width = paintSize.width();
     paintingData.height = paintSize.height();
     paintingData.bias = m_bias * 255;
+    paintingData.kernelMatrix = normalizedFloats(m_kernelMatrix);
 
     // Drawing fully covered pixels
     int clipRight = paintSize.width() - m_kernelSize.width();
index 00141fa..8518c97 100644 (file)
@@ -83,6 +83,7 @@ private:
         int width;
         int height;
         float bias;
+        Vector<float> kernelMatrix;
     };
 
     FEConvolveMatrix(Filter&, const IntSize&, float, float,
index bb5cbca..ea4ec77 100644 (file)
@@ -26,6 +26,7 @@
 #include "FloatRect.h"
 #include "IntRect.h"
 #include <runtime/Uint8ClampedArray.h>
+#include <wtf/MathExtras.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
 #include <wtf/Vector.h>
@@ -170,6 +171,14 @@ protected:
     void forceValidPreMultipliedPixels();
 
     void clipAbsolutePaintRect();
+    
+    static Vector<float> normalizedFloats(const Vector<float>& values)
+    {
+        Vector<float> normalizedValues(values.size());
+        for (size_t i = 0; i < values.size(); ++i)
+            normalizedValues[i] = normalizedFloat(values[i]);
+        return normalizedValues;
+    }
 
 private:
     std::unique_ptr<ImageBuffer> m_imageBufferResult;