From: csagan5 <32685696+csagan5@users.noreply.github.com>
Date: Sat, 24 Mar 2018 05:18:03 +0100
Subject: Canvas: fingerprinting mitigations for image data and webGL

Disable webGL renderer info and modify the color data returned by ToBlob,
ToDataURL and getImageData so that it will contain randomly manipulated
pixels (maximum 20) that slightly change the color of the R,G,B components
without a visible effect.

Credits to Slaviro (https://github.com/Slaviro) for coming up with a better
approach to change color components.
---
 .../canvas/canvas2d/canvas_rendering_context_2d.cc |   4 +-
 .../modules/webgl/webgl_debug_renderer_info.cc     |   4 +-
 third_party/blink/renderer/platform/BUILD.gn       |   4 +-
 .../platform/graphics/image_data_buffer.cc         |   5 +
 .../platform/graphics/static_bitmap_image.cc       | 156 +++++++++++++++++++++
 .../platform/graphics/static_bitmap_image.h        |   2 +
 6 files changed, 169 insertions(+), 6 deletions(-)

diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -33,8 +33,6 @@
 
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h"
 
-#include "base/rand_util.h"
-
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -799,7 +797,7 @@ TextMetrics* CanvasRenderingContext2D::measureText(const String& text) {
                              GetState().GetTextAlign(), text);
 
   // scale text metrics by 3/1000000th
-  auto shuffleFactor = 1 + (base::RandDouble() - 0.5) * 0.000003;
+  auto shuffleFactor = 1 + (canvas()->GetDocument().GetShuffleFactorX() - 0.5) * 0.000003;
   textMetrics->Shuffle(shuffleFactor);
 
   return textMetrics;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.cc b/third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.cc
--- a/third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.cc
@@ -37,11 +37,11 @@ WebGLExtensionName WebGLDebugRendererInfo::GetName() const {
 
 WebGLDebugRendererInfo* WebGLDebugRendererInfo::Create(
     WebGLRenderingContextBase* context) {
-  return MakeGarbageCollected<WebGLDebugRendererInfo>(context);
+  return nullptr;
 }
 
 bool WebGLDebugRendererInfo::Supported(WebGLRenderingContextBase*) {
-  return true;
+  return false;
 }
 
 const char* WebGLDebugRendererInfo::ExtensionName() {
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1423,7 +1423,9 @@ jumbo_component("platform") {
     "//third_party/blink/renderer:non_test_config",
   ]
 
-  include_dirs = []
+  include_dirs = [
+    "//third_party/skia/include/private", # For shuffler in graphics/static_bitmap_image.cc
+  ]
 
   public_deps = [
     ":blink_platform_public_deps",
diff --git a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
--- a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
@@ -35,6 +35,8 @@
 #include <memory>
 
 #include "base/memory/ptr_util.h"
+#include "base/rand_util.h"
+#include "base/logging.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/image-encoders/image_encoder.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -127,6 +129,9 @@ bool ImageDataBuffer::EncodeImageInternal(const ImageEncodingMimeType mime_type,
                                           const SkPixmap& pixmap) const {
   DCHECK(is_valid_);
 
+  // shuffle subchannel color data within the pixmap
+  StaticBitmapImage::ShuffleSubchannelColorData(pixmap_.writable_addr(), pixmap_.info(), 0, 0);
+
   if (mime_type == kMimeTypeJpeg) {
     SkJpegEncoder::Options options;
     options.fQuality = ImageEncoder::ComputeJpegQuality(quality);
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 
+#include "base/rand_util.h"
+#include "base/logging.h"
 #include "base/numerics/checked_math.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
@@ -15,6 +17,7 @@
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/private/SkColorData.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 
@@ -151,10 +154,163 @@ bool StaticBitmapImage::ConvertToArrayBufferContents(
   DCHECK(read_pixels_successful ||
          !sk_image->bounds().intersect(SkIRect::MakeXYWH(
              rect.X(), rect.Y(), info.width(), info.height())));
+
+  ShuffleSubchannelColorData(result.Data(), info, rect.X(), rect.Y());
+
   result.Transfer(dest_contents);
   return true;
 }
 
+// set the component to maximum-delta if it is >= maximum, or add to existing color component (color + delta)
+#define shuffleComponent(color, max, delta) ( (color) >= (max) ? ((max)-(delta)) : ((color)+(delta)) )
+
+#define writable_addr(T, p, stride, x, y) (T*)((const char *)p + y * stride + x * sizeof(T))
+
+void StaticBitmapImage::ShuffleSubchannelColorData(const void *addr, const SkImageInfo& info, int srcX, int srcY) {
+  auto w = info.width() - srcX, h = info.height() - srcY;
+
+  // skip tiny images; info.width()/height() can also be 0
+  if ((w < 8) || (h < 8)) {
+    return;
+  }
+
+  // generate the first random number here
+  double shuffleX = base::RandDouble();
+
+  // cap maximum pixels to change
+  auto pixels = (w + h) / 128;
+  if (pixels > 20) {
+    pixels = 20;
+  } else if (pixels < 2) {
+    pixels = 2;
+  }
+
+  auto colorType = info.colorType();
+  auto fRowBytes = info.minRowBytes(); // stride
+
+  DLOG(INFO) << "BRM: ShuffleSubchannelColorData() w=" << w << " h=" << h << " colorType=" << colorType << " fRowBytes=" << fRowBytes;
+
+  // second random number (for y/height)
+  double shuffleY = base::RandDouble();
+
+  // calculate random coordinates using bisection
+  auto currentW = w, currentH = h;
+  for(;pixels >= 0; pixels--) {
+    int x = currentW * shuffleX, y = currentH * shuffleY;
+
+    // calculate randomisation amounts for each RGB component
+    uint8_t shuffleR = base::RandInt(0, 4);
+    uint8_t shuffleG = (shuffleR + x) % 4;
+    uint8_t shuffleB = (shuffleG + y) % 4;
+
+    // manipulate pixel data to slightly change the R, G, B components
+    switch (colorType) {
+      case kAlpha_8_SkColorType:
+      {
+         auto *pixel = writable_addr(uint8_t, addr, fRowBytes, x, y);
+         auto r = SkColorGetR(*pixel), g = SkColorGetG(*pixel), b = SkColorGetB(*pixel), a = SkColorGetA(*pixel);
+
+         r = shuffleComponent(r, UINT8_MAX-1, shuffleR);
+         g = shuffleComponent(g, UINT8_MAX-1, shuffleG);
+         b = shuffleComponent(b, UINT8_MAX-1, shuffleB);
+         // alpha is left unchanged
+
+         *pixel = SkColorSetARGB(a, r, g, b);
+      }
+      break;
+      case kGray_8_SkColorType:
+      {
+         auto *pixel = writable_addr(uint8_t, addr, fRowBytes, x, y);
+         *pixel = shuffleComponent(*pixel, UINT8_MAX-1, shuffleB);
+      }
+      break;
+      case kRGB_565_SkColorType:
+      {
+         auto *pixel = writable_addr(uint16_t, addr, fRowBytes, x, y);
+         unsigned    r = SkPacked16ToR32(*pixel);
+         unsigned    g = SkPacked16ToG32(*pixel);
+         unsigned    b = SkPacked16ToB32(*pixel);
+
+         r = shuffleComponent(r, 31, shuffleR);
+         g = shuffleComponent(g, 63, shuffleG);
+         b = shuffleComponent(b, 31, shuffleB);
+
+         unsigned r16 = (r & SK_R16_MASK) << SK_R16_SHIFT;
+         unsigned g16 = (g & SK_G16_MASK) << SK_G16_SHIFT;
+         unsigned b16 = (b & SK_B16_MASK) << SK_B16_SHIFT;
+
+         *pixel = r16 | g16 | b16;
+      }
+      break;
+      case kARGB_4444_SkColorType:
+      {
+         auto *pixel = writable_addr(uint16_t, addr, fRowBytes, x, y);
+         auto a = SkGetPackedA4444(*pixel), r = SkGetPackedR4444(*pixel), g = SkGetPackedG4444(*pixel), b = SkGetPackedB4444(*pixel);
+
+         r = shuffleComponent(r, 15, shuffleR);
+         g = shuffleComponent(g, 15, shuffleG);
+         b = shuffleComponent(b, 15, shuffleB);
+         // alpha is left unchanged
+
+         unsigned a4 = (a & 0xF) << SK_A4444_SHIFT;
+         unsigned r4 = (r & 0xF) << SK_R4444_SHIFT;
+         unsigned g4 = (g & 0xF) << SK_G4444_SHIFT;
+         unsigned b4 = (b & 0xF) << SK_B4444_SHIFT;
+
+         *pixel = r4 | b4 | g4 | a4;
+      }
+      break;
+      case kRGBA_8888_SkColorType:
+      {
+         auto *pixel = writable_addr(uint32_t, addr, fRowBytes, x, y);
+         auto a = SkGetPackedA32(*pixel), r = SkGetPackedR32(*pixel), g = SkGetPackedG32(*pixel), b = SkGetPackedB32(*pixel);
+
+         r = shuffleComponent(r, UINT8_MAX-1, shuffleR);
+         g = shuffleComponent(g, UINT8_MAX-1, shuffleG);
+         b = shuffleComponent(b, UINT8_MAX-1, shuffleB);
+         // alpha is left unchanged
+
+         *pixel = (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) |
+                  (g << SK_G32_SHIFT) | (b << SK_B32_SHIFT);
+      }
+      break;
+      case kBGRA_8888_SkColorType:
+      {
+         auto *pixel = writable_addr(uint32_t, addr, fRowBytes, x, y);
+         auto a = SkGetPackedA32(*pixel), b = SkGetPackedR32(*pixel), g = SkGetPackedG32(*pixel), r = SkGetPackedB32(*pixel);
+
+         r = shuffleComponent(r, UINT8_MAX-1, shuffleR);
+         g = shuffleComponent(g, UINT8_MAX-1, shuffleG);
+         b = shuffleComponent(b, UINT8_MAX-1, shuffleB);
+         // alpha is left unchanged
+
+         *pixel = (a << SK_BGRA_A32_SHIFT) | (r << SK_BGRA_R32_SHIFT) |
+                  (g << SK_BGRA_G32_SHIFT) | (b << SK_BGRA_B32_SHIFT);
+      }
+      break;
+      default:
+         // the remaining formats are not expected to be used in Chromium
+         LOG(WARNING) << "BRM: ShuffleSubchannelColorData(): Ignoring pixel format";
+         return;
+    }
+
+    // keep bisecting or reset current width/height as needed
+    if (x == 0) {
+       currentW = w;
+    } else {
+       currentW = x;
+    }
+    if (y == 0) {
+       currentH = h;
+    } else {
+       currentH = y;
+    }
+  }
+}
+
+#undef writable_addr
+#undef shuffleComponent
+
 const gpu::SyncToken& StaticBitmapImage::GetSyncToken() const {
   static const gpu::SyncToken sync_token;
   return sync_token;
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
@@ -46,6 +46,8 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
   static scoped_refptr<StaticBitmapImage> Create(WTF::ArrayBufferContents&,
                                                  const SkImageInfo&);
 
+  static void ShuffleSubchannelColorData(const void *addr, const SkImageInfo& info, int srcX, int srcY);
+
   bool IsStaticBitmapImage() const override { return true; }
 
   // Methods overridden by all sub-classes
-- 
2.11.0

