Category: Hardware / Low-Level Rendering
1. Description
GPU fingerprinting exploits the fact that graphics rendering is not bit-identical across different hardware. The same JavaScript code produces different pixel outputs on different GPUs due to:
- GPU microarchitecture (ALU count, rasterizer units, TMUs, ROPs)
- Driver version and shader compiler optimizations
- Floating-point precision in shader calculations
- Anti-aliasing algorithms and their implementation
- Subpixel rendering order (RGB vs BGR)
- Color space conversion matrices
- Texture compression support
- Max texture size and other hardware limits
2. Vectors
2.1 Canvas Fingerprinting
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.font = '14px Arial'
ctx.fillStyle = '#f60'
ctx.fillRect(125, 1, 62, 20)
ctx.fillStyle = '#069'
ctx.fillText('Test string', 2, 15)
const hash = md5(canvas.toDataURL())
Entropy: ~5-7 bits (32-128 devices distinguishable)
Stability: High stable across browser restarts, moderate across browser updates
2.2 WebGL Fingerprinting
const gl = canvas.getContext('webgl')
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
// Exact GPU model:
const renderer = debugInfo.UNMASKED_RENDERER_WEBGL
// "NVIDIA GeForce RTX 3080/PCIe/SSE2"
const vendor = debugInfo.UNMASKED_VENDOR_WEBGL
// "NVIDIA Corporation"
Additional WebGL signals:
- WebGL scene render hash (render a 3D scene, hash pixels)
- Shader precision (highp, mediump, lowp support and precision)
MAX_TEXTURE_SIZE,MAX_VERTEX_ATTRIBS,MAX_VIEWPORT_DIMS(50+ numeric parameters)- WebGL extension list
Entropy: ~20-25 bits (renderer + extensions + limits)
Stability: Very High changes only with GPU driver updates
2.3 WebGPU Fingerprinting
const adapter = await navigator.gpu.requestAdapter()
const info = await adapter.requestAdapterInfo()
// { vendor: "nvidia", architecture: "ampere", device: "GA102", driver: "535.129.03", backend: "Vulkan" }
// adapter.limits contains 50+ numeric capabilities
// adapter.features contains ~30+ feature flags
Entropy: ~30+ bits (adapter info + limits + features)
Stability: Very High hardware-locked
3. Attacker's Strengths
| Strength | Explanation |
|---|---|
| Hardware-locked | GPU fingerprint cannot change without hardware replacement |
| Cross-browser | Same GPU produces similar fingerprints across Chrome, Firefox, Edge |
| Passive | Requires no user interaction, runs in background |
| No network dependency | Rendering is local, no request needed |
| Precise | Exact GPU model is highly identifying (rare GPUs are very unique) |
| Consistent | Same GPU driver version same render output every time |
4. Attacker's Weaknesses
| Weakness | Explanation | Exploitable? |
|---|---|---|
| Blockable | WebGL/WebGPU can be disabled in browser settings | Yes but detectable |
| Noise injection | CanvasBlocker adds random pixels to canvas output, breaking fingerprint | Yes but detectable |
| Driver updates | New drivers change rendering output, invalidating stored fingerprints | Occasional |
| Tor Browser | Software rendering normalizes all GPU output | Yes but massive performance cost |
| Identifies GPU, not user | All users with same GPU model look identical | Only for common GPUs |
5. Detection of Tampering
Attackers can detect canvas/WebGL spoofing via:
| Technique | How It Works |
|---|---|
| Sequential read comparison | Read canvas 2-3 times, compare hashes. Noise injection produces different hashes. |
| Statistical analysis | Real hardware output has characteristic noise patterns; injected noise looks different |
| Cross-API correlation | Canvas hash + WebGL renderer + AudioContext should all be consistent |
| Feature presence | WEBGL_debug_renderer_info returning null is suspicious |
| WebGPU adapter info missing | If WebGL works but WebGPU doesn't, that's unusual |
6. Mitigations for Obscura
6.1 What Obscura Can Do
| Mitigation | Effectiveness | Detectability | Implementation |
|---|---|---|---|
Block WEBGL_debug_renderer_info |
High (hides GPU model) | Medium | JS injection: return null |
| Block WebGPU adapter info | High (hides GPU info) | Medium | JS injection: requestAdapter null |
| Canvas noise injection | Medium | High | JS injection: CanvasBlocker algorithm |
| Block WebGL entirely | Very High | High | JS injection: getContext null |
| Block WebGPU entirely | Very High | High | JS injection: requestAdapter null |
| Round canvas dimensions | Low | Medium | JS injection: Math.round() |
6.2 What Obscura Cannot Do
| Cannot | Why |
|---|---|
| Change actual GPU rendering | Rendering happens on local GPU hardware |
| Spoof WebGL scene render hash | Rendering output is hardware-dependent |
| Spoof WebGPU limits | Limits are queried from the physical adapter |
| Normalize canvas output across users | Requires software GPU (SwiftShader) Tor Browser only |
6.3 Recommended Approach
1. Block WEBGL_debug_renderer_info (hides GPU model high value, low cost)
2. Block WebGPU adapterInfo (hides next-gen GPU info high value, low cost)
3. Inject canvas noise (breaks canvas fingerprint medium value, medium cost)
4. If device is powerful: suggest Tor (complete protection high value, high user cost)
7. Research References
- Mowery, K. & Shacham, H. (2012). "Pixel Perfect: Fingerprinting Canvas in HTML5." WOOT 2012.
- Acar, G. et al. (2014). "The Web Never Forgets: Persistent Tracking Mechanisms in the Wild." CCS 2014.
- Cao, Y. et al. (2017). "WebRTC and Canvas-based Device Fingerprinting." PET Symposium 2017.
- FingerprintJS (2026). "Canvas Fingerprinting: What It Is and How It Works." fingerprintjs.com/blog.