Skip to content

add static canvas noise based on user-data-dir hash #3663

@zimerfmm

Description

@zimerfmm

Description

it allows have static noise

Who's implementing?

  • I'm willing to implement this feature myself

The problem

for example, when i reloaded pixelscan.net i have different canvas hash. solution - use static seed based on tomething static of profile

Possible solutions

--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -40,6 +40,11 @@
 #include "base/i18n/time_formatting.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
+#include "base/rand_util.h"
+#include "base/logging.h"
+#include "base/command_line.h"
+#include "base/hash/hash.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
@@ -1089,6 +1090,16 @@ Document::Document(const DocumentInit& i
   TRACE_EVENT_WITH_FLOW0("blink", "Document::Document", TRACE_ID_LOCAL(this),
                          TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK(agent_);
+  if (RuntimeEnabledFeatures::FingerprintingClientRectsNoiseEnabled()) {
+    // Precompute -0.0003% to 0.0003% noise factor for get*ClientRect*() fingerprinting
+    const auto seed_base_ = base::CommandLine::ForCurrentProcess()->GetSwitchValueNative("user-data-dir");
+    uint32_t seed = seed_base_.empty() ? 0u : base::PersistentHash(base::WideToUTF8(seed_base_));
+    // извлекаем две 20-битные мантиссы из seed
+    double noise_x = 1.0 + (((seed & 0xFFFFF) / static_cast<double>(0x100000)) - 0.5) * 0.000003;
+    double noise_y = 1.0 + ((((seed >> 20) & 0xFFFFF) / static_cast<double>(0x100000)) - 0.5) * 0.000003;
+    noise_factor_x_ = noise_x;
+    noise_factor_y_ = noise_y;
+  }
   if (base::FeatureList::IsEnabled(features::kDelayAsyncScriptExecution) &&
       features::kDelayAsyncScriptExecutionDelayByDefaultParam.Get()) {
     script_runner_delayer_->Activate();
--- 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,11 @@
 
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 
+#include "base/rand_util.h"
+#include "base/logging.h"
+#include "base/command_line.h"
+#include "base/hash/hash.h"
+#include "base/strings/utf_string_conversions.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"
@@ -11,11 +13,13 @@
 #include "third_party/blink/renderer/platform/graphics/image_observer.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
 #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/core/SkSurface.h"
+#include "third_party/skia/src/core/SkColorData.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "v8/include/v8.h"
 
@@ -114,4 +118,163 @@ void StaticBitmapImage::DrawHelper(cc::P
                         ToSkiaRectConstraint(draw_options.clamping_mode));
 }
 
+// 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;
+  }
+
+  const auto add_flgaw = base::CommandLine::ForCurrentProcess()->GetSwitchValueNative("user-data-dir");
+  uint32_t seed = add_flgaw.empty() ? 0u : base::PersistentHash(base::WideToUTF8(add_flgaw));
+
+  // Generate two fixed factors from seed (0..1 range)
+  double shuffleX = (seed & 0xFFFFF) / static_cast<double>(0x100000);
+  double shuffleY = ((seed >> 20) & 0xFFFFF) / static_cast<double>(0x100000);
+
+  // cap maximum pixels to change
+  auto pixels = (w + h) / 128;
+  if (pixels > 10) {
+    pixels = 10;
+  } 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;
+
+  // calculate random coordinates using bisection
+  auto currentW = w, currentH = h;
+  for(;pixels >= 0; pixels--) {
+    int x = currentW * shuffleX, y = currentH * shuffleY;
+
+    // Deterministic per-pixel randomisation amounts
+    uint32_t r = seed;
+    r ^= (pixels * 0x9e3779b9);
+    r ^= (x * 0x8329c8eb);
+    r ^= (y * 0x2a9d8f7b);
+    r = (r ^ (r >> 16)) * 0x85ebca6b;
+    r = (r ^ (r >> 13)) * 0xc2b2ae35;
+    r ^= (r >> 16);
+
+    uint8_t shuffleR = (r >> 24) % 5;
+    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
+
 }  // namespace blink

fingerprinting-flags-client-rects-and-measuretext.patch
flag-fingerprinting-canvas-image-data-noise.patch

Alternatives

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions