Bug 1217290 - Refactor WebGL max & min attribute constants for WebGL fingerprinting draft
authorChung-Sheng Fu <cfu@mozilla.com>
Thu, 27 Jul 2017 15:49:46 +0800
changeset 651902 7845f0115fc8ea3d1f7dd72d509d56767ab55819
parent 651901 f4ac8ceaa5071a5226144744531462c97eb279b6
child 651903 4692d1258a6acffc7044d648cfbc319bf5e2b1b7
push id75856
push userbmo:cfu@mozilla.com
push dateThu, 24 Aug 2017 06:35:30 +0000
bugs1217290
milestone57.0a1
Bug 1217290 - Refactor WebGL max & min attribute constants for WebGL fingerprinting MozReview-Commit-ID: 5fxOdV8euJ0
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextState.cpp
dom/canvas/WebGLContextValidate.cpp
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -50,40 +50,16 @@
 #include "nsWrapperCache.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 
 class nsIDocShell;
 
 /*
- * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25
- *   https://bugzilla.mozilla.org/show_bug.cgi?id=686732
- *
- * Exceptions: some of the following values are set to higher values than in the spec because
- * the values in the spec are ridiculously low. They are explicitly marked below
- */
-#define MINVALUE_GL_MAX_TEXTURE_SIZE                  1024  // Different from the spec, which sets it to 64 on page 162
-#define MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE         512   // Different from the spec, which sets it to 16 on page 162
-#define MINVALUE_GL_MAX_VERTEX_ATTRIBS                8     // Page 164
-#define MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS      16    // Page 164
-#define MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS        128   // Page 164
-#define MINVALUE_GL_MAX_VARYING_VECTORS               8     // Page 164
-#define MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS           8     // Page 164
-#define MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS    0     // Page 164
-#define MINVALUE_GL_MAX_RENDERBUFFER_SIZE             1024  // Different from the spec, which sets it to 1 on page 164
-#define MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS  8     // Page 164
-
-/*
- * Minimum value constants define in 6.2 State Tables of OpenGL ES - 3.0.4
- */
-#define MINVALUE_GL_MAX_3D_TEXTURE_SIZE             256
-#define MINVALUE_GL_MAX_ARRAY_TEXTURE_LAYERS        256
-
-/*
  * WebGL-only GLenums
  */
 #define LOCAL_GL_BROWSER_DEFAULT_WEBGL                       0x9244
 #define LOCAL_GL_CONTEXT_LOST_WEBGL                          0x9242
 #define LOCAL_GL_MAX_CLIENT_WAIT_TIMEOUT_WEBGL               0x9247
 #define LOCAL_GL_UNPACK_COLORSPACE_CONVERSION_WEBGL          0x9243
 #define LOCAL_GL_UNPACK_FLIP_Y_WEBGL                         0x9240
 #define LOCAL_GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL              0x9241
@@ -1506,16 +1482,19 @@ public:
     GLuint MaxVertexAttribs() const {
         return mGLMaxVertexAttribs;
     }
 
     GLuint GLMaxTextureUnits() const {
         return mGLMaxTextureUnits;
     }
 
+    float mGLAliasedLineWidthRange[2];
+    float mGLAliasedPointSizeRange[2];
+
     bool IsFormatValidForFB(TexInternalFormat format) const;
 
 protected:
     // Represents current status of the context with respect to context loss.
     // That is, whether the context is lost, and what part of the context loss
     // process we currently are at.
     // This is used to support the WebGL spec's asyncronous nature in handling
     // context loss.
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -376,21 +376,17 @@ WebGLContext::GetParameter(JSContext* cx
             return JS::Int32Value(refValue & stencilMask);
         }
 
         case LOCAL_GL_STENCIL_CLEAR_VALUE:
         case LOCAL_GL_UNPACK_ALIGNMENT:
         case LOCAL_GL_PACK_ALIGNMENT:
         case LOCAL_GL_SUBPIXEL_BITS:
         case LOCAL_GL_SAMPLE_BUFFERS:
-        case LOCAL_GL_SAMPLES:
-        case LOCAL_GL_MAX_VERTEX_ATTRIBS:
-        case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS: {
+        case LOCAL_GL_SAMPLES: {
             GLint i = 0;
             gl->fGetIntegerv(pname, &i);
             return JS::Int32Value(i);
         }
 
         case LOCAL_GL_RED_BITS:
         case LOCAL_GL_GREEN_BITS:
         case LOCAL_GL_BLUE_BITS:
@@ -409,16 +405,28 @@ WebGLContext::GetParameter(JSContext* cx
             return JS::Int32Value(mGLMaxTextureSize);
 
         case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
             return JS::Int32Value(mGLMaxCubeMapTextureSize);
 
         case LOCAL_GL_MAX_RENDERBUFFER_SIZE:
             return JS::Int32Value(mGLMaxRenderbufferSize);
 
+        case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxVertexTextureImageUnits);
+
+        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxFragmentTextureImageUnits);
+
+        case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxCombinedTextureImageUnits);
+
+        case LOCAL_GL_MAX_VERTEX_ATTRIBS:
+            return JS::Int32Value(mGLMaxVertexAttribs);
+
         case LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS:
             return JS::Int32Value(mGLMaxVertexUniformVectors);
 
         case LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS:
             return JS::Int32Value(mGLMaxFragmentUniformVectors);
 
         case LOCAL_GL_MAX_VARYING_VECTORS:
             return JS::Int32Value(mGLMaxVaryingVectors);
@@ -489,25 +497,31 @@ WebGLContext::GetParameter(JSContext* cx
 
         ////////////////////////////////
         // Complex values
 
         // 2 floats
         case LOCAL_GL_DEPTH_RANGE:
         case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
         case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE: {
-            GLenum driverPName = pname;
-            if (gl->IsCoreProfile() &&
-                driverPName == LOCAL_GL_ALIASED_POINT_SIZE_RANGE)
-            {
-                driverPName = LOCAL_GL_POINT_SIZE_RANGE;
+            GLfloat fv[2] = { 0 };
+            switch (pname) {
+            case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
+                fv[0] = mGLAliasedPointSizeRange[0];
+                fv[1] = mGLAliasedPointSizeRange[1];
+                break;
+            case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
+                fv[0] = mGLAliasedLineWidthRange[0];
+                fv[1] = mGLAliasedLineWidthRange[1];
+                break;
+            // case LOCAL_GL_DEPTH_RANGE:
+            default:
+                gl->fGetFloatv(pname, fv);
+                break;
             }
-
-            GLfloat fv[2] = { 0 };
-            gl->fGetFloatv(driverPName, fv);
             JSObject* obj = dom::Float32Array::Create(cx, this, 2, fv);
             if (!obj) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         // 4 floats
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -29,16 +29,76 @@
 #include "WebGLValidateStrings.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 #if defined(MOZ_WIDGET_COCOA)
 #include "nsCocoaFeatures.h"
 #endif
 
+////////////////////
+// Minimum value constants defined in GLES 2.0.25 $6.2 "State Tables":
+const uint32_t kMinMaxVertexAttribs          =   8; // Page 164
+const uint32_t kMinMaxVertexUniformVectors   = 128; // Page 164
+const uint32_t kMinMaxFragmentUniformVectors =  16; // Page 164
+const uint32_t kMinMaxVaryingVectors         =   8; // Page 164
+
+const uint32_t kMinMaxVertexTextureImageUnits   = 0; // Page 164
+const uint32_t kMinMaxFragmentTextureImageUnits = 8; // Page 164
+const uint32_t kMinMaxCombinedTextureImageUnits = 8; // Page 164
+
+const uint32_t kMinMaxColorAttachments = 4;
+const uint32_t kMinMaxDrawBuffers      = 4;
+
+// These few deviate from the spec: (The minimum values in the spec are ridiculously low)
+const uint32_t kMinMaxTextureSize        = 1024; // ES2 spec says `64` (p162)
+const uint32_t kMinMaxCubeMapTextureSize =  512; // ES2 spec says `16` (p162)
+const uint32_t kMinMaxRenderbufferSize   = 1024; // ES2 spec says `1` (p164)
+
+// Minimum value constants defined in GLES 3.0.4 $6.2 "State Tables":
+const uint32_t kMinMax3DTextureSize      = 256;
+const uint32_t kMinMaxArrayTextureLayers = 256;
+
+////////////////////
+// "Common" but usable values to avoid WebGL fingerprinting:
+const uint32_t kCommonMaxTextureSize        = 2048;
+const uint32_t kCommonMaxCubeMapTextureSize = 2048;
+const uint32_t kCommonMaxRenderbufferSize   = 2048;
+
+const uint32_t kCommonMaxVertexTextureImageUnits   =  8;
+const uint32_t kCommonMaxFragmentTextureImageUnits =  8;
+const uint32_t kCommonMaxCombinedTextureImageUnits = 16;
+
+const uint32_t kCommonMaxVertexAttribs          =  16;
+const uint32_t kCommonMaxVertexUniformVectors   = 256;
+const uint32_t kCommonMaxFragmentUniformVectors = 224;
+const uint32_t kCommonMaxVaryingVectors         =   8;
+
+const uint32_t kCommonMaxViewportDims = 4096;
+
+// The following ranges came from a 2013 Moto E and an old macbook.
+const float kCommonAliasedPointSizeRangeMin =  1;
+const float kCommonAliasedPointSizeRangeMax = 63;
+const float kCommonAliasedLineWidthRangeMin =  1;
+const float kCommonAliasedLineWidthRangeMax =  5;
+
+template<class T>
+static bool
+RestrictCap(T* const cap, const T restrictedVal)
+{
+    if (*cap < restrictedVal) {
+        return false; // already too low!
+    }
+
+    *cap = restrictedVal;
+    return true;
+}
+
+////////////////////
+
 namespace mozilla {
 
 bool
 WebGLContext::ValidateBlendEquationEnum(GLenum mode, const char* info)
 {
     switch (mode) {
     case LOCAL_GL_FUNC_ADD:
     case LOCAL_GL_FUNC_SUBTRACT:
@@ -449,16 +509,17 @@ WebGLContext::InitAndValidateGL(FailureR
         *out_failReason = { "FEATURE_FAILURE_WEBGL_V_ATRB", reason };
         return false;
     }
 
     // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware,
     // even though the hardware supports much more.  The
     // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value.
     gl->GetUIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);
+    mGLMaxCombinedTextureImageUnits = mGLMaxTextureUnits;
 
     if (mGLMaxTextureUnits < 8) {
         const nsPrintfCString reason("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %u is < 8!",
                                      mGLMaxTextureUnits);
         *out_failReason = { "FEATURE_FAILURE_WEBGL_T_UNIT", reason };
         return false;
     }
 
@@ -527,16 +588,86 @@ WebGLContext::InitAndValidateGL(FailureR
                                             maxFragmentInputComponents) / 4;
         } else {
             mGLMaxVaryingVectors = 16;
             // 16 = 64/4, and 64 is the min value for
             // maxVertexOutputComponents in the OpenGL 3.2 spec.
         }
     }
 
+    ////////////////
+
+    gl->fGetFloatv(LOCAL_GL_ALIASED_LINE_WIDTH_RANGE, mGLAliasedLineWidthRange);
+
+    const GLenum driverPName = gl->IsCoreProfile() ? LOCAL_GL_POINT_SIZE_RANGE
+                                                   : LOCAL_GL_ALIASED_POINT_SIZE_RANGE;
+    gl->fGetFloatv(driverPName, mGLAliasedPointSizeRange);
+
+    ////////////////
+
+    if (gfxPrefs::WebGLMinCapabilityMode()) {
+        bool ok = true;
+
+        ok &= RestrictCap(&mGLMaxVertexTextureImageUnits  , kMinMaxVertexTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxFragmentTextureImageUnits, kMinMaxFragmentTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxCombinedTextureImageUnits, kMinMaxCombinedTextureImageUnits);
+
+        ok &= RestrictCap(&mGLMaxVertexAttribs         , kMinMaxVertexAttribs);
+        ok &= RestrictCap(&mGLMaxVertexUniformVectors  , kMinMaxVertexUniformVectors);
+        ok &= RestrictCap(&mGLMaxFragmentUniformVectors, kMinMaxFragmentUniformVectors);
+        ok &= RestrictCap(&mGLMaxVaryingVectors        , kMinMaxVaryingVectors);
+
+        ok &= RestrictCap(&mGLMaxColorAttachments, kMinMaxColorAttachments);
+        ok &= RestrictCap(&mGLMaxDrawBuffers     , kMinMaxDrawBuffers);
+
+        ok &= RestrictCap(&mGLMaxTextureSize       , kMinMaxTextureSize);
+        ok &= RestrictCap(&mGLMaxCubeMapTextureSize, kMinMaxCubeMapTextureSize);
+        ok &= RestrictCap(&mGLMax3DTextureSize     , kMinMax3DTextureSize);
+
+        ok &= RestrictCap(&mGLMaxArrayTextureLayers, kMinMaxArrayTextureLayers);
+        ok &= RestrictCap(&mGLMaxRenderbufferSize  , kMinMaxRenderbufferSize);
+
+        if (!ok) {
+            GenerateWarning("Unable to restrict WebGL limits to minimums.");
+            return false;
+        }
+
+        mDisableFragHighP = true;
+    } else if (nsContentUtils::ShouldResistFingerprinting()) {
+        bool ok = true;
+
+        ok &= RestrictCap(&mGLMaxTextureSize       , kCommonMaxTextureSize);
+        ok &= RestrictCap(&mGLMaxCubeMapTextureSize, kCommonMaxCubeMapTextureSize);
+        ok &= RestrictCap(&mGLMaxRenderbufferSize  , kCommonMaxRenderbufferSize);
+
+        ok &= RestrictCap(&mGLMaxVertexTextureImageUnits  , kCommonMaxVertexTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxFragmentTextureImageUnits, kCommonMaxFragmentTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxCombinedTextureImageUnits, kCommonMaxCombinedTextureImageUnits);
+
+        ok &= RestrictCap(&mGLMaxVertexAttribs         , kCommonMaxVertexAttribs);
+        ok &= RestrictCap(&mGLMaxVertexUniformVectors  , kCommonMaxVertexUniformVectors);
+        ok &= RestrictCap(&mGLMaxFragmentUniformVectors, kCommonMaxFragmentUniformVectors);
+        ok &= RestrictCap(&mGLMaxVaryingVectors        , kCommonMaxVaryingVectors);
+
+        ok &= RestrictCap(&mGLAliasedLineWidthRange[0], kCommonAliasedLineWidthRangeMin);
+        ok &= RestrictCap(&mGLAliasedLineWidthRange[1], kCommonAliasedLineWidthRangeMax);
+        ok &= RestrictCap(&mGLAliasedPointSizeRange[0], kCommonAliasedPointSizeRangeMin);
+        ok &= RestrictCap(&mGLAliasedPointSizeRange[1], kCommonAliasedPointSizeRangeMax);
+
+        ok &= RestrictCap(&mGLMaxViewportDims[0], kCommonMaxViewportDims);
+        ok &= RestrictCap(&mGLMaxViewportDims[1], kCommonMaxViewportDims);
+
+        if (!ok) {
+            GenerateWarning("Unable to restrict WebGL limits in order to resist fingerprinting");
+            return false;
+        }
+    }
+
+    ////////////////
+
     if (gl->IsCompatibilityProfile()) {
         gl->fEnable(LOCAL_GL_POINT_SPRITE);
     }
 
     if (!gl->IsGLES()) {
         gl->fEnable(LOCAL_GL_PROGRAM_POINT_SIZE);
     }