Mark Brand and Sergei Glazunov, Project Zero (Originally posted on Project Zero blog 2021-02-04)
Disclosure or Patch Date: 2 November 2020
Product: Google Chrome for Android
Advisory: https://chromereleases.googleblog.com/2020/11/chrome-for-android-update.html
Affected Versions: 86.0.4240.114 and previous
First Patched Version: 86.0.4240.185
Issue/Bug Report:
- Project Zero: https://bugs.chromium.org/p/project-zero/issues/detail?id=2112
- Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=1144368
Patch CL: https://chromium.googlesource.com/chromium/src.git/+/e598fc599bd920392256d05c61826466c73c8e89
Bug-Introducing CL: Unknown - at least from the migration of the use of SkColorType from SkBitmap::Config but may have existed prior to that.
Reporter(s): Maddie Stone, Mark Brand, and Sergei Glazunov of Google Project Zero
Proof-of-concept: https://bugs.chromium.org/p/project-zero/issues/detail?id=2112
Exploit sample: N/A
Did you have access to the exploit sample when doing the analysis? Yes
Bug class: Buffer overflow
Vulnerability details:
ConvertToJavaBitmap
doesn't handle all Skia supported color formats, and when converting a Skia bitmap with an unsupported SkColorType
, it falls back to a default format (32-bits-per-pixel, ARGB_8888).
static int SkColorTypeToBitmapFormat(SkColorType color_type) {
switch (color_type) {
case kAlpha_8_SkColorType:
return BITMAP_FORMAT_ALPHA_8;
case kARGB_4444_SkColorType:
return BITMAP_FORMAT_ARGB_4444;
case kN32_SkColorType:
return BITMAP_FORMAT_ARGB_8888;
case kRGB_565_SkColorType:
return BITMAP_FORMAT_RGB_565;
case kUnknown_SkColorType:
default:
NOTREACHED(); // NOTREACHED has no effect in release builds.
return BITMAP_FORMAT_NO_CONFIG;
}
}
/**
* Provides a matching Bitmap.Config for the enum config value passed.
*
* @param bitmapFormatValue The Bitmap Configuration enum value.
* @return Matching Bitmap.Config for the enum value passed.
*/
private static Bitmap.Config getBitmapConfigForFormat(int bitmapFormatValue) {
switch (bitmapFormatValue) {
case BitmapFormat.ALPHA_8:
return Bitmap.Config.ALPHA_8;
case BitmapFormat.ARGB_4444:
return Bitmap.Config.ARGB_4444;
case BitmapFormat.RGB_565:
return Bitmap.Config.RGB_565;
case BitmapFormat.ARGB_8888:
default:
return Bitmap.Config.ARGB_8888; // <-- fallback to 32-bpp
}
}
Since Skia supports formats with more than 32-bits-per-pixel (eg. 64-bits-per-pixel, RGBA_F16
), this can cause a mismatch between the size of the input and output bitmap buffers.
This allows an attacker to supply a malicious bitmap that will cause CreateJavaBitmap
to create an output JavaBitmap
with a smaller backing store than the input SkBitmap
. This leads to a heap buffer overflow when copying the raw pixel data into the destination bitmap.
The vulnerability can be fixed by ensuring that all of the input SkBitmap
's parameters are supported before performing the conversion, and adding an additional size check to make sure that the destination bitmap buffer is large enough prior to doing the memcpy
.
ScopedJavaLocalRef<jobject> ConvertToJavaBitmap(const SkBitmap* skbitmap,
OomBehavior reaction) {
DCHECK(skbitmap);
DCHECK(!skbitmap->isNull());
SkColorType color_type = skbitmap->colorType();
DCHECK((color_type == kRGB_565_SkColorType) ||
(color_type == kN32_SkColorType));
ScopedJavaLocalRef<jobject> jbitmap = CreateJavaBitmap(
skbitmap->width(), skbitmap->height(), color_type, reaction);
if (!jbitmap) {
DCHECK_EQ(OomBehavior::kReturnNullOnOom, reaction);
return jbitmap;
}
JavaBitmap dst_lock(jbitmap);
void* src_pixels = skbitmap->getPixels();
void* dst_pixels = dst_lock.pixels();
memcpy(dst_pixels, src_pixels, skbitmap->computeByteSize()); // <-- we use skbitmap size here, which may be larger than allocated size.
return jbitmap;
}
Patch analysis: N/A
Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):
Most likely through source-code auditing.
This issue (at least in the context that it was exploited) would have required an IPC fuzzer that was sufficiently aware to produce valid serialized SkBitmap
objects, and that would be more work than simply auditing all of the relevant code.
(Historical/present/future) context of bug:
(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)
Exploit strategy (or strategies): Still under analysis
Exploit flow: Still under analysis
Known cases of the same exploit flow: Still under analysis
Part of an exploit chain? This vulnerability was chained as the sandbox escape on Android with CVE-2020-15999. It was delivered to Android devices running Chrome 81+, while a Chrome 1-day was delivered to Android devices running Chrome <= 80.
Areas/approach for variant analysis (and why): Manual source-code auditing of similar image format conversion paths, especially where incoming image data comes from an untrusted source (IPC).
Found variants:
- CVE-2020-16011: An identical bug existing in Chrome for Windows
This specific vulnerability would not have been exploitable if it wasn't for the surprising (lack of) behaviour of Chrome's NOTREACHED
macro in release builds; but this would not address the general class of issue.