Bromite: WebGL fingerprinting deception

Created on 17 Sep 2018  路  16Comments  路  Source: bromite/bromite

Is your feature request related to privacy?
Only privacy-related feature requests are considered.

Yes

Is there a patch available for this feature somewhere?

Not that I'm aware of.

Describe the solution you'd like
A clear and concise description of what you want to happen.

Do you know if it's practical to implement a WebGL fingerprinting deception mechanism similar to the one for Canvas image data?

Specifically, for WebGL image hashes as implemented in https://browserleaks.com/webgl

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

N/A

enhancement

Most helpful comment

Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1428034

Edit: I have added the WebGL image hash to the fingerprinting mitigations page: https://www.bromite.org/detect

All 16 comments

@Eloston I have thought of this in the past, but I quickly dismissed the possibility because of the possible vectorial nature of WebGL renderings. You can have pretty advanced color shifts (and certainly not limited to what we see in that image hash there) and texture usage, so a single pixel change might cause major visual glitches.

However this is just speculation, I have not actually tried and right now by looking at the approach used there perhaps it is possible. If we look at https://browserleaks.com/js/webgl.js towards the end there is:

try{
  var j=new Uint8Array(131072);
  if (a.readPixels(0,0,256,128,a.RGBA,a.UNSIGNED_BYTE,j),i=JSON.stringify(j).replace(/,?"[0-9]+":/g,""),""==i.replace(/^{[0]+}$/g,""))

And a is created as:

a=b.getContext("webgl2",{preserveDrawingBuffer:!0})||
b.getContext("experimental-webgl2",{preserveDrawingBuffer:!0})||
b.getContext("webgl",{preserveDrawingBuffer:!0})||
b.getContext("experimental-webgl",{preserveDrawingBuffer:!0})||
b.getContext("moz-webgl",{preserveDrawingBuffer:!0})}

So if we get a hold of readPixels on all of the above (those available in Chromium), then yes we can just re-use the same subchannel color data randomisation approach on the extracted raster. The only boring/lengthy part would be to implement it for all of the modes supported (not just RGBA) as I did for the SkImage case; likely it is using a SkImage internally as well, so the code might be reused.

I will give a look to this and report back here, thanks for opening this.

@Eloston yesterday I did some digging. The readPixels function is defined in https://github.com/chromium/chromium/blob/ffd9d67094adb539dd2cfd34d3a8aaa57c77fcc7/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h but ultimately is implemented by the GL context.

An example of such context (not sure if there are others in the codebase) is the GLES2Interface, as you can see being used in this test here: https://github.com/chromium/chromium/blob/ffd9d67094adb539dd2cfd34d3a8aaa57c77fcc7/cc/paint/oop_pixeltest.cc#L149

The actual ReadPixels is stubbed here though: https://github.com/chromium/chromium/blob/0c92aa0f72e2dd58e8e8a86e053e79f801dce629/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h#L453

Open questions:

  • is this function coming directly from the platform? e.g. dynamically linked-in, somehow?
  • is this the same for Android/desktop?
  • can we intercept the result?
  • is Skia really involved?

Interesting, thanks for the preliminary research.

Here are my thoughts based on what you found:

can we intercept the result?

Based on the documentation on WebGLRenderingContext.readPixels(), note that the data has to be read into a new array; i.e. the data is copied into the JavaScript environment in order to be accessed.

I think we can look into intercepting the JavaScript interface definition for readPixels; from there we can determine how we can manipulate the data being retrieved (e.g. during the copying into the JavaScript array, or after it has been copied).

is Skia really involved?

Skia is purely a 2D graphics library, and this readPixels method seems to be a thin layer around OpenGL calls. I think it's safe to say this isn't the case.

I think we can look into intercepting the JavaScript interface definition for readPixels; from there we can determine how we can manipulate the data being retrieved (e.g. during the copying into the JavaScript array, or after it has been copied).

Yes we could change the data after it has been copied there, but there must be guarantees that access to the ArrayBufferView is exclusively locked at that time.

Skia is purely a 2D graphics library, and this readPixels method seems to be a thin layer around OpenGL calls. I think it's safe to say this isn't the case.

See https://github.com/chromium/chromium/blob/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.h

See https://github.com/chromium/chromium/blob/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.h

It would be a bit strange to me if they're converting to Skia first, since readPixels seems like a thin layer around the GL calls as I mentioned earlier.

Nevertheless, I would need to start looking at code too in order to contribute something meaningful to this discussion. I can't guarantee I will have the chance to do so anytime soon, though.

Based on the documentation on WebGLRenderingContext.readPixels(), note that the data has to be read into a new array; i.e. the data is copied into the JavaScript environment in order to be accessed.

I think we can look into intercepting the JavaScript interface definition for readPixels;

@Eloston the corresponding IDL functions should be here: https://github.com/chromium/chromium/blob/bf31bedf12761be9d4e56500559f2777cbcbeedd/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc#L734

