Open Bug 1916271 Opened 10 months ago Updated 2 hours ago

Gecko reveals sanitized GPU Characteristics; webkit and blink return hardcoded strings for all users

Categories

(Core :: Graphics: CanvasWebGL, defect)

defect

Tracking

()

People

(Reporter: salahlouffidi, Unassigned, NeedInfo)

References

()

Details

(Keywords: privacy, reporter-external, Whiteboard: [client-bounty-form][fingerprinting])

Attachments

(1 file)

Attached image output.png

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.

Flags: sec-bounty?
Duplicate of this bug: 1916272

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

Group: firefox-core-security
Component: Security → Graphics: CanvasWebGL
Product: Firefox → Core

The privacy.resistfingerprinting change was bug 1337157

Keywords: privacy
Whiteboard: [client-bounty-form] → [client-bounty-form][fingerprinting]
Flags: sec-bounty? → sec-bounty-
See Also: → 1715690

Kelsey, can you comment to this bug?

Flags: needinfo?(jgilbert)
Blocks: gfx-triage

this is confirmed ?

No, this is not confirmed.

No longer blocks: gfx-triage
Duplicate of this bug: 1941847

From bug 1941847 comment 5

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

Status: UNCONFIRMED → NEW
Ever confirmed: true
Summary: Bug Bounty Report: GPU Characteristic Leak in Firefox → Gecko reveals sanitized GPU Characteristics; webkit and blink return hardcoded strings for all users

also see Bug 1693745 - you can probably close it as a dupe

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 UNMASKED_VENDOR_WEBGL as Google Inc. (NVIDIA) - @fkilic sorry for the noise

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.

Flags: needinfo?(jgilbert) → needinfo?(ahale)

Fatih just did some stuff on this and I think can clarify the situation and probably close this bug.

Flags: needinfo?(fkilic)

(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 renderer
  • UNMASKED_RENDERER_WEBGL: In RFP, "Mozilla", otherwise a sanitized renderer
  • VENDOR: 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.

Flags: needinfo?(fkilic)

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...

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.

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.

See Also: → 1976287

That sounds good, but don't forget me

(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

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: