CVE-2020-16010: Chrome for Android ConvertToJavaBitmap Heap Buffer Overflow

Posted by Mark Brand and Sergei Glazunov, Project Zero (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:

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.

Proof-of-Concept: https://bugs.chromium.org/p/project-zero/issues/detail?id=2112 
Exploit Sample: N/A

Access to the exploit sample? Yes

Reporter(s): Maddie Stone, Mark Brand, and Sergei Glazunov of Google Project Zero 

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

https://source.chromium.org/chromium/chromium/src/+/master:ui/gfx/android/java_bitmap.cc;drc=8811640591d59f0b489a7703224530b03efe127b;l=44

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;

  }

}

https://source.chromium.org/chromium/chromium/src/+/master:ui/android/java/src/org/chromium/ui/gfx/BitmapHelper.java;drc=8811640591d59f0b489a7703224530b03efe127b;l=61

/**

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

https://source.chromium.org/chromium/chromium/src/+/master:ui/gfx/android/java_bitmap.cc;drc=8811640591d59f0b489a7703224530b03efe127b;l=73

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;

}

Is the exploit method known?
Exploit method:

Further analysis pending.

How do you think you would have found this bug?

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:

This vulnerability was chained as the sandbox escape on Android with CVE-2020-15999 (RCA). 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:

Manual source-code auditing of similar image format conversion paths, especially where incoming image data comes from an untrusted source (IPC).

Found variants:

Structural improvements:

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.

Potential detection methods for similar 0-days:

Other references:

No comments:

Post a Comment