Gecko reveals sanitized GPU Characteristics; webkit and blink return hardcoded strings for all users
Categories
(Core :: Graphics: CanvasWebGL, defect)
Tracking
()
People
(Reporter: salahlouffidi, Unassigned, NeedInfo)
References
()
Details
(Keywords: privacy, reporter-external, Whiteboard: [client-bounty-form][fingerprinting])
Attachments
(1 file)
33.35 KB,
image/png
|
Details |
Summary:
A vulnerability has been identified in Firefox that allows a malicious website to retrieve detailed GPU information using WebGL, which may expose the user's hardware characteristics. This information can be used for fingerprinting and tracking purposes. The issue is similar to previously reported vulnerabilities in other browsers but presents unique risks in the context of Firefox's privacy-focused user base.
Vulnerability Details:
Browser Affected: Mozilla Firefox
Vulnerability Type: Information Disclosure (GPU Characteristic Leak)
Impact: Medium
Components Involved: WebGL, WEBGL_debug_renderer_info extension
Description:
The vulnerability allows any website with access to WebGL to extract detailed GPU information from the user's system. The provided Proof of Concept (PoC) demonstrates that the GPU vendor, model, and the rendering engine are disclosed. This data includes specific identifiers such as the GPU model and driver version, which can be used to create a unique fingerprint of the user's device.
Proof of Concept (PoC):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU Information</title>
</head>
<body>
<h1>GPU Information</h1>
<button id="gpuButton">Show GPU Information</button>
<div id="gpuInfo" style="margin-top: 20px;"></div>
<script>
// Function to show GPU information
function showGPUInformation() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
const gpuInfo = `
<p>GPU Vendor: ${vendor}</p>
<p>GPU Renderer: ${renderer}</p>
`;
document.getElementById("gpuInfo").innerHTML = gpuInfo;
} else {
document.getElementById("gpuInfo").innerHTML = "<p>WebGL Debug Renderer Info not available.</p>";
}
} else {
document.getElementById("gpuInfo").innerHTML = "<p>WebGL not supported in this browser.</p>";
}
}
// Attach the click event to the button
document.getElementById("gpuButton").addEventListener("click", showGPUInformation);
</script>
</body>
</html>
the output
output.png
The detailed GPU information can be exploited to create a persistent fingerprint of the user's device, even if other privacy measures are in place (e.g., clearing cookies or using VPNs). This persistent fingerprint can undermine the user's anonymity, making them susceptible to tracking across different sessions and websites.
Steps to Reproduce:
Open the provided PoC HTML file in Firefox.
Click the "Show GPU Information" button.
Observe the GPU vendor and renderer details displayed on the webpage.
Recommended Fix:
To mitigate this issue, it is recommended to restrict access to detailed GPU information through the WebGL API by either:
Disabling the WEBGL_debug_renderer_info extension by default.
Masking or generalizing the output provided by this extension to reduce the specificity of the information exposed.
References:
Similar vulnerabilities in Chromium-based browsers have been addressed by limiting the granularity of GPU information accessible through WebGL.
Comment 2•10 months ago
|
||
We must have this on file somewhere as an issue, given we disable it when you enable privacy.resistfingerprinting and https://browserleaks.com/webgl has been around forever. But I can't find a bug that aims to do anything about this in normal browsing mode.
Given browserleaks we don't need to keep this hidden
Comment 3•10 months ago
|
||
The privacy.resistfingerprinting
change was bug 1337157
Updated•10 months ago
|
Comment 4•10 months ago
|
||
see Bug 1715690
Updated•10 months ago
|
Updated•10 months ago
|
Reporter | ||
Comment 6•10 months ago
|
||
this is confirmed ?
Comment 7•10 months ago
|
||
No, this is not confirmed.
Updated•10 months ago
|
Comment 9•6 months ago
•
|
||
We sanitize while they hardcode it completely GL_RENDERER and GL_VENDOR from blink
blink is just doing what WebKit is doing.
Our values are not completely raw information, but we reveal a lot more to websites than other browsers think they need to. See https://searchfox.org/mozilla-central/source/dom/canvas/SanitizeRenderer.cpp
Updated•6 months ago
|
Updated•6 months ago
|
Comment 10•5 months ago
|
||
also see Bug 1693745 - you can probably close it as a dupe
Comment 11•5 months ago
•
|
||
let's not play games here, this is not some magical blink FP protection :) e.g.
// Chrome
{
UNMASKED_RENDERER_WEBGL: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002487) Direct3D11 vs_5_0 ps_5_0, D3D11)",
RENDERER: "WebKit WebGL",
UNMASKED_VENDOR_WEBGL: "Google Inc. (NVIDIA)",
VENDOR: "WebKit",
}
// Firefox
{
UNMASKED_RENDERER_WEBGL: "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
RENDERER: "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
UNMASKED_VENDOR_WEBGL: "Google Inc. (NVIDIA)",
VENDOR: "Mozilla",
}
// RFP
{
UNMASKED_RENDERER_WEBGL: undefined, // it actually throws a TypeError [extension] is null
RENDERER: "Mozilla",
UNMASKED_VENDOR_WEBGL: undefined, // TypeError
VENDOR: "Mozilla",
}
we could probably follow suit and make RENDERER just return "Mozilla", but IANAE
edit: we could also reduce errors caused by RFP and return sorry for the noiseUNMASKED_VENDOR_WEBGL
as Google Inc. (NVIDIA)
- @fkilic
Comment 12•2 days ago
|
||
Redirect a needinfo that is pending on an inactive user to the triage owner.
:ahale, since the bug doesn't have a severity set, could you please set the severity or close the bug?
For more information, please visit BugBot documentation.
Comment 13•2 days ago
|
||
Fatih just did some stuff on this and I think can clarify the situation and probably close this bug.
Comment 14•2 days ago
•
|
||
(I remember closing another bug that's very similar to this, but can't find it now)
If you run
(() => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const info = {
renderer: gl.getParameter(gl.RENDERER),
vendor: gl.getParameter(gl.VENDOR),
unmaskedRenderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL),
unmaskedVendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
};
console.log(info);
})()
You'll get the following on Firefox
RENDERER
: In RFP, "Mozilla", otherwise a sanitized rendererUNMASKED_RENDERER_WEBGL
: In RFP, "Mozilla", otherwise a sanitized rendererVENDOR
: Always "Mozilla"UNMASKED_VENDOR_WEBGL
: In RFP, "Mozilla', otherwise unmasked vendor info
So, the only thing that we don't sanitize/limit in non-RFP is the unmasked vendor info.
Comment 15•2 days ago
|
||
From a subset of users:
Google Inc. (Intel) 213343
Qualcomm 72588
ARM 62064
Google Inc. (NVIDIA) 57882
Google Inc. (AMD) 47822
Google Inc. (Microsoft) 8465
Apple 7679
Samsung Electronics Co., Ltd. 6897
Intel 6080
Imagination Technologies 5879
Intel Inc. 3822
AMD 2131
ATI Technologies Inc. 1580
NVIDIA Corporation 1492
Mesa 1222
Google Inc. (Unknown) 372
None 247
Intel Open Source Technology Center 204
Broadcom 85
HUAWEI 60
Mesa/X.org 45
VMware, Inc. 39
X.Org 37
Mesa Project 30
nouveau 27
Google Inc. (ARM) 16
freedreno 11
Google (Apple) 9
Google (NVIDIA Corporation) 3
X.Org R300 Project 3
12 Other Values 20
So there's some entropy there, but way less than implied in the original post, which probably didn't realize we sanitize the UNMASKED_RENDERER_WEBGL and it just looks raw.
I think this is WONTFIX - if we wanted to make a change here, we would probably trial it in FPP first. I'm not sure what we would do, maybe collapse all the Linux users as a start...
Comment 16•2 days ago
•
|
||
Running
Object.entries({
"Google Inc. (Intel)": 213343,
"Qualcomm": 72588,
"ARM": 62064,
"Google Inc. (NVIDIA)": 57882,
"Google Inc. (AMD)": 47822,
"Google Inc. (Microsoft)": 8465,
"Apple": 7679,
"Samsung Electronics Co., Ltd.": 6897,
"Intel": 6080,
"Imagination Technologies": 5879,
"Intel Inc.": 3822,
"AMD": 2131,
"ATI Technologies Inc.": 1580,
"NVIDIA Corporation": 1492,
"Mesa": 1222,
"Google Inc. (Unknown)": 372,
"None": 247,
"Intel Open Source Technology ": 204,
"Broadcom": 85,
"HUAWEI": 60,
"Mesa/X.org": 45,
"VMware, Inc.": 39,
"X.Org": 37,
"Mesa Project": 30,
"nouveau": 27,
"Google Inc. (ARM)": 16,
"freedreno": 11,
"Google (Apple)": 9,
"Google (NVIDIA Corporation)": 3,
"X.Org R300 Project": 3,
"12 Other Values": 20
}).reduce((acc, [name, count]) => {
for (const knownVendor of ["Intel", "Qualcomm", "ARM", "NVIDIA", "AMD", "Microsoft", "Apple", "Samsung", "Imagination Technologies", "ATI"]) {
if (name.includes(knownVendor)) {
acc[knownVendor] = (acc[knownVendor] ?? 0) + count;
return acc;
}
}
acc["Other"] = (acc["Other"] ?? 0) + count;
return acc;
}, {});
changes numbers to
AMD: 49953
ARM: 62080
ATI: 1580
Apple: 7688
"Imagination Technologies": 5879
Intel: 223449
Microsoft: 8465
NVIDIA: 59377
Other: 2198
Qualcomm: 72588
Samsung: 6897
We could add/remove to the knownVendors
list.
So maybe we can implement this basic sanitization function.
Comment 17•2 days ago
|
||
That sounds good to me - if the Graphics team think it's worth trying, we can code it up and turn it into an experiment.
Reporter | ||
Comment 18•6 hours ago
|
||
That sounds good, but don't forget me
Comment 19•2 hours ago
|
||
(In reply to salah eddine louffidi from comment #18)
That sounds good, but don't forget me
Looks like there was a misunderstanding of how our bug bounty system worked. We marked this as non-elegible 10 months ago.
Please follow our public documentation first, if you have further questions
Description
•