Open Thorin-Oakenpants opened 1 year ago
Q1 - answered: I just needed two canvas elements
<div>
<canvas id="webglcanvas"></canvas>
<canvas id="webgl2canvas"></canvas>
</div>
is there some benefit to creating the canvas on the fly?
The gist is good. Always welcome... All credit for that one goes to https://github.com/CesiumGS/webglreport. My refactoring is minimal.
There are a few techniques not in there, but worth checking out. These are mostly experimental ideas that can be used to determine trustworthy vs. suspicious GPUs.
// Highlight common GPU brands
// gpu can be a string of the renderer or both the vendor and renderer
function getGpuBrand(gpu) {
if (!gpu) return
const gpuBrandMatcher = /(adreno|amd|apple|intel|llvm|mali|microsoft|nvidia|parallels|powervr|samsung|swiftshader|virtualbox|vmware)/i
const brand = (
/radeon/i.test(gpu) ? 'AMD' :
/geforce/i.test(gpu) ? 'NVIDIA' :
( (gpuBrandMatcher.exec(gpu) || [])[0] || 'Other' )
)
return brand
}
// Takes the parameter object and generate a fingerprint of sorted numeric values
// This can be used to create a known good hash table and known brands can be labelled for each hash.
function generateParamFingerprint(parameters) {
if (!parameters) return
return '' + [
...new Set(Object.values(parameters)
.filter((val) => val && typeof val != 'string')
.flat()
.map((val) => Number(val))),
].sort((a, b) => (a - b))
}
Q1
A premade canvas in the HTML can sometimes bypass scripts observing createElement('canvas')
. Optionally, one context is good. webgl2
has more parameters. Since we are not appending the element to the page, a new document.createElement('canvas')
on repeat re-runs should be fine.
Q2
newMethods
is just functions in WebGLRenderingContext.prototype
. It's not that great for fingerprinting (compared to CSS and the Window). It just current browser features in the prototype.
Q3
I forgot to add the sections like "Vertex Shader" and "Rasterizer", but these can be determined based on the property names. We just need to create a map.
parameterType = {
'Vertex Shader': [
'MAX_VERTEX_ATTRIBS',
'MAX_VERTEX_TEXTURE_IMAGE_UNITS',
'MAX_VERTEX_UNIFORM_VECTORS',
]
}
if (ParameterType['Vertex Shader'].includes(propertyName)) {
// add to Vertex Shader section
}
Q4
I merge as a final simplification, but also check and flag mismatch behind the scenes. I recall an incident where a mismatch was valid, so I just treat it as questionable.
Q5
We can trap the error and push to a global collection. There are a few places different errors might get thrown due to users disabling or blocking the API, or parts of the API.
I merge as a final simplification
hmmm, so I'm not sure how I want to present all this info - where is all the experimental stuff? I mean we can see webgl and webgl2. e.g. here's just checking for support. https://browserleaks.com/webgl has THREE toggles. Is this info in there (sorry for being a lazy bum but not into learning about webgl right now, lol) or it something we need to add
e.g.
let types = ["webgl", "webgl2", "experimental-webgl"]
types.forEach(function(type){
try {
let canvas = window.document.createElement("canvas")
try {
var context = canvas.getContext(type)
if (!context) {
throw new Error()
}
console.log(type, "supported")
} catch(e) {
console.log(type, "not supported")
}
} catch(e) {
console.log("canvas failed")
}
})
I think I can just take the objects and pull things out - I need to check various things as RFP protected, so they need to be separate.
Since we are not appending the element to the page,
doh. nothing to remove. nice. BTW not creating the canvas is a tiny perf win :) woo!
https://github.com/arkenfox/TZP/issues/234#issuecomment-1495196774
can you add that to your gist so I can copy it, thanks
Refactored: https://gist.github.com/abrahamjuliot/7baf3be8c451d23f7a8693d7e28a35e2
experimental-webgl
separatelygpuHash
. The length may vary, so we can feed this to a SHA or mini hash instead of join
awesome, thx
the gpuBrand is redundant (but nice) - I can drop that
the gpuHash doesn't make much sense to me - sorting values could/would create collisions ? for example on it's own
NVIDIA:0:1:8:16:23:24:30:32:127:1024:4095:16384:32767
VERTEX_SHADER_BEST_FLOAT_PRECISION: Array(3) [ 23, 127, 127 ]
This doesn't make a good fingerprint for max entropy, but I get it, it's a nice wee string. Unless I'm missing something
gpu
doesn't handle an empty string, returns ,
let gpuV = cleanFn(parameters.UNMASKED_VENDOR_WEBGL),
gpuR = cleanFn(parameters.UNMASKED_RENDERER_WEBGL)
const gpu = String([gpuV, gpuR])
edit: well, it does handle it, it's just not very reader friendly :)
I'm confused. Top is TB (which has RFP webgl mitigations - you get the same result in FF with RFP enabled), bottom is nightly with no RFP.
The bottom test looks fine, but at first glance seem to have duplication, but the top test - why are we not returning a debugRendererInfo
object with undefined items (and extensions that mimic this RFP behavior). Why is webglcontext.renderer different to gpuRenderer ?
https://bugzilla.mozilla.org/show_bug.cgi?id=1337157 - lemme look at this. I know it's supposed to return undefined or blanks and/or Mozilla
const Categories = {
'debugRendererInfo': [
'UNMASKED_VENDOR_WEBGL',
'UNMASKED_RENDERER_WEBGL',
], // snip
}
parameters = { // snip
UNMASKED_VENDOR_WEBGL: getUnmasked(context, 'UNMASKED_VENDOR_WEBGL'),
UNMASKED_RENDERER_WEBGL: getUnmasked(context, 'UNMASKED_RENDERER_WEBGL')
}
parameters.DIRECT_3D = /Direct3D|D3D(\d+)/.test(parameters.UNMASKED_RENDERER_WEBGL)
gpuVendor = parameters.UNMASKED_VENDOR_WEBGL
gpuRenderer = parameters.UNMASKED_RENDERER_WEBGL
so gpu
(which I split into gpuVendor and gpuRenderer) is redundant then? right? It's a direct lookup of debuginfo? right?
Yeah, gpu is non-essential. I forgot. GPU showing up under render is this issue. It can be ignored, but the way to around is to feature detect the version and then use renderer instead of debug info, but its only necessary to remove the console warning.
gpuHash
This too is not needed.
It can be useful to put known good fingerprints into a lookup table (similar to known good audio sums). Clashing is likely and okay, as long random WebGL fingerprints find it hard to fit in. I'm guessing, based on limited data on CreepJS, the table size would require no more than 100 hashes. Everything unknown can be questioned until it is established trustworthy. This is more of a test concept, I suppose. It would require many samples.
tis all cool. The RFP notation should be pretty simple now I refreshed my memory as to what we were doing with it - there's more to it than just vendor/renderer. But easy to check the few places it shows.
but its only necessary to remove the console warning
I log all errors as part of the error entropy :)
gpu/gpuHash/gpuBrand
no worries. I get it was experimental - but this is gecko and all I care about is Tor Browser and RFP. If I check prototype lies (I'll need to check) then I can just return the whole thing as useless (might revisit later)
when we collect data from surveys (via a Tor Browser annual or bi-annual FP drive - 100% opt-in with one test per profile), we can just reject collecting tainted data based on prototype lies alone - i.e either it is all empty or it matches NoScripts signature
There's also these anti-detection browsers that fake the GPUs at engine level. No prototype lies. For Firefox, I think they use what is called "Stealthfox Browser". The only way I'm aware of to detect them is by using a look-up table of known good hashes. WebGL is too high entropy for a local lookup, but this lower entropy hash works. I have not used it on CreepJS, but I have the data visually from last 60 days of bad traffic.
gummy bear browsers :) lols ... I'm not too worried about it TBH, I need to focus on actual TB and FF + RFP users
Something I discovered, many of them have a frozen max stack size fingerprint. Something with the way it's compiled, I guess.
I'm not entirely sure what you mean by that, tell me more :) - you had me at "something"
Yeah, math PoCs are great, like known pixel tests, joining chars vs individual chars in textmetrics width, domrect etc. Enumerating goodness is hard and no bulletproof - so I understand the mini simplified hash - like the simplified (less measurements) of https://arkenfox.github.io/TZP/tests/domrectspoofratio.html that covers multiple chrome results
I just had to drop enumerating goodness for audio in FF (TZP 2.0 is gecko only) - math library changes on android, and they will change again, and then RFP is also doing something with it (or it will apply for all) - https://bugzilla.mozilla.org/show_bug.cgi?id=1358149#c26
you had me at "something"
do you mean recursion, stack depth, rather than some webgl thing?
Yeah, recursion, stack depth. I have not tested for Firefox, but it's somewhat of an unbeatable fingerprint for custom Chrome builds that are slow to update.
OT: but I had an email exchange with a moz dev about this, and it's a good (fuzzy) FP for determining ion and jit etc
@abrahamjuliot - is this uptodate? https://gist.github.com/abrahamjuliot/7baf3be8c451d23f7a8693d7e28a35e2
I don't want to re-invent the wheel, so want to use your code (accredited) if that's OK - please advise
ATM, I only want to collect the parameters, extensions, vendor, etc - not any actual rendering
so some questions
Q1
https://gist.github.com/abrahamjuliot/7baf3be8c451d23f7a8693d7e28a35e2#file-webgl-js-L91-L94
so here, I wanted to use an already created canvas (I think it saves time)
but I get errors for webgl2 (webgl is fine)
I also don't see where you remove and cleanup the
document.createElement('canvas')
- remember, TZP allows section reruns so I don't want to end up with dozens of canvas elementsQ2
what is newmethods meant to signify in webgl2 ?
Q3
for data collection/entropy - I am not webgl savvy - but remember in the old TZP repo we discussed, and I mocked up, a webgl section that would return webgl, webgl2, experimental results - a bit like
how is this all reflected in the objects in the console? I'm a bit lost
Q4
how/what are you doing with this data to only show one set of parameters on creepy - I can see that some are identical in webgl 1 vs 2 - and there is a diffs output. Are you merging these?
Q5?
I guess I'll have to play to catch errors and return that - you seem to just console them and return undefined (I guess I could do that)