Further improved WebGL spoofing beta.12

- Added ability to spoof webgl2 supported extensions
- Added ability to block parameters that aren't defined in config
- Passing null in config will block the value
- Added more parameters to the demo site
This commit is contained in:
daijro 2024-10-14 20:31:58 -05:00
parent 1532d7bb31
commit 5bfc3ee026
5 changed files with 100 additions and 65 deletions

View file

@ -348,7 +348,7 @@ Because I don't have a dataset of WebGL fingerprints to rotate against, WebGL fi
This repository includes a demo site (see [here](https://github.com/daijro/camoufox/blob/main/scripts/examples/webgl.html)) that prints your browser's WebGL parameters. You can use this site to generate WebGL fingerprints for Camoufox from other devices.
<img src="https://i.imgur.com/jwT5VqG.png" width="80%">
<img src="https://i.imgur.com/jwT5VqG.png">
### Properties
@ -356,19 +356,22 @@ Camoufox supports spoofing WebGL parameters, supported extensions, context attri
**Note**: Do NOT randomly assign values to these properties. WAFs hash your WebGL fingerprint and compare it against a dataset. Randomly assigning values will lead to detection as an unknown device.
| Property | Description | Example |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| webgl:renderer | Spoofs the name of the unmasked WebGL renderer. | `"NVIDIA GeForce GTX 980, or similar"` |
| webgl:vendor | Spoofs the name of the unmasked WebGL vendor. | `"NVIDIA Corporation"` |
| webgl:supportedExtensions | An array of supported WebGL extensions ([full list](https://registry.khronos.org/webgl/extensions/)). | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl:contextAttributes | A dictionary of WebGL context attributes. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl2:contextAttributes | The same as `webgl:contextAttributes`, but for WebGL2. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl:parameters | A dictionary of WebGL parameters. Keys must be GL enums, and values are the values to spoof them as. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl2:parameters | The same as `webgl:parameters`, but for WebGL2. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl:shaderPrecisionFormats | A dictionary of WebGL shader precision formats. Keys are formatted as `"<shaderType>,<precisionType>"`. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl2:shaderPrecisionFormats | The same as `webGL:shaderPrecisionFormats`, but for WebGL2. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl:shaderPrecisionFormats` will be passed. Everything else will be blocked. | `true` |
| webgl2:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl2:shaderPrecisionFormats` will be passed. Everything else will be blocked. | `true` |
| Property | Description | Example |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| webgl:renderer | Spoofs the name of the unmasked WebGL renderer. | `"NVIDIA GeForce GTX 980, or similar"` |
| webgl:vendor | Spoofs the name of the unmasked WebGL vendor. | `"NVIDIA Corporation"` |
| webgl:supportedExtensions | An array of supported WebGL extensions ([full list](https://registry.khronos.org/webgl/extensions/)). | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl2:supportedExtensions | The same as `webgl:supportedExtensions`, but for WebGL2. | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl:contextAttributes | A dictionary of WebGL context attributes. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl2:contextAttributes | The same as `webgl:contextAttributes`, but for WebGL2. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl:parameters | A dictionary of WebGL parameters. Keys must be GL enums, and values are the values to spoof them as. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl2:parameters | The same as `webgl:parameters`, but for WebGL2. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl:parameters:blockIfNotDefined | If set to `true`, only the parameters in `webgl:parameters` will be allowed. Can be dangerous if not used correctly. | `true`/`false` |
| webgl2:parameters:blockIfNotDefined | If set to `true`, only the parameters in `webgl2:parameters` will be allowed. Can be dangerous if not used correctly. | `true`/`false` |
| webgl:shaderPrecisionFormats | A dictionary of WebGL shader precision formats. Keys are formatted as `"<shaderType>,<precisionType>"`. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl2:shaderPrecisionFormats | The same as `webGL:shaderPrecisionFormats`, but for WebGL2. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl:shaderPrecisionFormats` will be allowed. | `true`/`false` |
| webgl2:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl2:shaderPrecisionFormats` will be allowed. | `true`/`false` |
</details>

View file

@ -15,6 +15,7 @@ Written by daijro.
#include <stdlib.h>
#include <stdio.h>
#include <variant>
#include <cstddef>
#ifdef _WIN32
# include <windows.h>
@ -217,14 +218,15 @@ inline std::optional<T> GetAttribute(const std::string attrib, bool isWebGL2) {
return value.value().get<T>();
}
inline std::optional<std::variant<int64_t, bool, double, std::string>> GLParam(
uint32_t pname, bool isWebGL2) {
inline std::optional<
std::variant<int64_t, bool, double, std::string, std::nullptr_t>>
GLParam(uint32_t pname, bool isWebGL2) {
auto value =
MaskConfig::GetNested(isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
std::to_string(pname));
if (!value) return std::nullopt;
auto data = value.value();
// cast the data and return
if (data.is_null()) return std::nullptr_t();
if (data.is_number_integer()) return data.get<int64_t>();
if (data.is_boolean()) return data.get<bool>();
if (data.is_number_float()) return data.get<double>();
@ -249,7 +251,8 @@ inline std::vector<T> MParamGLVector(uint32_t pname,
bool isWebGL2) {
if (auto value = MaskConfig::GetNested(
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
std::to_string(pname)); value.has_value()) {
std::to_string(pname));
value.has_value()) {
if (value.value().is_array()) {
std::array<T, 4UL> result = value.value().get<std::array<T, 4UL>>();
return std::vector<T>(result.begin(), result.end());

View file

@ -1,17 +1,18 @@
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
index db60868f65..afed6eeb7c 100644
index db60868f65..7361f0fc9c 100644
--- a/dom/canvas/ClientWebGLContext.cpp
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -4,6 +4,8 @@
@@ -4,6 +4,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ClientWebGLContext.h"
+#include "MaskConfig.hpp"
+#include <algorithm>
+#include <cstddef>
#include <bitset>
@@ -744,6 +746,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
@@ -744,6 +747,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
}
@ -25,7 +26,7 @@ index db60868f65..afed6eeb7c 100644
void ClientWebGLContext::GetContextAttributes(
dom::Nullable<dom::WebGLContextAttributes>& retval) {
retval.SetNull();
@@ -754,14 +763,38 @@ void ClientWebGLContext::GetContextAttributes(
@@ -754,14 +764,38 @@ void ClientWebGLContext::GetContextAttributes(
const auto& options = mNotLost->info.options;
@ -72,7 +73,7 @@ index db60868f65..afed6eeb7c 100644
}
// -----------------------
@@ -979,18 +1012,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
@@ -979,18 +1013,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
auto ret = std::unordered_map<GLenum, bool>{};
@ -111,11 +112,13 @@ index db60868f65..afed6eeb7c 100644
}
return ret;
@@ -2058,6 +2101,28 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2058,6 +2102,57 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
const auto& state = State();
// -
+ std::optional<std::variant<int64_t, bool, double, std::string>> data;
+ std::optional<
+ std::variant<int64_t, bool, double, std::string, std::nullptr_t>>
+ data;
+ data = MaskConfig::GLParam(pname, mIsWebGL2);
+
+ if (data.has_value()) {
@ -136,11 +139,38 @@ index db60868f65..afed6eeb7c 100644
+ retval.set(StringValue(cx, std::get<std::string>(value), rv));
+ return;
+ }
+ if (std::holds_alternative<std::nullptr_t>(value)) {
+ retval.set(JS::NullValue());
+ return;
+ }
+ }
+ // If the value is not array (we will handle those later),
+ // then check if it should be blocked.
+ switch (pname) {
+ case LOCAL_GL_DEPTH_RANGE:
+ case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
+ case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
+ case LOCAL_GL_COLOR_CLEAR_VALUE:
+ case LOCAL_GL_BLEND_COLOR:
+ case LOCAL_GL_MAX_VIEWPORT_DIMS:
+ case LOCAL_GL_SCISSOR_BOX:
+ case LOCAL_GL_VIEWPORT:
+ case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
+ case LOCAL_GL_COLOR_WRITEMASK:
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
+ break;
+ default:
+ if (MaskConfig::GetBool(mIsWebGL2 ? "webgl2:parameters:blockIfNotDefined"
+ : "webgl:parameters:blockIfNotDefined")) {
+ retval.set(JS::NullValue());
+ return;
+ }
+ }
const auto fnSetRetval_Buffer = [&](const GLenum target) {
const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
@@ -2163,49 +2228,84 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2163,49 +2258,84 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
// 2 floats
case LOCAL_GL_DEPTH_RANGE:
@ -238,7 +268,7 @@ index db60868f65..afed6eeb7c 100644
return;
}
@@ -2385,6 +2485,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2385,6 +2515,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
switch (pname) {
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
@ -249,7 +279,7 @@ index db60868f65..afed6eeb7c 100644
ret = GetUnmaskedRenderer();
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
*ret = webgl::SanitizeRenderer(*ret);
@@ -2392,6 +2496,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2392,6 +2526,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
break;
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
@ -260,7 +290,7 @@ index db60868f65..afed6eeb7c 100644
ret = GetUnmaskedVendor();
break;
@@ -2482,7 +2590,9 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2482,7 +2620,9 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
case LOCAL_GL_COLOR_WRITEMASK: {
const auto mask = uint8_t(*maybe);
const auto bs = std::bitset<4>(mask);
@ -271,7 +301,7 @@ index db60868f65..afed6eeb7c 100644
JS::Rooted<JS::Value> arr(cx);
if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
rv = NS_ERROR_OUT_OF_MEMORY;
@@ -2865,6 +2975,24 @@ ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
@@ -2865,6 +3005,24 @@ ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
const GLenum precisiontype) {
if (IsContextLost()) return nullptr;
const auto info = [&]() {
@ -296,7 +326,7 @@ index db60868f65..afed6eeb7c 100644
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
@@ -5822,6 +5950,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
@@ -5822,6 +5980,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
return false;
}
@ -314,14 +344,15 @@ index db60868f65..afed6eeb7c 100644
const auto& limits = Limits();
return limits.supportedExtensions[ext];
}
@@ -5833,6 +5972,17 @@ void ClientWebGLContext::GetSupportedExtensions(
@@ -5833,6 +6002,18 @@ void ClientWebGLContext::GetSupportedExtensions(
if (!mNotLost) return;
auto& retarr = retval.SetValue();
+
+ // Implement separately to prevent O(n^2) timing
+ if (std::vector<std::string> maskValues =
+ MaskConfig::GetStringList("webgl:supportedExtensions");
+ MaskConfig::GetStringList(mIsWebGL2 ? "webgl2:supportedExtensions"
+ : "webgl:supportedExtensions");
+ !maskValues.empty()) {
+ for (const auto& ext : maskValues) {
+ retarr.AppendElement(NS_ConvertUTF8toUTF16(ext));

View file

@ -87,7 +87,8 @@
border-radius: 4px;
display: none;
position: relative;
transition: opacity 0.3s ease-out, max-height 0.3s ease-out, padding 0.3s ease-out;
transition: opacity 0.3s ease-out, max-height 0.3s ease-out,
padding 0.3s ease-out;
opacity: 1;
max-height: 100px;
overflow: hidden;
@ -114,7 +115,10 @@
<body>
<div class="container">
<h1>Your WebGL Information (in Camoufox format)</h1>
<div id="warning">Warning: This browser is not Firefox. The fingerprint below will not run properly on Camoufox.<button id="close-warning">&times;</button></div>
<div id="warning">
Warning: This browser is not Firefox. The fingerprint below will not run
properly on Camoufox.<button id="close-warning">&times;</button>
</div>
<div id="hash"></div>
<pre id="output"></pre>
<div class="button-container">
@ -152,33 +156,21 @@
return {};
}
function getMaxAnisotropy(ctx) {
if (ctx) {
const ext =
ctx.getExtension("EXT_texture_filter_anisotropic") ||
ctx.getExtension("WEBKIT_EXT_texture_filter_anisotropic") ||
ctx.getExtension("MOZ_EXT_texture_filter_anisotropic");
if (ext) {
return ctx.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
}
}
return null;
}
const glEnums = [
2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961,
2962, 2963, 2964, 2965, 2966, 2967, 2968, 2978, 3024, 3042, 3088,
3089, 3106, 3107, 3317, 3333, 3379, 3386, 3408, 3410, 3411, 3412,
3413, 3414, 3415, 7936, 7937, 7938, 10752, 32773, 32777, 32823, 32824,
32873, 32883, 32936, 32937, 32938, 32939, 32968, 32969, 32970, 32971,
33170, 33901, 33902, 34016, 34024, 34045, 34047, 34068, 34076, 34467,
34816, 34817, 34818, 34819, 34852, 34877, 34921, 34930, 34964, 34965,
35071, 35076, 35077, 35371, 35373, 35374, 35375, 35376, 35377, 35379,
35380, 35657, 35658, 35659, 35660, 35661, 35724, 35725, 35968, 35978,
35979, 36003, 36004, 36005, 36006, 36007, 36063, 36183, 36347, 36348,
36349, 37154, 37157, 37440, 37441, 37443, 37444, 37445, 37446,
2962, 2963, 2964, 2965, 2966, 2967, 2968, 2978, 3024, 3042, 3074,
3088, 3089, 3106, 3107, 3314, 3315, 3316, 3317, 3330, 3331, 3332,
3333, 3379, 3386, 3408, 3410, 3411, 3412, 3413, 3414, 3415, 7936,
7937, 7938, 10752, 32773, 32777, 32823, 32824, 32873, 32877, 32878,
32883, 32926, 32928, 32936, 32937, 32938, 32939, 32968, 32969, 32970,
32971, 33000, 33001, 33170, 33901, 33902, 34016, 34024, 34045, 34047,
34068, 34076, 34467, 34816, 34817, 34818, 34819, 34852, 34853, 34854,
34855, 34856, 34857, 34858, 34859, 34860, 34877, 34921, 34930, 34964,
34965, 35071, 35076, 35077, 35371, 35373, 35374, 35375, 35376, 35377,
35379, 35380, 35657, 35658, 35659, 35660, 35661, 35723, 35724, 35725,
35738, 35739, 35968, 35977, 35978, 35979, 36003, 36004, 36005, 36006,
36007, 36063, 36183, 36203, 36345, 36347, 36348, 36349, 36387, 36388,
37137, 37154, 37157, 37440, 37441, 37443, 37444, 37445, 37446, 37447,
];
let rendererInfo = null;
@ -300,15 +292,18 @@
"DOMContentLoaded",
async () => {
// Check if the user agent is Firefox
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (!isFirefox) {
document.getElementById('warning').style.display = 'block';
document.getElementById("warning").style.display = "block";
}
// Add event listener for close button
document.getElementById('close-warning').addEventListener('click', function() {
document.getElementById('warning').classList.add('hide');
});
document
.getElementById("close-warning")
.addEventListener("click", function () {
document.getElementById("warning").classList.add("hide");
});
const [webglDetail, webgl2Detail] = await Promise.all([
dumpWebGLCore("webgl", "experimental-webgl"),

View file

@ -70,8 +70,11 @@
{ "property": "webgl:renderer", "type": "str" },
{ "property": "webgl:vendor", "type": "str" },
{ "property": "webgl:supportedExtensions", "type": "array" },
{ "property": "webgl2:supportedExtensions", "type": "array" },
{ "property": "webgl:parameters", "type": "dict" },
{ "property": "webgl:parameters:blockIfNotDefined", "type": "bool" },
{ "property": "webgl2:parameters", "type": "dict" },
{ "property": "webgl2:parameters:blockIfNotDefined", "type": "bool" },
{ "property": "webgl:shaderPrecisionFormats", "type": "dict" },
{ "property": "webgl:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" },
{ "property": "webgl2:shaderPrecisionFormats", "type": "dict" },