Posted by Ivan Fratric, Google Project Zero
Intro
Some time ago, I noticed a tweet about an externally reported vulnerability in Skia graphics library (used by Chrome, Firefox and Android, among ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs). The vulnerability caught my attention for several reasons:
Firstly, I looked at Skia before within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of finding precision issues, and any bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code I already looked at instantly evoke cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “What did I miss?” question in my head.
Secondly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug was described as a stack-based buffer overflow, and you don’t see many bugs of this type anymore, especially in web browsers.
And finally, while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug itself was found by fuzzing and didn’t contain much in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sense of root cause analysis, a part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fix involved changing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 floating point precision from single to double which is something I argued against in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous blog post on precision issues in graphics libraries.
So I wondered what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root cause was and if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch really addressed it, or if ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r variants could be found. As it turned out, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were indeed ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r variants, resulting in stack and heap out-of-bounds writes in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome renderer.
Geometry for exploit writers
To understand what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue was, let’s quickly cover some geometry basics we’ll need later. This is all pretty basic stuff, so if you already know some geometry, feel free to skip this section.
A convex polygon is a polygon with a following property: you can take any two points inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon, and if you connect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting line will be entirely contained within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon. A concave polygon is a polygon that is not convex. This is illustrated in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following images:
Image 1: An example of a convex polygon
Image 2: An example of a concave polygon
A polygon is monotone with respect to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Y axis (also called y-monotone) if every horizontal line intersects it at most twice. Anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r way to describe a y-monotone polygon is: if we traverse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 points of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon from its topmost to its bottom-most point (or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r way around), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 y coordinates of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 points we encounter are always going to decrease (or always increase) but never alternate directions. This is illustrated by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following examples:
Image 3: An example of a y-monotone polygon
Image 4: An example of a non-y-monotone polygon
A polygon can also be x-monotone if every vertical line intersects it at most twice. A convex polygon is both x- and y-monotone, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inverse is not true: A monotone polygon can be concave, as illustrated in Image 3.
All of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concepts above can easily be extended to ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r curves, not just polygons (which are made entirely from line segments).
A polygon can be transformed by transforming all of its points. A so-called affine transformation is a combination of scaling, skew and translation (note that affine transformation also includes rotation because rotation can be expressed as a combination of scale and skew). Affine transformation has a property that, when it is used to transform a convex shape, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting shape must also be convex.
For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 readers with a basic knowledge of linear algebra: a transformation can be represented in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 form of a matrix, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 transformed coordinates can be computed by multiplying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 matrix with a vector representing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original coordinates. Transformations can be combined by multiplying matrices. For example, if you multiply a rotation matrix and a translation matrix, you’ll get a transformation matrix that includes both rotation and translation. Depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 multiplication order, eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r rotation or translation is going to be applied first.
The bug
Back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug: after analyzing it, I found out that it was triggered by a malformed RRect (a rectangle with curved corners where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user can specify a radius for each corner). In this case, tiny values were used as RRect parameters which caused precision issues when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RRect was converted into a path object (a more general shape representation in Skia which can consist of both line and curve segments). The result of this was, after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RRect was converted to a path and transformed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting shape didn’t look like a RRect at all - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting shape was concave.
At cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same time Skia assumes that every RRect must be convex and so, when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RRect is converted to a path, it sets cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity attribute on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path to kConvex_Convexity (for RRects this happens in a helper class SkAutoPathBoundsUpdate).
Why is this a problem? Because Skia has different drawing algorithms, some of which only work for convex paths. And, unfortunately, using algorithms for drawing convex paths when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is concave can result in memory corruption. This is exactly what happened here.
Skia developers fixed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug by addressing RRect-specific computations: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y increased cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 precision of some calculations performed when converting RRects to paths and also made sure that any RRect corner with a tiny radius would be treated as if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 radius is 0. Possibly (I haven’t checked), this makes sure that converting RRect to a path won’t result in a concave shape.
However, anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r detail caught my attention:
Initially, when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RRect was converted into a path, it might have been concave, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavities were so tiny that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y wouldn’t cause any issues when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path was rendered. At some point cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path was transformed which caused cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavities to become more pronounced (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path was very clearly concave at this point). And yet, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path was still treated as convex. How could that be?
The answer: The transformation used was an affine transform, and Skia respects cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 macá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365matical property that transforming a shape with an affine transform can not change its convexity, and so, when using an affine transform to transform a path, it copies cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity attribute to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting path object.
This means: if we can convince Skia that a path is convex, when in reality it is not, and if we apply any affine transform to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting path will also be treated as convex. The affine transform can be crafted so that it enlarges, rotates and positions concavities so that, once cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convex drawing algorithm is used on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path, memory corruption issues are triggered.
Additionally (untested) it might be possible that, due to precision errors, computing a transformation itself might introduce tiny concavities when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were none previously. These concavities might cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n be enlarged in subsequent path transformations.
Unfortunately for computational geometry coders everywhere, accurately determining whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a path is convex or not in floating point precision (regardless if single or double floating point precision is used) is very difficult / almost impossible to do. So, how does Skia do it? Convexity computations in Skia happen in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Convexicator class, where Skia uses several criteria to determine if a path is convex:
- It traverses a path and computes changes of direction. For example, if we follow a path and always turn left (or always turn right), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path must be convex.
- It checks if a path is both x- and y-monotone
When analyzing this Convexicator class, I noticed two cases where a concave paths might pass as convex:
- As can be seen here, any pair of points for which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 squared distance does not fit in a 32-bit float (i.e. distance between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 points smaller than ~3.74e-23) will be completely ignored. This, of course, includes sequences of points which form concavities.
- Due to tolerances when computing direction changes (e.g here and here) even concavities significantly larger than 3.74e-23 can easily passing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity check (I experimented with values around 1e-10). However, such concavities must also pass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 x- and y-monotonicity check.
Note that, in both cases, a path needs to have some larger edges (for which direction can be properly computed) in order to be declared convex, so just having a tiny path is not sufficient. Fortunately, a line is considered convex by Skia, so it is sufficient to have a tiny concave shape and a single point at a sufficient distance away from it for a path to be declared convex.
Alternately, by combining both issues above, one can have tiny concavities along cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line, which is a technique I used to create paths that are both small and clearly concave when transformed (Note: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is often a factor when determining which algorithms can handle which paths).
To make things clearer, let’s see an example of bypassing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity check with a polygon that is both x- and y- monotone. Consider cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon in Image 5 (a) and imagine that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 part inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red circle is much smaller than depicted. Note that this polygon is concave, but it is also both x-monotone and y-monotone. Thus, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavity depicted in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red circle is sufficiently small, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon is going to be declared convex.
Now, let’s see what we can do with it by applying an affine transform - firstly, we can rotate it and make it non-y-monotone as depicted in Image 5 (b). Having a polygon that is not y-monotone will be very important for triggering memory corruption issues later.
Secondly, we can scale (enlarge) and translate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavity to fill cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole drawing area, and when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavity is intersected with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 drawing area we’ll end up with something like depicted in Image 5 (c), in which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon is clearly concave and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concavity is no longer small.
(a)
(b)
(c)
Image 5: Bypassing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity check with a monotone polygon
The walk_convex_edges algorithm
Now that we can bypass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity check in various ways, let’s see how it can lead to problems. To understand this, let’s first examine how Skia's algorithm for drawing (filling) convex paths works (code here). Let’s consider an example in Image 6 (a). The first thing Skia does is, it extracts polygon (path) lines (edges) and sorts cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m according to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 coordinates of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 topmost point. The sorting order is top-to-bottom, and if two points have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same y coordinate, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one with a smaller x coordinate goes first. This has been done for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon in Image 6 (a) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 numbers next to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 edges depict cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir order. The bottommost edge is ignored because it is fully horizontal and thus not needed (you’ll see why in a moment).
Next, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 edges are traversed and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m drawn. First, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first two edges (edges 1 and 2) are taken and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m is filled from top to bottom - this is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red area in Image 6 (b). After this, edge 1 is “done” and it is replaced by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next edge - edge 3. Now, area between edge 2 and edge 3 is filled (orange area). Next, edge 2 is “done” and is replaced by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next in line: edge 4. Finally, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area between edges 3 and 4 is rendered. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are no more edges, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 algorithm stops.
(a)
(b)
Image 6: Skia convex path filling algorithm
Note that, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code for rendering areas where both edges are vertical (here) is different than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code for rendering areas where at least one edge is at an angle (here). In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole area is rendered in a single call to blitter->blitRect() while in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area is rendered line-by-line and for each line blitter->blitH() is called. Of special interest here is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 local_top variable, essentially keeping track of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next y coordinate to fill. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case of drawing non-vertical edges, this is simply incremented for every line drawn. In case of vertical lines (drawing a rectangle), after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rectangle is drawn, local_top is set based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 coordinates of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current edge pair. This difference in behavior is going to be useful later.
One interesting observation about this algorithm is that it would not only work correctly for convex paths - it would work correctly for all paths that are y-monotone. Using it for y-monotone paths would also have anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r benefit: Checking if a path is y-monotone could be performed faster and more accurately than checking if a path is convex.
Variant 1
Now, let’s see how drawing concave paths using this algorithm can lead to problems. As cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first example, consider cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 polygon in Image 7 (a) with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 edge ordering marked.
(a)
(b)
Image 7: An example of a concave path that causes problem in Skia if rendered as convex
Image 7 (b) shows how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape is rendered. First, a large red area between edges 1 and 2 is rendered. At this point, both edges 1 and 2 are done, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 orange rectangular area between areas 3 and 4 is rendered next. The purpose of this rectangular area is simply to reset cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 local_top variable to its correct value (here), ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise local_top would just continue increasing for every line drawn. Next, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 green area between edges 3 and 5 is drawn - and this causes problems. Why?
Because Skia expects to always draw pixels in a top-to-bottom, left-to right order, e.g. point (x, y) = (1, 1) is always going to be drawn before (1, 2) and (1, 1) is also going to be always drawn before (2, 1).
However, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 example above, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area between edges 1 and 2 will have (partially) cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same y values as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 area between edges 3 and 5. The second area is going to be drawn, well, second, and yet it contains a subset of same y coordinates and lower x coordinates than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first region.
Now let’s see how this leads to memory corruption. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original bug, a concave (but presumed convex) path was used as a clipping region (every subsequent draw call draws only inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clipping region). When setting a path as a clipping region, it also gets “drawn”, but instead of drawing pixels on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 screen, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y just get saved so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y could be intersected with what gets drawn afterwards. The pixels are saved in SkRgnBuilder::blitH and actually, individual pixels aren't saved but instead cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire range of pixels (from x to x + width at height y) gets stored at once to save space. These ranges - you guessed it - also depend on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct drawing order as can be seen here (among ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r places).
Now let’s see what happens when a second path is drawn inside a clipping region with incorrect ordering. If antialiasing is turned on when drawing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second path, SkRgnClipBlitter::blitAntiH gets called for every range drawn. This function needs to intersect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clip region ranges with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range being drawn and only output cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pixels that are present in both. For that purpose, it gets cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clipping ranges that intersect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line being drawn one by one and processes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m. SkRegion::Spanerator::next is used to return cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next clipping range.
Let’s assume cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clipping region for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 y coordinate currently drawn has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ranges [start x, end x] = [10, 20] and [0, 2] and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line being drawn is [15,16]. Let’s also consider cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following snippet of code from SkRegion::Spanerator::next:
if (runs[0] >= fRight) {
fDone = true;
return false;
}
SkASSERT(runs[1] > fLeft);
if (left) {
*left = SkMax32(fLeft, runs[0]);
}
if (right) {
*right = SkMin32(fRight, runs[1]);
}
fRuns = runs + 2;
return true;
where left and right are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output pointers, fLeft and fRight are going to be left and right x value of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line being drawn (15 and 16 respectively), while runs is a pointer to clipping region ranges that gets incremented for every iteration. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first clipping line [10, 20] this is going to work correctly, but let’s see what happens for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range [0, 2]. Firstly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 part
if (runs[0] >= fRight) {
fDone = true;
return false;
}
is supposed to stop cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 algorithm, but due to incorrect ordering, it does not work (16 >= 0 is false). Next, left is computed as Max(15, 0) = 15 and right as Min(16, 2) = 2. Note how left is larger than right. This is going to result in calling SkAlphaRuns::Break with a negative count argument on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line,
SkAlphaRuns::Break((int16_t*)runs, (uint8_t*)aa, left - x, right - left);
which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n leads to out-of-bounds write on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following lines in SkAlphaRuns::Break:
x = count;
...
alpha[x] = alpha[0];
Why did this result in out-of-bounds write on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack? Because, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case of drawing only two pixels, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range arrays passed to SkRgnClipBlitter::blitAntiH and subsequently SkAlphaRuns::Break are allocated on stack in SkBlitter::blitAntiH2 here.
Triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue in a browser
This is great - we have a stack out-of-bounds write in Skia, but can we trigger this in Chrome? In general, in order to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following conditions must be met:
- We control a path (SkPath) object
- Something must be done to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path object that computes its convexity
- The same path must be transformed and filled / set as a clip region
My initial idea was to use a CanvasRenderingContext2D API and render a path twice: once without any transform just to establish its convexity and a second time with a transformation applied to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CanvasRenderingContext2D object.
Unfortunately, this approach won’t work - when drawing a path, Skia is going to copy it before applying a transformation, even if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is effectively no transformation set (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 transformation matrix is an identity matrix). So cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity property is going to be set on a copy of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path and not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one we get to keep cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference to.
Additionally, Chrome itself makes a copy of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path object when calling any canvas functions that cause a path to be drawn, and all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r functions we can call with a path object as an argument do not check its convexity.
However, I noticed Chrome canvas still draws my convex/concave paths incorrectly - even if I just draw cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m once. So what is going on? As it turns out, when drawing a path using Chrome canvas, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path won’t be drawn immediately. Instead, Chrome just records cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 draw path operation using RecordPaintCanvas and all such draw operations will be executed togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r, at a later time. When a DrawPathOp object (representing a path drawing operation) is created, among ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r things, it is going to check if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is “slow”, and one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 criteria for this is path convexity:
int DrawPathOp::CountSlowPaths() const {
if (!flags.isAntiAlias() || path.isConvex())
return 0;
…
}
All of this happens before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is transformed, so we seemingly have a perfect scenario: We control a path, its convexity is checked, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same path object later gets transformed and rendered.
The second problem with canvas is that, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previously described approach to converting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue to memory corruption, we relied on SkRgnBuilder, which is only used when a clip region has antialiasing turned off, while everything in Chrome canvas is going to be drawn with antialiasing on. Chrome also implements cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OffscreenCanvas API which sets clip antialiasing to off (I’m not sure if this is deliberate or a bug), but OffscreenCanvas does not use RecordPaintCanvas and instead draws everything immediately.
So cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 best way forward seemed to be to find some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r variants of turning convexity issues into memory corruption, ones that would work with antialiasing on for all operations.
Variant 2
As it happens, Skia implements three different algorithms for path drawing with antialiasing on and one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se (SkScan::SAAFillPath, using supersampled antialiasing) uses essentially cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same filling algorithm we analyzed before. Unfortunately, this does not mean we can get to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same buffer overflow as before - as mentioned before SkRgnBuilder / SkRgnClipBlitter are not used with antialiasing on. However, we have ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r options.
If we simply fill cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path (no clip region needed this time) with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct algorithm, SuperBlitter::blitH is going to be called without respecting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 top-to-bottom, left-to-right drawing order. SuperBlitter::blitH calls SkAlphaRuns::add and as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last argument, it passes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rightmost x coordinate we have drawn so far. This is subtracted from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 currently drawn x coordinate on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line:
x -= offsetX;
And if x is smaller than something we drew already (for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same y coordinate) it becomes negative. This is of course exactly what happens when drawing pixels out of Skia expected order.
The result of this is calling SkAlphaRuns::Break with a negative “x” argument. This skips cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire first part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “while (x > 0)” loop), and continues to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second part:
runs = next_runs;
alpha = next_alpha;
x = count;
for (;;) {
int n = runs[0];
SkASSERT(n > 0);
if (x < n) {
alpha[x] = alpha[0];
runs[0] = SkToS16(x);
runs[x] = SkToS16(n - x);
break;
}
x -= n;
if (x <= 0) {
break;
}
runs += n;
alpha += n;
}
Here, x gets overwritten with count, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 problem is that runs[0] is not going to be initialized (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function is supposed to initialize it), so in
int n = runs[0];
an uninitialized variable gets read into n and is used as an offset into arrays, which can result in both out-of-bounds read and out-of-bounds write when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following lines are executed:
runs += n;
alpha += n;
alpha[x] = alpha[0];
runs[0] = SkToS16(x);
runs[x] = SkToS16(n - x);
The shape needed to trigger this is depicted in image 8 (a).
(a)
(b)
Image 8: Shape used to trigger variant 2 in Chrome
This shape is similar to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one previously depicted, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are some differences, namely:
- We must render two ranges for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same y coordinate immediately one after anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r, where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second range is going to be to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 left of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first range. This is accomplished by making cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rectangular area between edges 3 and 4 (orange in Image 8 (b)) less than a pixel wide (so it does not in fact output anything) and making cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 green area between edges 5 and 6 (green in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image) only a single pixel high.
- The second range for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same y must not start at x = 0. This is accomplished by edge 5 ending a bit away from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 left side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image bounds.
This variant can be triggered in Chrome by simply drawing a path - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 poc can be seen here.
Variant 3
Uninitialized variable bug in a browser is nice, but not as nice as a stack out-of-bounds write, so I looked for more variants. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next and final one, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path we need is a bit more complicated and can be seen in Image 9 (a) (note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is self-intersecting).
(a)
(b)
Image 9: A shape used to trigger a stack buffer overflow in Chrome
Let’s see what happens in this one (assume cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same drawing algorithm is used as before): First, edges 1, 2, 3 and 4 are handled. This part is drawn incorrectly (only red and orange areas in Image 9 (b) are filled), but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 details aren’t relevant for triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug. For now, just note that edges 2 and 4 terminate at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same height, so when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are done, edges 2 and 4 are both replaced with edges 5 and 6. The purpose of edges 5 and 6 is once again to reset cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 local_top variable - it will be set to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 height shown as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red dotted line in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image. Now, edge 5 and 6 will both get replaced with edges 7 and 8 - and here is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue: Edges 7 and 8 are not going to be drawn for y coordinates between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 green and blue line, as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are supposed to. Instead, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are going to be rendered all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red line to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blue line. Note cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 very low steepness of edges 7 and 8 - for every line, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 x coordinates to draw to are going to be significantly increased and, given that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are going to be drawn in a larger number of iterations than intended, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 x coordinate will eventually spill past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image bounds.
This causes a stack out-of-bounds write if a path is drawn using SkScan::SAAFillPath algorithm with MaskSuperBlitter. MaskSuperBlitter can only handle very small paths (up to 32x32 pixels) and contains a fixed-size buffer that is going to be filled with 8-bit opacity for each pixel of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path region. Since MaskSuperBlitter is a local variable in SkScan::SAAFillPath, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 (fixed-size) buffer is going to be allocated on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path above is drawn, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re aren’t any bounds checks on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 opacity buffer (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are only debug asserts here and here), which leads to an out-of bounds write on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack. Specifically (due to how opacity buffer works) we can increment values on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer by a small amount.
This variant is again triggerable in Chrome by simply drawing a path to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Canvas and gives us a pretty nice primitive for exploitation - note that this is not a linear overflow and offsets involved can be controlled by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slope of edges 7 and 8. The PoC can be seen here - most of it is just setting up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path coordinates so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is initially declared convex and at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same time small enough so that MaskSuperBlitter can render it.
How to make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape needed to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug appear convex to Skia but also fit in 32x32 pixels? Note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape is already x-monotone. Now assume we squash it in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 y direction until it becomes (almost) a line lying on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 x axis. It is still not y-monotone because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are tiny shifts in y direction along cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line - but if we skew (or rotate) it just a tiny amount, so that it is no longer parallel to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 x axis, it also becomes y-monotone. The only parts we can’t make monotone are vertical edges (edges 5 and 6), but if you squashed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape sufficiently cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y become so short that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir square length does not fit in a float and are ignored by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Skia convexity test. This is illustrated in Image 10. In reality cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se steps need to be followed in reverse, as we start with a shape that needs to pass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Skia convexity test and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n transform it to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape depicted in Image 9.
(a)
(b)
(c)
Image 10: Making cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shape from Image 9 appear convex, (a) original shape, (b) shape after y-scale, (c) shape after y-scale rotation
On fixing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue
Initially, Skia developers attempted to fix cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue by not propagating convexity information after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 transformation, but only in some cases. Specifically, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convexity was still propagated if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 transformation consisted only of scale and translation. Such a fix is insufficient because very small concavities (where square distance between points is too small to fit in a 32-bit float) could still be enlarged using only scale transformation and could form shapes that would trigger memory corruption issues.
After talking to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Skia developers, a stronger patch was created, modifying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 convex drawing algorithm in a way that passing concave shapes to it won’t result in memory corruption, but racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r in returning from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 draw operation early. This patch shipped, along with ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r improvements, in Chrome 72.
It isn’t uncommon that an initial fix for a vulnerability is insufficient. But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 saving grace for Skia, Chrome and most open source projects is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug reporter can see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fix immediately when it’s created and point out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 potential drawbacks. Unfortunately, this isn’t cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case for many closed-source projects or even open-sourced projects where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug fixing process is opaque to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reporter, which caused mishaps in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 past. However, regardless of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vendor, we at Project Zero are happy to receive information on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fixes early and comment on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are released to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 public.
Conclusion
There are several things worth highlighting about this bug. Firstly, computational geometry is hard. Seriously. I have some experience with it and, while I can’t say I’m an expert I know that much at least. Handling all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 special cases correctly is a pain, even without considering security issues. And doing it using floating point arithmetic might as well be impossible. If I was writing a graphics library, I would convert floats to fixed-point precision as soon as possible and wouldn’t trust anything computed based on floating-point arithmetic at all.
Secondly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue highlights cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 importance of doing variant analysis - I discovered it based on a public bug report and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r people could have done cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same.
Thirdly, it highlights cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 importance of defense-in-depth. The latest patch makes sure that drawing a concave path with convex path algorithms won’t result in memory corruption, which also addresses unknown variants of convexity issues. If this was implemented immediately after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial report, Project Zero would now have one blog post less :-)