Thursday, March 7, 2019

Android Messaging: A Few Bugs Short of a Chain

Posted by Natalie Silvanovich, Project Zero

About a year and a half ago, I did some research into Android messaging and mail clients. At cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time, I didn’t blog about it, because though I found bugs, I wasn’t able to assemble cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m into a credible attack. However, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spirit of writing about research that didn’t go as expected, I have decided to share it now. I think cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is something interesting to learn about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impact of design choices on security from this research.

Applications Reviewed
I reviewed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following applications: Messages (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 default Android SMS application), Gmail for Android, Microsoft Outlook, Facebook Messenger, WhatsApp, Signal and Telegram, which represent some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most popular messaging and mail apps on Google Play.

Architecture, Attack Surface and Test Set-up
At a high level, messaging clients have a similar architecture. They communicate with a server that is responsible for locating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 recipient that a user wants to communicate with and generally maintains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 messaging session. Messages are also transmitted through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server, and clients that support end-to-end encryption can include some sort of encrypted envelope in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se messages, that are passed directly to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 recipient.

Though cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir protocols differ substantially, Facebook Messenger, WhatsApp and Telegram all use a layered approach, where messages can contain fields of different types with a hierarchy.  Telegram’s protocol is documented publicly. A single message can include pieces for both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiving client.

I started by looking at how unencrypted messages were handled by Facebook messenger, but this turned out to be a challenging attack surface. There were many protocols that could be used to send messages (such as mobile and web APIs), but 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ý bet365y were processed substantially before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message was received by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target device. This made it difficult to know whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r any attack surface in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client was reachable. Therefore, I decided to focus on encrypted messages, as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server is not able to filter those.

I did not have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source for Facebook Messenger or WhatsApp, so I was only able to analyze cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 smali extracted from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 APKs for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se applications with apktool. The popularity of signal protocol simplified finding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 encryption portion of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se applications, as I was able to search for strings in libsignal. I cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n added a function that sent each message to a server I control and gave me cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 option to alter it before encrypting it. This gave me cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to test malformed encrypted  messages.

Testing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mail and SMS clients were easier. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Messaging app, I sent raw SMS messages using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sendDataMessage API from an app I wrote for an Android device. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mail clients, I sent MIME messages directly using an SMTP server.  

Results Overview
Please note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se results are over a year old, and messaging clients have high code churn, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clients may be different now. All cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerabilities discussed in this section have been fixed.

Messages


The default Android Messages app is largely written in Java, and uses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 AOSP SMS and MMS processing to parse messages. It has a fairly low risk of memory corruption vulnerabilities due to this. Android processes some special SMS messages differently, which increases cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 risk of receiving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se messages. Some messages are interpreted as OMTP for Visual Voicemail messages, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 processing of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se messages, as well as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IMAP functionality used by Visual Voicemail are implemented in Java and contain cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 minimum necessary functionality. Android once supported SUPL (an SMS protocol for GPS ephemeris updates) parsing in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user space as well, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 intent handlers for it are still in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SMS stack. However, Qualcomm’s baseband handles cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se messages directly now, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y will not make it to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user space on newer devices. The main risk of vulnerabilities in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Messages app is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 libraries used to process incoming media files. Many vulnerabilities have been discovered in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se components in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 past, though fewer have been reported recently, and media is processed by a low-privileged process. I did not find any vulnerabilities in Messages.

Gmail


The Gmail app processes MIME messages provided by an IMAP, POP or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r server. The contents of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIME message usually comes directly from anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r user, though mail servers sometimes do some filtering. Gmail uses some underlying mail APIs in AOSP, and both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 application and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 underlying APIs are implemented entirely in Java. I reported one directory traversal issue in Gmail due to attachment downloads.

Outlook


The Microsoft Outlook app is similar to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Gmail app in functionality. It also processes MIME provided by anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r server, however all of its protocol processing is done by code within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app. The application is written mostly in Java, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 existing native functionality is used for metrics and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r features not related to mail. I reported a directory traversal issue similar to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one in Gmail in Outlook.

Facebook Messenger


Facebook Messenger is a large application compared to 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 messaging applications I reviewed, and it was difficult to determine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 functionality of a lot of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code. It contains a large number of native libraries, which are updated dynamically from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Facebook server on a nearly daily basis. Few of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m seemed to be directly involved processing messages, though.

Facebook messenger communicates with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server using a protocol called Messaging Queue Telemetry Transport (MQTT). Underneath cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MQTT is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thrift protocol, which is open source. The application contains both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Java and C++ version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 library, but only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Java one appears to be used. I reviewed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se protocols, but did not find any vulnerabilities affecting Facebook Messenger.

WhatsApp


WhatsApp uses a layered protocol that is mostly implemented in Java. A small amount of message handling code used to process voice and video call requests is implemented in native code, which I discussed in this post. I did not find any vulnerabilities in WhatsApp messaging

Telegram


Telegram is open source and its APIs are publicly documented. It contains a fair amount of native code, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code is mostly used to process device-to-server messages, as opposed to device-to-device messages. The attack surface available to a remote attacker is almost entirely in Java. I found one vulnerability in Telegram, which was also a directory traversal vulnerability related to attachments.

Signal


Signal is largely implemented in Java, and has a fairly small attack surface. Its protocols are well documented. I did not find any vulnerabilities in Signal.

Do These Bugs Matter?
Overall, I found three directory traversal vulnerabilities in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se clients. They were surprisingly similar, all related to attachment names being allowed to contain special characters when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are downloaded. Directory traversal issues are fairly common in Android applications, likely in part due to how forgiving Java APIs are with regards to file names.

These bugs had some limitations though. To start, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Gmail and Outlook mail clients only work when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client is being used with non-standard email addresses (i.e. Gmail is being used with a non-Gmail account or Outlook is being used with a non-Outlook email address). Both of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se clients are widely used for this purpose, but this does mean cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bugs will not impact every user. The bugs also require cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user to download an attachment.

But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are also some limitations that affect exploitability. To start, none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se issues can overwrite a file that exists, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y can only write a new file. So cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re needs to be somewhere an attacker can place a file that is processed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system, but does not already exist. Secondly, applications on Android have limited permissions, and typically cannot write outside of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir application directory.

The Telegram issue probably is exploitable, as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re happened to be a configuration file that did not exist by default, but Telegram would process it if it existed, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 processing code had a memory corruption vulnerability in it. I couldn’t find anything like this for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mail clients, though.

One idea I had was to look for a vulnerability in SQLite, as Gmail and Outlook use SQLite databases to store a variety of data. SQLite supports journaling, which means that when a SQLite database is changed, a journal file is written first, so that if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system crashes or loses power during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 database is not corrupted. If this occurs, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 journal file will exist cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next time cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 database is accessed, and SQLite knows to restore it. Ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 journal file will be deleted when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write is completed successfully. This means that a SQLite database can be altered by writing a journal file with a specific name in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same directory, and this file does not generally already exist.

I tried this, and was able to alter databases, but none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m seemed to allow me to escalate privileges or perform unpermitted operations ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than altering what mail a user sees in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app. Looking for something more generic, I considered cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 possibility of finding a bug in how SQLite parses journal files or database files.

I fuzzed both database loading and journal handling using AFL. I found four bugs, three of which I think are unexploitable, and one which is very borderline in terms of exploitability.

I could not find any more ways to exploit a directory traversal that cannot overwrite a file in an Android application, so I’m not convinced that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se directory traversal attacks would be especially valuable to attackers.

Conclusion
I reviewed several Android messaging and mail applications. I found three directory traversal bugs, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bugs had some limitations, and it is not clear if this type of bug is exploitable in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 generic case.

These bugs were very similar, and point to challenges in correctly handling file paths in Android Java. The two bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mail clients only occurred when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 application was used with an email address which is not managed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 maker of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 application, despite it being a common use case,  which suggests this could be an under-tested or under-reviewed feature of mail clients. For cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most part, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se clients were implemented in Java, which made it more difficult to find bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m, as memory corruption attacks were not available.

This research focused on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 portions of mail and messages that are not processed by an intermediary server, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore allow an attacker to send any data to be processed without filtering. While cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 easiest to attack, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se features are only a part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attack surface of a messaging or mail client. I will likely investigate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server-mediated features of messaging clients in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 future.

Tuesday, February 5, 2019

The Curious Case of Convexity Confusion

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:

  1. 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.

  1. 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:

  1. We control a path (SkPath) object
  2. 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
  3. 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 :-)