mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-10 15:32:06 -08:00
feat: WebGL fingerprint spoofing
Experimental WebGL fingerprint injection. - Allows the ability to set all WebGL parameters, supported extension list, shader precision formats, & context attributes. - Added a demo website under scripts/examples/webgl.html that can be used to generate Camoufox config data
This commit is contained in:
parent
df755def7f
commit
02bc15161a
10 changed files with 880 additions and 58 deletions
2
Makefile
2
Makefile
|
|
@ -109,7 +109,7 @@ build:
|
|||
@if [ ! -f $(cf_source_dir)/_READY ]; then \
|
||||
make dir; \
|
||||
fi
|
||||
cd $(cf_source_dir) && ./mach build
|
||||
cd $(cf_source_dir) && ./mach build $(_ARGS)
|
||||
|
||||
edits:
|
||||
python ./scripts/developer.py $(version) $(release)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Written by daijro.
|
|||
#include "mozilla/glue/Debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <variant>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
|
|
@ -76,11 +77,9 @@ inline const nlohmann::json& GetJson() {
|
|||
}
|
||||
|
||||
inline bool HasKey(const std::string& key, nlohmann::json& data) {
|
||||
if (!data.contains(key)) {
|
||||
// printf_stderr("WARNING: Key not found: %s\n", key.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// printf_stderr("Property: %s\n", key.c_str());
|
||||
// printf_stderr("WARNING: Key not found: %s\n", key.c_str());
|
||||
return data.contains(key);
|
||||
}
|
||||
|
||||
inline std::optional<std::string> GetString(const std::string& key) {
|
||||
|
|
@ -163,8 +162,8 @@ inline std::optional<std::array<uint32_t, 4>> GetRect(
|
|||
const std::string& height) {
|
||||
// Make top and left default to 0
|
||||
std::array<std::optional<uint32_t>, 4> values = {
|
||||
GetUint32(left).value_or(0), GetUint32(top).value_or(0),
|
||||
GetUint32(width), GetUint32(height)};
|
||||
GetUint32(left).value_or(0), GetUint32(top).value_or(0), GetUint32(width),
|
||||
GetUint32(height)};
|
||||
|
||||
// If height or width is std::nullopt, return std::nullopt
|
||||
if (!values[2].has_value() || !values[3].has_value()) {
|
||||
|
|
@ -197,4 +196,88 @@ inline std::optional<std::array<int32_t, 4>> GetInt32Rect(
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Helpers for WebGL
|
||||
|
||||
inline std::optional<nlohmann::json> GetNested(const std::string& domain,
|
||||
std::string keyStr) {
|
||||
auto data = GetJson();
|
||||
if (!data.contains(domain)) return std::nullopt;
|
||||
|
||||
if (!data[domain].contains(keyStr)) return std::nullopt;
|
||||
|
||||
return data[domain][keyStr];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::optional<T> GetAttribute(const std::string attrib, bool isWebGL2) {
|
||||
auto value = MaskConfig::GetNested(
|
||||
isWebGL2 ? "webgl2:contextAttributes" : "webgl:contextAttributes",
|
||||
attrib);
|
||||
if (!value) return std::nullopt;
|
||||
return value.value().get<T>();
|
||||
}
|
||||
|
||||
inline std::optional<std::variant<int64_t, bool, double, std::string>> 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_number_integer()) return data.get<int64_t>();
|
||||
if (data.is_boolean()) return data.get<bool>();
|
||||
if (data.is_number_float()) return data.get<double>();
|
||||
if (data.is_string()) return data.get<std::string>();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T MParamGL(uint32_t pname, T defaultValue, bool isWebGL2) {
|
||||
if (auto value = MaskConfig::GetNested(
|
||||
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
|
||||
std::to_string(pname));
|
||||
value.has_value()) {
|
||||
return value.value().get<T>();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> MParamGLVector(uint32_t pname,
|
||||
std::vector<T> defaultValue,
|
||||
bool isWebGL2) {
|
||||
if (auto value = MaskConfig::GetNested(
|
||||
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
|
||||
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());
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
inline std::optional<std::array<int32_t, 3UL>> MShaderData(
|
||||
uint32_t shaderType, uint32_t precisionType, bool isWebGL2) {
|
||||
std::string valueName =
|
||||
std::to_string(shaderType) + "," + std::to_string(precisionType);
|
||||
if (auto value =
|
||||
MaskConfig::GetNested(isWebGL2 ? "webgl2:shaderPrecisionFormats"
|
||||
: "webgl:shaderPrecisionFormats",
|
||||
valueName)) {
|
||||
// Convert {rangeMin: int, rangeMax: int, precision: int} to array
|
||||
auto data = value.value();
|
||||
// Assert rangeMin, rangeMax, and precision are present
|
||||
if (!data.contains("rangeMin") || !data.contains("rangeMax") ||
|
||||
!data.contains("precision")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::array<int32_t, 3U>{data["rangeMin"].get<int32_t>(),
|
||||
data["rangeMax"].get<int32_t>(),
|
||||
data["precision"].get<int32_t>()};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace MaskConfig
|
||||
|
|
@ -463,52 +463,6 @@ index 3a90c93c01..91d673039b 100644
|
|||
+# DOM Mask
|
||||
+LOCAL_INCLUDES += ["/camoucfg"]
|
||||
\ No newline at end of file
|
||||
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
|
||||
index 4e43425ef9..26bbfdc868 100644
|
||||
--- a/dom/canvas/ClientWebGLContext.cpp
|
||||
+++ b/dom/canvas/ClientWebGLContext.cpp
|
||||
@@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ClientWebGLContext.h"
|
||||
+#include "MaskConfig.hpp"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
@@ -2399,6 +2400,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||
|
||||
switch (pname) {
|
||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
|
||||
+ if (auto value = MaskConfig::GetString("webGl:renderer")) {
|
||||
+ ret = Some(value.value());
|
||||
+ break;
|
||||
+ }
|
||||
ret = GetUnmaskedRenderer();
|
||||
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
|
||||
*ret = webgl::SanitizeRenderer(*ret);
|
||||
@@ -2406,6 +2411,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||
break;
|
||||
|
||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
|
||||
+ if (auto value = MaskConfig::GetString("webGl:vendor")) {
|
||||
+ ret = Some(value.value());
|
||||
+ break;
|
||||
+ }
|
||||
ret = GetUnmaskedVendor();
|
||||
break;
|
||||
|
||||
diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build
|
||||
index 3a533d36d1..41d74d9b3f 100644
|
||||
--- a/dom/canvas/moz.build
|
||||
+++ b/dom/canvas/moz.build
|
||||
@@ -221,3 +221,6 @@ if CONFIG["CC_TYPE"] == "gcc":
|
||||
|
||||
# Add libFuzzer configuration directives
|
||||
include("/tools/fuzzing/libfuzzer-config.mozbuild")
|
||||
+
|
||||
+# DOM Mask
|
||||
+LOCAL_INCLUDES += ["/camoucfg"]
|
||||
\ No newline at end of file
|
||||
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
|
||||
index b05e409a84..8e0f7ea657 100644
|
||||
--- a/dom/workers/WorkerNavigator.cpp
|
||||
|
|
|
|||
360
patches/webgl-spoofing.patch
Normal file
360
patches/webgl-spoofing.patch
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
|
||||
index db60868f65..afed6eeb7c 100644
|
||||
--- a/dom/canvas/ClientWebGLContext.cpp
|
||||
+++ b/dom/canvas/ClientWebGLContext.cpp
|
||||
@@ -4,6 +4,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ClientWebGLContext.h"
|
||||
+#include "MaskConfig.hpp"
|
||||
+#include <algorithm>
|
||||
|
||||
#include <bitset>
|
||||
|
||||
@@ -744,6 +746,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
|
||||
Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
|
||||
}
|
||||
|
||||
+bool ClientWebGLContext::MBoolVal(const std::string& key, bool defaultValue) {
|
||||
+ if (auto value = MaskConfig::GetAttribute<bool>(key, mIsWebGL2);
|
||||
+ value.has_value())
|
||||
+ return value.value();
|
||||
+ return defaultValue;
|
||||
+}
|
||||
+
|
||||
void ClientWebGLContext::GetContextAttributes(
|
||||
dom::Nullable<dom::WebGLContextAttributes>& retval) {
|
||||
retval.SetNull();
|
||||
@@ -754,14 +763,38 @@ void ClientWebGLContext::GetContextAttributes(
|
||||
|
||||
const auto& options = mNotLost->info.options;
|
||||
|
||||
- result.mAlpha.Construct(options.alpha);
|
||||
- result.mDepth = options.depth;
|
||||
- result.mStencil = options.stencil;
|
||||
- result.mAntialias.Construct(options.antialias);
|
||||
- result.mPremultipliedAlpha = options.premultipliedAlpha;
|
||||
- result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
|
||||
- result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
|
||||
- result.mPowerPreference = options.powerPreference;
|
||||
+ result.mAlpha.Construct(MBoolVal("alpha", options.alpha));
|
||||
+ result.mDepth = MBoolVal("depth", options.depth);
|
||||
+ result.mStencil = MBoolVal("stencil", options.stencil);
|
||||
+ result.mAntialias.Construct(MBoolVal("antialias", options.antialias));
|
||||
+ result.mPremultipliedAlpha = MBoolVal(
|
||||
+ "webgl:contextAttributes.premultipliedAlpha", options.premultipliedAlpha);
|
||||
+ result.mPreserveDrawingBuffer =
|
||||
+ MBoolVal("preserveDrawingBuffer", options.preserveDrawingBuffer);
|
||||
+ result.mFailIfMajorPerformanceCaveat = MBoolVal(
|
||||
+ "failIfMajorPerformanceCaveat", options.failIfMajorPerformanceCaveat);
|
||||
+ if (auto value =
|
||||
+ MaskConfig::GetAttribute<std::string>("powerPreference", mIsWebGL2);
|
||||
+ value.has_value()) {
|
||||
+ // Convert to enum
|
||||
+ switch (value.value()[0]) {
|
||||
+ case 'd':
|
||||
+ result.mPowerPreference = dom::WebGLPowerPreference::Default;
|
||||
+ break;
|
||||
+ case 'l':
|
||||
+ result.mPowerPreference = dom::WebGLPowerPreference::Low_power;
|
||||
+ break;
|
||||
+ case 'h':
|
||||
+ result.mPowerPreference = dom::WebGLPowerPreference::High_performance;
|
||||
+ break;
|
||||
+ default:
|
||||
+ // Invalid value
|
||||
+ result.mPowerPreference = options.powerPreference;
|
||||
+ break;
|
||||
+ }
|
||||
+ } else {
|
||||
+ result.mPowerPreference = options.powerPreference;
|
||||
+ }
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
@@ -979,18 +1012,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
|
||||
std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
|
||||
auto ret = std::unordered_map<GLenum, bool>{};
|
||||
|
||||
- ret[LOCAL_GL_BLEND] = false;
|
||||
- ret[LOCAL_GL_CULL_FACE] = false;
|
||||
- ret[LOCAL_GL_DEPTH_TEST] = false;
|
||||
- ret[LOCAL_GL_DITHER] = true;
|
||||
- ret[LOCAL_GL_POLYGON_OFFSET_FILL] = false;
|
||||
- ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = false;
|
||||
- ret[LOCAL_GL_SAMPLE_COVERAGE] = false;
|
||||
- ret[LOCAL_GL_SCISSOR_TEST] = false;
|
||||
- ret[LOCAL_GL_STENCIL_TEST] = false;
|
||||
+ ret[LOCAL_GL_BLEND] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_BLEND, false, webgl2);
|
||||
+ ret[LOCAL_GL_CULL_FACE] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_CULL_FACE, false, webgl2);
|
||||
+ ret[LOCAL_GL_DEPTH_TEST] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_DEPTH_TEST, false, webgl2);
|
||||
+ ret[LOCAL_GL_DITHER] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_DITHER, true, webgl2);
|
||||
+ ret[LOCAL_GL_POLYGON_OFFSET_FILL] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_POLYGON_OFFSET_FILL, false, webgl2);
|
||||
+ ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = MaskConfig::MParamGL<bool>(
|
||||
+ LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false, webgl2);
|
||||
+ ret[LOCAL_GL_SAMPLE_COVERAGE] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_SAMPLE_COVERAGE, false, webgl2);
|
||||
+ ret[LOCAL_GL_SCISSOR_TEST] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_SCISSOR_TEST, false, webgl2);
|
||||
+ ret[LOCAL_GL_STENCIL_TEST] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_STENCIL_TEST, false, webgl2);
|
||||
|
||||
if (webgl2) {
|
||||
- ret[LOCAL_GL_RASTERIZER_DISCARD] = false;
|
||||
+ ret[LOCAL_GL_RASTERIZER_DISCARD] =
|
||||
+ MaskConfig::MParamGL<bool>(LOCAL_GL_RASTERIZER_DISCARD, false, webgl2);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -2058,6 +2101,28 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||
const auto& state = State();
|
||||
|
||||
// -
|
||||
+ std::optional<std::variant<int64_t, bool, double, std::string>> data;
|
||||
+ data = MaskConfig::GLParam(pname, mIsWebGL2);
|
||||
+
|
||||
+ if (data.has_value()) {
|
||||
+ const auto& value = data.value();
|
||||
+ if (std::holds_alternative<int64_t>(value)) {
|
||||
+ retval.set(JS::NumberValue(double(std::get<int64_t>(value))));
|
||||
+ return;
|
||||
+ }
|
||||
+ if (std::holds_alternative<double>(value)) {
|
||||
+ retval.set(JS::NumberValue(std::get<double>(value)));
|
||||
+ return;
|
||||
+ }
|
||||
+ if (std::holds_alternative<bool>(value)) {
|
||||
+ retval.set(JS::BooleanValue(std::get<bool>(value)));
|
||||
+ return;
|
||||
+ }
|
||||
+ if (std::holds_alternative<std::string>(value)) {
|
||||
+ retval.set(StringValue(cx, std::get<std::string>(value), rv));
|
||||
+ 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,
|
||||
|
||||
// 2 floats
|
||||
case LOCAL_GL_DEPTH_RANGE:
|
||||
- retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
|
||||
+ retval.set(Create<dom::Float32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<float, 2UL>>(pname, state.mDepthRange,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
|
||||
- retval.set(
|
||||
- Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
|
||||
+ retval.set(Create<dom::Float32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<float, 2UL>>(
|
||||
+ pname, limits.pointSizeRange, mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
|
||||
- retval.set(
|
||||
- Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
|
||||
+ retval.set(Create<dom::Float32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<float, 2UL>>(
|
||||
+ pname, limits.lineWidthRange, mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
// 4 floats
|
||||
case LOCAL_GL_COLOR_CLEAR_VALUE:
|
||||
- retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
|
||||
+ retval.set(Create<dom::Float32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<float, 4UL>>(pname, state.mClearColor,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
case LOCAL_GL_BLEND_COLOR:
|
||||
- retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
|
||||
+ retval.set(Create<dom::Float32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<float, 4UL>>(pname, state.mBlendColor,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
// 2 ints
|
||||
case LOCAL_GL_MAX_VIEWPORT_DIMS: {
|
||||
auto maxViewportDim = BitwiseCast<int32_t>(limits.maxViewportDim);
|
||||
const auto dims = std::array<int32_t, 2>{maxViewportDim, maxViewportDim};
|
||||
- retval.set(Create<dom::Int32Array>(cx, this, dims, rv));
|
||||
+ retval.set(Create<dom::Int32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<int32_t, 2UL>>(pname, dims,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4 ints
|
||||
case LOCAL_GL_SCISSOR_BOX:
|
||||
- retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
|
||||
+ retval.set(Create<dom::Int32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<int32_t, 4UL>>(pname, state.mScissor,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
case LOCAL_GL_VIEWPORT:
|
||||
- retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
|
||||
+ retval.set(Create<dom::Int32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGL<std::array<int32_t, 4UL>>(pname, state.mViewport,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
|
||||
- // any
|
||||
case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
|
||||
- retval.set(Create<dom::Uint32Array>(cx, this,
|
||||
- state.mCompressedTextureFormats, rv));
|
||||
+ std::vector<uint32_t> compressedTextureUint32(
|
||||
+ state.mCompressedTextureFormats.begin(),
|
||||
+ state.mCompressedTextureFormats.end());
|
||||
+ retval.set(Create<dom::Uint32Array>(
|
||||
+ cx, this,
|
||||
+ MaskConfig::MParamGLVector<uint32_t>(pname, compressedTextureUint32,
|
||||
+ mIsWebGL2),
|
||||
+ rv));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2385,6 +2485,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||
|
||||
switch (pname) {
|
||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
|
||||
+ if (auto value = MaskConfig::GetString("webGl:renderer")) {
|
||||
+ ret = Some(value.value());
|
||||
+ break;
|
||||
+ }
|
||||
ret = GetUnmaskedRenderer();
|
||||
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
|
||||
*ret = webgl::SanitizeRenderer(*ret);
|
||||
@@ -2392,6 +2496,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||
break;
|
||||
|
||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
|
||||
+ if (auto value = MaskConfig::GetString("webGl:vendor")) {
|
||||
+ ret = Some(value.value());
|
||||
+ break;
|
||||
+ }
|
||||
ret = GetUnmaskedVendor();
|
||||
break;
|
||||
|
||||
@@ -2482,7 +2590,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);
|
||||
- const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
|
||||
+ const auto src = MaskConfig::MParamGL<std::array<bool, 4>>(
|
||||
+ pname, std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]},
|
||||
+ mIsWebGL2);
|
||||
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,
|
||||
const GLenum precisiontype) {
|
||||
if (IsContextLost()) return nullptr;
|
||||
const auto info = [&]() {
|
||||
+ // Check for spoofed value
|
||||
+ if (auto value =
|
||||
+ MaskConfig::MShaderData(shadertype, precisiontype, mIsWebGL2)) {
|
||||
+ const auto& format = value.value();
|
||||
+ return Some(webgl::ShaderPrecisionFormat{
|
||||
+ format[0], // rangeMin
|
||||
+ format[1], // rangeMax
|
||||
+ format[2] // precision
|
||||
+ });
|
||||
+ }
|
||||
+ // Check if block if not defined is on
|
||||
+ if (MaskConfig::GetBool(
|
||||
+ mIsWebGL2 ? "webgl2:shaderPrecisionFormats:blockIfNotDefined"
|
||||
+ : "webgl:shaderPrecisionFormats:blockIfNotDefined")) {
|
||||
+ Maybe<webgl::ShaderPrecisionFormat> ret;
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
const auto& inProcess = mNotLost->inProcess;
|
||||
if (inProcess) {
|
||||
return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
|
||||
@@ -5822,6 +5950,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
|
||||
return false;
|
||||
}
|
||||
|
||||
+ if (std::vector<std::string> maskValues =
|
||||
+ MaskConfig::GetStringList(mIsWebGL2 ? "webgl2:supportedExtensions"
|
||||
+ : "webgl:supportedExtensions");
|
||||
+ !maskValues.empty()) {
|
||||
+ if (std::find(maskValues.begin(), maskValues.end(),
|
||||
+ GetExtensionName(ext)) != maskValues.end()) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
const auto& limits = Limits();
|
||||
return limits.supportedExtensions[ext];
|
||||
}
|
||||
@@ -5833,6 +5972,17 @@ 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");
|
||||
+ !maskValues.empty()) {
|
||||
+ for (const auto& ext : maskValues) {
|
||||
+ retarr.AppendElement(NS_ConvertUTF8toUTF16(ext));
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
|
||||
if (!IsSupported(i, callerType)) continue;
|
||||
|
||||
diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h
|
||||
index 9e8b3c0f00..16e3fc7aa5 100644
|
||||
--- a/dom/canvas/ClientWebGLContext.h
|
||||
+++ b/dom/canvas/ClientWebGLContext.h
|
||||
@@ -1067,6 +1067,9 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
|
||||
|
||||
// -
|
||||
|
||||
+ // Helper to get booleans set in MaskConfig
|
||||
+ bool MBoolVal(const std::string& key, bool defaultValue);
|
||||
+
|
||||
void GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
|
||||
|
||||
private:
|
||||
diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build
|
||||
index 3a533d36d1..41d74d9b3f 100644
|
||||
--- a/dom/canvas/moz.build
|
||||
+++ b/dom/canvas/moz.build
|
||||
@@ -221,3 +221,6 @@ if CONFIG["CC_TYPE"] == "gcc":
|
||||
|
||||
# Add libFuzzer configuration directives
|
||||
include("/tools/fuzzing/libfuzzer-config.mozbuild")
|
||||
+
|
||||
+# DOM Mask
|
||||
+LOCAL_INCLUDES += ["/camoucfg"]
|
||||
\ No newline at end of file
|
||||
63
scripts/examples/buttonclick.html
Normal file
63
scripts/examples/buttonclick.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Random Button Clicker</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.button {
|
||||
position: absolute;
|
||||
padding: 10px 20px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
#cursor-highlight {
|
||||
position: fixed;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 0, 0, 0.5);
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const body = document.body;
|
||||
|
||||
function createButton() {
|
||||
const button = document.createElement("button");
|
||||
button.classList.add("button");
|
||||
button.textContent = "Click me!";
|
||||
|
||||
const maxX = window.innerWidth - 100;
|
||||
const maxY = window.innerHeight - 50;
|
||||
const randomX = Math.floor(Math.random() * maxX);
|
||||
const randomY = Math.floor(Math.random() * maxY);
|
||||
|
||||
button.style.left = `${randomX}px`;
|
||||
button.style.top = `${randomY}px`;
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
button.remove();
|
||||
createButton();
|
||||
});
|
||||
|
||||
body.appendChild(button);
|
||||
}
|
||||
|
||||
// Create the first button
|
||||
createButton();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
scripts/examples/serve.sh
Normal file
1
scripts/examples/serve.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
python -m http.server
|
||||
345
scripts/examples/webgl.html
Normal file
345
scripts/examples/webgl.html
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebGL Fingerprinting - Camoufox</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
h1 {
|
||||
color: #569cd6;
|
||||
margin-top: 0;
|
||||
}
|
||||
#output {
|
||||
flex: 1;
|
||||
background-color: #252526;
|
||||
border: 1px solid #3e3e42;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.btn {
|
||||
background-color: #0e639c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 3px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #1177bb;
|
||||
}
|
||||
.string {
|
||||
color: #ce9178;
|
||||
}
|
||||
.number {
|
||||
color: #b5cea8;
|
||||
}
|
||||
.boolean {
|
||||
color: #569cd6;
|
||||
}
|
||||
.null {
|
||||
color: #569cd6;
|
||||
}
|
||||
.key {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
#hash {
|
||||
margin-bottom: 5px;
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
color: #569cd6;
|
||||
}
|
||||
#warning {
|
||||
background-color: #ff9800;
|
||||
color: #000;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
position: relative;
|
||||
transition: opacity 0.3s ease-out, max-height 0.3s ease-out, padding 0.3s ease-out;
|
||||
opacity: 1;
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
}
|
||||
#warning.hide {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#close-warning {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<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">×</button></div>
|
||||
<div id="hash"></div>
|
||||
<pre id="output"></pre>
|
||||
<div class="button-container">
|
||||
<button id="copy-btn" class="btn">Copy data</button>
|
||||
<button id="copy-minified-btn" class="btn">Copy data (minified)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const dumpWebGLCore = async (
|
||||
webglContextId,
|
||||
experimentalWebglContextId
|
||||
) => {
|
||||
function getWebGLContext() {
|
||||
const canvas = new OffscreenCanvas(256, 256);
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result =
|
||||
canvas.getContext(webglContextId, {
|
||||
preserveDrawingBuffer: false,
|
||||
}) ||
|
||||
canvas.getContext(experimentalWebglContextId, {
|
||||
preserveDrawingBuffer: false,
|
||||
});
|
||||
} catch (ex) {}
|
||||
|
||||
result || (result = null);
|
||||
return result;
|
||||
}
|
||||
|
||||
const webglContext = getWebGLContext();
|
||||
|
||||
if (!webglContext) {
|
||||
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,
|
||||
];
|
||||
|
||||
let rendererInfo = null;
|
||||
const debug_ext = webglContext.getExtension(
|
||||
"WEBGL_debug_renderer_info"
|
||||
);
|
||||
if (debug_ext) {
|
||||
rendererInfo = {
|
||||
webglVendor: webglContext.getParameter(
|
||||
debug_ext.UNMASKED_VENDOR_WEBGL
|
||||
),
|
||||
webglRenderer: webglContext.getParameter(
|
||||
debug_ext.UNMASKED_RENDERER_WEBGL
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const result = {
|
||||
[`${webglContextId}:renderer`]: rendererInfo
|
||||
? rendererInfo.webglRenderer
|
||||
: "Not available",
|
||||
[`${webglContextId}:vendor`]: rendererInfo
|
||||
? rendererInfo.webglVendor
|
||||
: "Not available",
|
||||
[`${webglContextId}:contextAttributes`]:
|
||||
webglContext.getContextAttributes(),
|
||||
[`${webglContextId}:supportedExtensions`]:
|
||||
webglContext.getSupportedExtensions() || [],
|
||||
[`${webglContextId}:parameters`]: {},
|
||||
[`${webglContextId}:shaderPrecisionFormats`]: {},
|
||||
};
|
||||
for (const glEnum of glEnums) {
|
||||
try {
|
||||
const parmValue = webglContext.getParameter(glEnum);
|
||||
if (
|
||||
Array.isArray(parmValue) ||
|
||||
[Float32Array, Int32Array, Uint32Array].some(
|
||||
(type) => parmValue instanceof type
|
||||
)
|
||||
) {
|
||||
const arrayValue = Array.from(parmValue);
|
||||
result[`${webglContextId}:parameters`][glEnum] =
|
||||
arrayValue.length > 0 ? arrayValue : null;
|
||||
} else {
|
||||
result[`${webglContextId}:parameters`][glEnum] = parmValue;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
const shaderTypes = [
|
||||
webglContext.VERTEX_SHADER,
|
||||
webglContext.FRAGMENT_SHADER,
|
||||
];
|
||||
const precisionTypes = [
|
||||
webglContext.LOW_FLOAT,
|
||||
webglContext.MEDIUM_FLOAT,
|
||||
webglContext.HIGH_FLOAT,
|
||||
webglContext.LOW_INT,
|
||||
webglContext.MEDIUM_INT,
|
||||
webglContext.HIGH_INT,
|
||||
];
|
||||
|
||||
for (let shaderType of shaderTypes) {
|
||||
for (let precisionType of precisionTypes) {
|
||||
const r = webglContext.getShaderPrecisionFormat(
|
||||
shaderType,
|
||||
precisionType
|
||||
);
|
||||
const key = `${shaderType},${precisionType}`;
|
||||
result[`${webglContextId}:shaderPrecisionFormats`][key] = {
|
||||
rangeMin: r.rangeMin,
|
||||
rangeMax: r.rangeMax,
|
||||
precision: r.precision,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function syntaxHighlight(json) {
|
||||
json = json
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
return json.replace(
|
||||
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
||||
function (match) {
|
||||
var cls = "number";
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
cls = "key";
|
||||
} else {
|
||||
cls = "string";
|
||||
}
|
||||
} else if (/true|false/.test(match)) {
|
||||
cls = "boolean";
|
||||
} else if (/null/.test(match)) {
|
||||
cls = "null";
|
||||
}
|
||||
return '<span class="' + cls + '">' + match + "</span>";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function sha256(message) {
|
||||
const msgBuffer = new TextEncoder().encode(message);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
return hashHex;
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
async () => {
|
||||
// Check if the user agent is Firefox
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
if (!isFirefox) {
|
||||
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');
|
||||
});
|
||||
|
||||
const [webglDetail, webgl2Detail] = await Promise.all([
|
||||
dumpWebGLCore("webgl", "experimental-webgl"),
|
||||
dumpWebGLCore("webgl2", "experimental-webgl2"),
|
||||
]);
|
||||
|
||||
const combinedInfo = { ...webglDetail, ...webgl2Detail };
|
||||
const jsonString = JSON.stringify(combinedInfo, null, 2);
|
||||
const minifiedJson = JSON.stringify(combinedInfo);
|
||||
const hashValue = await sha256(minifiedJson);
|
||||
|
||||
document.getElementById("hash").textContent = `SHA256: ${hashValue}`;
|
||||
document.getElementById("output").innerHTML =
|
||||
syntaxHighlight(jsonString);
|
||||
|
||||
document.getElementById("copy-btn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(jsonString).then(() => {
|
||||
alert("Copied formatted JSON to clipboard!");
|
||||
});
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("copy-minified-btn")
|
||||
.addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(minifiedJson).then(() => {
|
||||
alert("Copied minified JSON to clipboard!");
|
||||
});
|
||||
});
|
||||
},
|
||||
false
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -194,6 +194,9 @@ pref("layout.css.always_underline_links", true);
|
|||
|
||||
// No WebGL
|
||||
pref("webgl.disabled", true);
|
||||
// Force software rendering
|
||||
pref("webgl.forbid-hardware", true);
|
||||
pref("webgl.forbid-software", false);
|
||||
|
||||
// Debloat pt.2 (from Librewolf)
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@
|
|||
{ "property": "webrtc:ipv4", "type": "str" },
|
||||
{ "property": "webrtc:ipv6", "type": "str" },
|
||||
{ "property": "pdfViewerEnabled", "type": "bool" },
|
||||
{ "property": "webGl:renderer", "type": "str" },
|
||||
{ "property": "webGl:vendor", "type": "str" },
|
||||
{ "property": "battery:charging", "type": "bool" },
|
||||
{ "property": "battery:chargingTime", "type": "double" },
|
||||
{ "property": "battery:dischargingTime", "type": "double" },
|
||||
|
|
@ -64,6 +62,21 @@
|
|||
{ "property": "locale:script", "type": "str" },
|
||||
{ "property": "humanize", "type": "bool" },
|
||||
{ "property": "humanize:maxTime", "type": "double" },
|
||||
{ "property": "humanize:minTime", "type": "double" },
|
||||
{ "property": "showcursor", "type": "bool" },
|
||||
{ "property": "AudioContext:sampleRate", "type": "uint" },
|
||||
{ "property": "AudioContext:outputLatency", "type": "double" },
|
||||
{ "property": "AudioContext:maxChannelCount", "type": "uint" },
|
||||
{ "property": "webGl:renderer", "type": "str" },
|
||||
{ "property": "webGl:vendor", "type": "str" },
|
||||
{ "property": "webgl:supportedExtensions", "type": "array" },
|
||||
{ "property": "webgl:parameters", "type": "dict" },
|
||||
{ "property": "webgl2:parameters", "type": "dict" },
|
||||
{ "property": "webgl:shaderPrecisionFormats", "type": "dict" },
|
||||
{ "property": "webgl:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" },
|
||||
{ "property": "webgl2:shaderPrecisionFormats", "type": "dict" },
|
||||
{ "property": "webgl2:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" },
|
||||
{ "property": "webgl:contextAttributes", "type": "dict" },
|
||||
{ "property": "webgl2:contextAttributes", "type": "dict" },
|
||||
{ "property": "debug", "type": "bool" }
|
||||
]
|
||||
]
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
version=130.0.1
|
||||
release=beta.11
|
||||
release=beta.12
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue