Firefox · DOM
CVE-2026-92053
Logic Error in DOM
Overview
Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/canvas/WebGLProgram.cpp |
modified | |
fordom/canvas/WebGLProgram.cpp |
modified | |
fordom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html |
modified | |
ifdom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html |
modified |
Files Changed
dom/canvas/WebGLProgram.cppdom/canvas/test/webgl-mochitest/mochitest.tomldom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html
Patch
diff --git a/dom/canvas/WebGLProgram.cpp b/dom/canvas/WebGLProgram.cpp
index 05f549dc7d5..d80d3cb85e9 100644
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -609,6 +609,8 @@ const char* webgl::ToString(const webgl::UniformBaseType x) {
MOZ_CRASH("pacify gcc6 warning");
}
+static uint8_t NumUsedLocationsByElemType(GLenum elemType);
+
const webgl::CachedDrawFetchLimits*
webgl::LinkedProgramInfo::GetDrawFetchLimits() const {
const auto& webgl = prog->mContext;
@@ -643,46 +645,50 @@ webgl::LinkedProgramInfo::GetDrawFetchLimits() const {
if (loc == -1) continue;
hasActiveAttrib |= true;
- const auto& binding = vao->AttribBinding(loc);
- const auto& buffer = binding.buffer;
- const auto& layout = binding.layout;
- hasActiveDivisor0 |= (layout.divisor == 0);
-
- webgl::AttribBaseType attribDataBaseType;
- if (layout.isArray) {
- MOZ_ASSERT(buffer);
- fetchLimits.usedBuffers.push_back(
- {buffer.get(), static_cast<uint32_t>(loc)});
-
- attribDataBaseType = layout.baseType;
-
- const auto availBytes = buffer->ByteLength();
- const auto availElems = AvailGroups(availBytes, layout.byteOffset,
- layout.byteSize, layout.byteStride);
- if (layout.divisor) {
- const auto availInstances =
- CheckedInt<uint64_t>(availElems) * layout.divisor;
- if (availInstances.isValid()) {
- fetchLimits.maxInstances =
- std::min(fetchLimits.maxInstances, availInstances.value());
- } // If not valid, it overflowed too large, so we're super safe.
+ const auto numUsedLocs = NumUsedLocationsByElemType(progAttrib.elemType);
+ for (uint8_t locOffset = 0; locOffset < numUsedLocs; locOffset++) {
+ const auto usedLoc = static_cast<uint32_t>(loc) + locOffset;
+
+ const auto& binding = vao->AttribBinding(usedLoc);
+ const auto& buffer = binding.buffer;
+ const auto& layout = binding.layout;
+ hasActiveDivisor0 |= (layout.divisor == 0);
+
+ webgl::AttribBaseType attribDataBaseType;
+ if (layout.isArray) {
+ MOZ_ASSERT(buffer);
+ fetchLimits.usedBuffers.push_back({buffer.get(), usedLoc});
+
+ attribDataBaseType = layout.baseType;
+
+ const auto availBytes = buffer->ByteLength();
+ const auto availElems = AvailGroups(availBytes, layout.byteOffset,
+ layout.byteSize, layout.byteStride);
+ if (layout.divisor) {
+ const auto availInstances =
+ CheckedInt<uint64_t>(availElems) * layout.divisor;
+ if (availInstances.isValid()) {
+ fetchLimits.maxInstances =
+ std::min(fetchLimits.maxInstances, availInstances.value());
+ } // If not valid, it overflowed too large, so we're super safe.
+ } else {
+ fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems);
+ }
} else {
- fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems);
+ attribDataBaseType = webgl->mGenericVertexAttribTypes[usedLoc];
}
- } else {
- attribDataBaseType = webgl->mGenericVertexAttribTypes[loc];
- }
- const auto& progBaseType = progAttrib.baseType;
- if ((attribDataBaseType != progBaseType) &&
- (progBaseType != webgl::AttribBaseType::Boolean)) {
- const auto& dataType = ToString(attribDataBaseType);
- const auto& progType = ToString(progBaseType);
- webgl->ErrorInvalidOperation(
- "Vertex attrib %u requires data of type %s,"
- " but is being supplied with type %s.",
- loc, progType, dataType);
- return nullptr;
+ const auto& progBaseType = progAttrib.baseType;
+ if ((attribDataBaseType != progBaseType) &&
+ (progBaseType != webgl::AttribBaseType::Boolean)) {
+ const auto& dataType = ToString(attribDataBaseType);
+ const auto& progType = ToString(progBaseType);
+ webgl->ErrorInvalidOperation(
+ "Vertex attrib %u requires data of type %s,"
+ " but is being supplied with type %s.",
+ usedLoc, progType, dataType);
+ return nullptr;
+ }
}
}
diff --git a/dom/canvas/test/webgl-mochitest/mochitest.toml b/dom/canvas/test/webgl-mochitest/mochitest.toml
index 3f775a34f0b..9a6d1f6fb5c 100644
--- a/dom/canvas/test/webgl-mochitest/mochitest.toml
+++ b/dom/canvas/test/webgl-mochitest/mochitest.toml
@@ -170,6 +170,8 @@ support-files = ["../captureStream_common.js"]
["test_draw_fakevert_large_offset.html"]
+["test_draw_matrix_attrib_fetch_limits.html"]
+
["test_fb_param.html"]
["test_fb_param_crash.html"]
diff --git a/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html b/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html
new file mode 100644
index 00000000000..db55898ce04
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<script src='/tests/SimpleTest/SimpleTest.js'></script>
+</head>
+<body>
+<pre id=e_out></pre>
+<script>
+ok = window.ok || function(e, s) {
+ e_out.textContent += `\n${!e ? 'FAIL' : 'pass'}: ${s}`;
+};
+
+// Bug 2061503
+function run(version, vsSrc, attribName, numLocs, comps, opts = {}) {
+ const gl = document.createElement("canvas").getContext(version);
+ const fsSrc = version == "webgl2"
+ ? `#version 300 es
+ precision mediump float;
+ out vec4 o;
+ void main() { o = vec4(0.0); }`
+ : `void main() { gl_FragColor = vec4(0.0); }`;
+
+ const vs = gl.createShader(gl.VERTEX_SHADER);
+ gl.shaderSource(vs, vsSrc);
+ gl.compileShader(vs);
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
+ gl.shaderSource(fs, fsSrc);
+ gl.compileShader(fs);
+
+ const prog = gl.createProgram();
+ gl.attachShader(prog, vs);
+ gl.attachShader(prog, fs);
+ gl.bindAttribLocation(prog, 0, attribName);
+ gl.linkProgram(prog);
+ gl.useProgram(prog);
+
+ const N = 4096;
+ const vertBytes = comps * 4;
+
+ const big = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, big);
+ gl.bufferData(gl.ARRAY_BUFFER, N * vertBytes, gl.STATIC_DRAW);
+
+ const tiny = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, tiny);
+ gl.bufferData(gl.ARRAY_BUFFER, vertBytes, gl.STATIC_DRAW);
+
+ for (let loc = 0; loc < numLocs; loc++) {
+ const colBig = opts.allBig ||
+ (loc == 0 ? !opts.firstColSmall : false);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colBig ? big : tiny);
+ gl.enableVertexAttribArray(loc);
+ gl.vertexAttribPointer(loc, comps, gl.FLOAT, false, vertBytes, 0);
+ if (opts.instanced) {
+ gl.vertexAttribDivisor(loc, 1);
+ }
+ }
+
+ // Ensure no pending errors.
+ ok(gl.getError() == gl.NO_ERROR, "no error during setup");
+
+ if (opts.instanced) {
+ gl.drawArraysInstanced(gl.POINTS, 0, 1, N);
+ } else {
+ gl.drawArrays(gl.POINTS, 0, N);
+ }
+ return gl.getError();
+}
+
+const INVALID_OPERATION =
+ document.createElement("canvas").getContext("webgl").INVALID_OPERATION;
+
+function check(desc, expectError, version, ...args) {
+ const err = run(version, ...args);
+ const errored = err == INVALID_OPERATION;
+ ok(errored == expectError,
+ `${desc}: err:0x${err.toString(16)} ` +
+ `(expected ${expectError ? "INVALID_OPERATION" : "no error"})`);
+}
+
+const WEBGL1 = {
+ vec4: `attribute vec4 a; void main() { gl_Position = a; gl_PointSize = 1.0; }`,
+ mat2: `attribute mat2 m;
+ void main() { gl_Position = vec4(m[0], m[1]); gl_PointSize = 1.0; }`,
+ mat3: `attribute mat3 m;
+ void main() { gl_Position = vec4(m[0] + m[1] + m[2], 1.0);
+ gl_PointSize = 1.0; }`,
+ mat4: `attribute mat4 m;
+ void main() { gl_Position = m[0] + m[1] + m[2] + m[3];
+ gl_PointSize = 1.0; }`,
+};
+
+check("vec4 over-fetch", true, "webgl", WEBGL1.vec4, "a", 1, 4, {firstColSmall: true});
+check("mat2 later-column over-fetch", true, "webgl", WEBGL1.mat2, "m", 2, 4);
+check("mat3 later-column over-fetch", true, "webgl", WEBGL1.mat3, "m", 3, 4);
+check("mat4 later-column over-fetch", true, "webgl", WEBGL1.mat4, "m", 4, 4);
+
+check("mat4 fully backed", false, "webgl", WEBGL1.mat4, "m", 4, 4,
+ {allBig: true});
+
+if (document.createElement("canvas").getContext("webgl2")) {
+ const WEBGL2 = {
+ mat2x4: `#version 300 es
+ in mat2x4 m;
+ void main() { gl_Position = m[0] + m[1]; gl_PointSize = 1.0; }`,
+ mat4x2: `#version 300 es
+ in mat4x2 m;
+ void main() { gl_Position = vec4(m[0], m[1]) + vec4(m[2], m[3]);
+ gl_PointSize = 1.0; }`,
+ };
+
+ check("mat2x4 later-column over-fetch", true, "webgl2",
+ WEBGL2.mat2x4, "m", 2, 4);
+ check("mat4x2 later-column over-fetch", true, "webgl2",
+ WEBGL2.mat4x2, "m", 4, 2);
+ check("mat4x2 instanced later-column over-fetch", true, "webgl2",
+ WEBGL2.mat4x2, "m", 4, 2, {instanced: true});
+} else {
+ ok(true, "webgl2 unavailable, skipping non-square/instanced cases");
+}
+
+ok(true, 'done');
+</script>
+</body>
+</html>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/dom/canvas/test/webgl-mochitest/mochitest.toml b/dom/canvas/test/webgl-mochitest/mochitest.toml
index 3f775a34f0b..9a6d1f6fb5c 100644
--- a/dom/canvas/test/webgl-mochitest/mochitest.toml
+++ b/dom/canvas/test/webgl-mochitest/mochitest.toml
@@ -170,6 +170,8 @@ support-files = ["../captureStream_common.js"]
["test_draw_fakevert_large_offset.html"]
+["test_draw_matrix_attrib_fetch_limits.html"]
+
["test_fb_param.html"]
["test_fb_param_crash.html"]
diff --git a/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html b/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html
new file mode 100644
index 00000000000..db55898ce04
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_draw_matrix_attrib_fetch_limits.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<script src='/tests/SimpleTest/SimpleTest.js'></script>
+</head>
+<body>
+<pre id=e_out></pre>
+<script>
+ok = window.ok || function(e, s) {
+ e_out.textContent += `\n${!e ? 'FAIL' : 'pass'}: ${s}`;
+};
+
+// Bug 2061503
+function run(version, vsSrc, attribName, numLocs, comps, opts = {}) {
+ const gl = document.createElement("canvas").getContext(version);
+ const fsSrc = version == "webgl2"
+ ? `#version 300 es
+ precision mediump float;
+ out vec4 o;
+ void main() { o = vec4(0.0); }`
+ : `void main() { gl_FragColor = vec4(0.0); }`;
+
+ const vs = gl.createShader(gl.VERTEX_SHADER);
+ gl.shaderSource(vs, vsSrc);
+ gl.compileShader(vs);
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
+ gl.shaderSource(fs, fsSrc);
+ gl.compileShader(fs);
+
+ const prog = gl.createProgram();
+ gl.attachShader(prog, vs);
+ gl.attachShader(prog, fs);
+ gl.bindAttribLocation(prog, 0, attribName);
+ gl.linkProgram(prog);
+ gl.useProgram(prog);
+
+ const N = 4096;
+ const vertBytes = comps * 4;
+
+ const big = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, big);
+ gl.bufferData(gl.ARRAY_BUFFER, N * vertBytes, gl.STATIC_DRAW);
+
+ const tiny = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, tiny);
+ gl.bufferData(gl.ARRAY_BUFFER, vertBytes, gl.STATIC_DRAW);
+
+ for (let loc = 0; loc < numLocs; loc++) {
+ const colBig = opts.allBig ||
+ (loc == 0 ? !opts.firstColSmall : false);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colBig ? big : tiny);
+ gl.enableVertexAttribArray(loc);
+ gl.vertexAttribPointer(loc, comps, gl.FLOAT, false, vertBytes, 0);
+ if (opts.instanced) {
+ gl.vertexAttribDivisor(loc, 1);
+ }
+ }
+
+ // Ensure no pending errors.
+ ok(gl.getError() == gl.NO_ERROR, "no error during setup");
+
+ if (opts.instanced) {
+ gl.drawArraysInstanced(gl.POINTS, 0, 1, N);
+ } else {
+ gl.drawArrays(gl.POINTS, 0, N);
+ }
+ return gl.getError();
+}
+
+const INVALID_OPERATION =
+ document.createElement("canvas").getContext("webgl").INVALID_OPERATION;
+
+function check(desc, expectError, version, ...args) {
+ const err = run(version, ...args);
+ const errored = err == INVALID_OPERATION;
+ ok(errored == expectError,
+ `${desc}: err:0x${err.toString(16)} ` +
+ `(expected ${expectError ? "INVALID_OPERATION" : "no error"})`);
+}
+
+const WEBGL1 = {
+ vec4: `attribute vec4 a; void main() { gl_Position = a; gl_PointSize = 1.0; }`,
+ mat2: `attribute mat2 m;
+ void main() { gl_Position = vec4(m[0], m[1]); gl_PointSize = 1.0; }`,
+ mat3: `attribute mat3 m;
+ void main() { gl_Position = vec4(m[0] + m[1] + m[2], 1.0);
+ gl_PointSize = 1.0; }`,
+ mat4: `attribute mat4 m;
+ void main() { gl_Position = m[0] + m[1] + m[2] + m[3];
+ gl_PointSize = 1.0; }`,
+};
+
+check("vec4 over-fetch", true, "webgl", WEBGL1.vec4, "a", 1, 4, {firstColSmall: true});
+check("mat2 later-column over-fetch", true, "webgl", WEBGL1.mat2, "m", 2, 4);
+check("mat3 later-column over-fetch", true, "webgl", WEBGL1.mat3, "m", 3, 4);
+check("mat4 later-column over-fetch", true, "webgl", WEBGL1.mat4, "m", 4, 4);
+
+check("mat4 fully backed", false, "webgl", WEBGL1.mat4, "m", 4, 4,
+ {allBig: true});
+
+if (document.createElement("canvas").getContext("webgl2")) {
+ const WEBGL2 = {
+ mat2x4: `#version 300 es
+ in mat2x4 m;
+ void main() { gl_Position = m[0] + m[1]; gl_PointSize = 1.0; }`,
+ mat4x2: `#version 300 es
+ in mat4x2 m;
+ void main() { gl_Position = vec4(m[0], m[1]) + vec4(m[2], m[3]);
+ gl_PointSize = 1.0; }`,
+ };
+
+ check("mat2x4 later-column over-fetch", true, "webgl2",
+ WEBGL2.mat2x4, "m", 2, 4);
+ check("mat4x2 later-column over-fetch", true, "webgl2",
+ WEBGL2.mat4x2, "m", 4, 2);
+ check("mat4x2 instanced later-column over-fetch", true, "webgl2",
+ WEBGL2.mat4x2, "m", 4, 2, {instanced: true});
+} else {
+ ok(true, "webgl2 unavailable, skipping non-square/instanced cases");
+}
+
+ok(true, 'done');
+</script>
+</body>
+</html>
Loading diff…
References
On This Page