Interception should be possible right after the call to ReadPixelsHelper, or better inside that function because we do not want to edit pixels if no pixel data was added; specifically here: https://github.com/chromium/chromium/blob/53f3d30ef104e4c91ab0d7eb9683305139072ef3/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc#L4345

If you check that file you will find several calls like ContextGL()->ReadPixels(), that ReadPixels with uppercase R comes from the context, like in the case of GLES2 here:
https://github.com/chromium/chromium/blob/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/gpu/command_buffer/client/gles2_implementation.cc#L3808

Next step would be to check which pixels format are maximally supported, so that we know which ones to implement.

Also check the font fingerprint
https://browserleaks.com/fonts

Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1428034

Edit: I have added the WebGL image hash to the fingerprinting mitigations page: https://www.bromite.org/detect

I have found that even if both WebGL and WebGL2 use ReadPixelsHelper, the poorly documented WebGL2ComputeRenderingContext does not use that function when providing readPixels.

I am saying "poorly documented" because I could not find a W3C or other draft describing it, only this document from Yunchao He (@ Intel): https://docs.google.com/document/d/1EwhDJO_lBH1mGMMwheQUXGhhFk9yoC98Ant3TPqwmmA/view

You can see that readPixels is exposed in the IDL (although not documented in the previous document):
https://github.com/chromium/chromium/blob/cf7ed613af7b01f2e64929f969d3737067e28083/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt#L8610

Covering this method as well poses new challenges as it uses an internal buffer; it might be simpler to remove this undocumented feature of a non-standard interface.

I would consider adding an option for turning off WebGL entirely. As much for it having no real use aside for obscene 3D popups, as for near impossibility of sanitising the shader code.

Somewhere in 7x.x development, Google disabled the flag to disable the WebGL, so now I believe we have to put it back.

I would consider adding an option for turning off WebGL entirely. As much for it having no real use aside for obscene 3D popups, as for near impossibility of sanitising the shader code.

Somewhere in 7x.x development, Google disabled the flag to disable the WebGL, so now I believe we have to put it back.

However, this could provoke a raise in the uniqueness of your fingerprint as a "not supported/broken webgl" support.

Flags for turning off WebGL have been added, see #411; they are not disabled by default in Bromite.

this could provoke a raise in the uniqueness of your fingerprint as a "not supported/broken webgl" support.

@PoorPocketsMcNewHold by disabling WebGL/WebGL2 you are swapping n bits (whichever amount of entropy they convey) with 1 bit, effectively reducing the leak. This bit will however be used in combination with all the other sources, so that in the best case users can be identified as Bromite users and in the worst case as detailed as any Chromium browser allows (even individually).

Some things I noticed:

  • Disabling WebGL in flags doesn't seem to do anything for detection and/or fingerprinting. BrowserLeaks still detects the hardware renderer and the GPU.
  • WebRTC device ID's are not hidden.

Some questions:

  • Any chance for Bromite for Windows? It would be a heavy competitor for Brave.
  • Why disable QUIC by default? Does it pose a security and/or fingerprint risk?
  • On non-rooted Nougat devices, default Android WebView is disabled and Chrome is set as the default Web View. If Chrome is disabled, Nougat auto-selects "Google Web View" (whatever that is). If Chrome is disabled, Bromite WebView is installed, then does Google WebView use Bromite WebView? Nougat is special when it comes to WebView thing.

Some questions:

  • Any chance for Bromite for Windows? It would be a heavy competitor for Brave.

Go for ungoogled-chromium.
Bromite take some patches from it.

  • Why disable QUIC by default? Does it pose a security and/or fingerprint risk?

https://brave.com/quic-in-the-wild
https://bugs.chromium.org/p/chromium/issues/detail?id=715140#c11
https://bugzilla.mozilla.org/show_bug.cgi?id=1158011
https://github.com/diracdeltas/quic-request

Thanks!
FYI, on original rooted Pixel + LineageOS 17.1 Nightly (Android 10) + Magisk + Magisk Bromite Module, Bromite WebView works perfectly, does not break anything, can be selected in Developer Options. Android WebView can be removed fully. Its awesome!

Flags for turning off WebGL have been added, see #411; they are not disabled by default in Bromite.

this could provoke a raise in the uniqueness of your fingerprint as a "not supported/broken webgl" support.

@PoorPocketsMcNewHold by disabling WebGL/WebGL2 you are swapping _n_ bits (whichever amount of entropy they convey) with 1 bit, effectively reducing the leak. This bit will however be used in combination with all the other sources, so that in the best case users can be identified as Bromite users and in the worst case as detailed as any Chromium browser allows (even individually).

Hello, i don't find WebGL OFF flag in Bromite 85.0.4183.86
Can you please indicate the exact path of how to find it?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

baybal picture baybal  路  29Comments

lowtrucks picture lowtrucks  路  48Comments

csagan5 picture csagan5  路  26Comments

mthomos picture mthomos  路  22Comments

SebiderSushi picture SebiderSushi  路  80Comments