From f3b438abf346c7f49b16f800a3af7ff9d1a4edcc Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 23 Nov 2020 19:54:41 +0000 (2020-11-24) Subject: [PATCH] CVE-2020-26971 --- dom/canvas/WebGLFramebuffer.cpp | 129 ++++++++++++++---- dom/canvas/WebGLTypes.h | 51 ++++++- .../test/webgl-conf/generated-mochitest.ini | 1 - .../test/webgl-conf/mochitest-errata.ini | 2 - 4 files changed, 154 insertions(+), 29 deletions(-) diff --git a/dom/canvas/WebGLFramebuffer.cpp b/dom/canvas/WebGLFramebuffer.cpp index 155ef0277e..53be7b4570 100644 --- a/dom/canvas/WebGLFramebuffer.cpp +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -1263,11 +1263,16 @@ static void GetBackbufferFormats(const WebGLContext* webgl, } /*static*/ -void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, - GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, - GLint dstY1, GLbitfield mask, - GLenum filter) { +void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint _srcX0, + GLint _srcY0, GLint _srcX1, GLint _srcY1, + GLint _dstX0, GLint _dstY0, GLint _dstX1, + GLint _dstY1, GLbitfield mask, + GLenum filter) { + auto srcP0 = ivec2{_srcX0, _srcY0}; + auto srcP1 = ivec2{_srcX1, _srcY1}; + auto dstP0 = ivec2{_dstX0, _dstY0}; + auto dstP1 = ivec2{_dstX1, _dstY1}; + const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; if (bool(mask & depthAndStencilBits) && filter == LOCAL_GL_LINEAR) { @@ -1420,8 +1425,8 @@ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, return; } - if (dstX0 != srcX0 || dstX1 != srcX1 || dstY0 != srcY0 || dstY1 != srcY1) { - webgl->ErrorInvalidOperation( + if (srcP0 != dstP0 || srcP1 != dstP1) { + webgl->ErrorInvalidOperation( "If the source is multisampled, then the" " source and dest regions must match exactly."); return; @@ -1510,11 +1515,83 @@ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, } // - + // Mutually constrain src and dst rects for eldritch blits. + + [&]{ + using fvec2 = avec2; // Switch to float, because there's no perfect solution anyway. + + const auto zero2f = fvec2{0, 0}; + const auto srcSizef = AsVec(srcSize).StaticCast(); + const auto dstSizef = AsVec(dstSize).StaticCast(); + + const auto srcP0f = srcP0.StaticCast(); + const auto srcP1f = srcP1.StaticCast(); + const auto dstP0f = dstP0.StaticCast(); + const auto dstP1f = dstP1.StaticCast(); + + const auto srcRectDiff = srcP1f - srcP0f; + const auto dstRectDiff = dstP1f - dstP0f; + + // Skip if zero-sized. + if (!srcRectDiff.x || !srcRectDiff.y || !dstRectDiff.x || !dstRectDiff.y) { + srcP0 = srcP1 = dstP0 = dstP1 = {0,0}; + return; + } + + // Clamp the rect points + const auto srcQ0 = srcP0f.ClampMinMax(zero2f, srcSizef); + const auto srcQ1 = srcP1f.ClampMinMax(zero2f, srcSizef); + + // Normalized to the [0,1] abstact copy rect + const auto srcQ0Norm = (srcQ0 - srcP0f) / srcRectDiff; + const auto srcQ1Norm = (srcQ1 - srcP0f) / srcRectDiff; + + // Map into dst + const auto srcQ0InDst = dstP0f + srcQ0Norm * dstRectDiff; + const auto srcQ1InDst = dstP0f + srcQ1Norm * dstRectDiff; + + // Clamp the rect points + const auto dstQ0 = srcQ0InDst.ClampMinMax(zero2f, dstSizef); + const auto dstQ1 = srcQ1InDst.ClampMinMax(zero2f, dstSizef); + + // Alright, time to go back to src! + // Normalized to the [0,1] abstact copy rect + const auto dstQ0Norm = (dstQ0 - dstP0f) / dstRectDiff; + const auto dstQ1Norm = (dstQ1 - dstP0f) / dstRectDiff; + + // Map into src + const auto dstQ0InSrc = srcP0f + dstQ0Norm * srcRectDiff; + const auto dstQ1InSrc = srcP0f + dstQ1Norm * srcRectDiff; + + const auto srcQ0Constrained = dstQ0InSrc.ClampMinMax(zero2f, srcSizef); + const auto srcQ1Constrained = dstQ1InSrc.ClampMinMax(zero2f, srcSizef); + + // Round, don't floor: + srcP0 = (srcQ0Constrained + 0.5).StaticCast(); + srcP1 = (srcQ1Constrained + 0.5).StaticCast(); + dstP0 = (dstQ0 + 0.5).StaticCast(); + dstP1 = (dstQ1 + 0.5).StaticCast(); + }(); + + bool inBounds = true; + inBounds &= ( srcP0 == srcP0.Clamp({0,0}, AsVec(srcSize)) ); + inBounds &= ( srcP1 == srcP1.Clamp({0,0}, AsVec(srcSize)) ); + inBounds &= ( dstP0 == dstP0.Clamp({0,0}, AsVec(dstSize)) ); + inBounds &= ( dstP1 == dstP1.Clamp({0,0}, AsVec(dstSize)) ); + if (!inBounds) { + webgl->ErrorImplementationBug("Subrects still not within src and dst after constraining."); + return; + } + + // - + // Execute as constrained + const auto& gl = webgl->gl; const ScopedDrawCallWrapper wrapper(*webgl); - gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, - mask, filter); + + gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y, + dstP1.x, dstP1.y, mask, filter); // - @@ -1557,13 +1634,14 @@ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, if (srcColorFormat->isSRGB) { // srgb -> linear - gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, srcX0, srcY0, srcX1, - srcY1, LOCAL_GL_COLOR_BUFFER_BIT, - LOCAL_GL_NEAREST); + gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, srcP0.x, + srcP0.y, srcP1.x, srcP1.y, + LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); } else { // linear -> srgb - gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, - dstY1, LOCAL_GL_COLOR_BUFFER_BIT, filter); + gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, + dstP0.y, dstP1.x, dstP1.y, + LOCAL_GL_COLOR_BUFFER_BIT, filter); } const auto& blitHelper = *gl->BlitHelper(); @@ -1577,13 +1655,14 @@ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, if (srcColorFormat->isSRGB) { // srgb -> linear - gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, - dstY1, LOCAL_GL_COLOR_BUFFER_BIT, filter); + gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, + dstP0.y, dstP1.x, dstP1.y, + LOCAL_GL_COLOR_BUFFER_BIT, filter); } else { // linear -> srgb - gl->fBlitFramebuffer(dstX0, dstY0, dstX1, dstY1, dstX0, dstY0, dstX1, - dstY1, LOCAL_GL_COLOR_BUFFER_BIT, - LOCAL_GL_NEAREST); + gl->fBlitFramebuffer(dstP0.x, dstP0.y, dstP1.x, dstP1.y, dstP0.x, + dstP0.y, dstP1.x, dstP1.y, + LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); } } } @@ -1598,13 +1677,15 @@ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint srcX0, if (webgl->mRasterizerDiscardEnabled) { gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD); } - const WebGLContext::ScissorRect dstRect = { - std::min(dstX0, dstX1), std::min(dstY0, dstY1), abs(dstX1 - dstX0), - abs(dstY1 - dstY0)}; + const auto dstRectMin = MinExtents(dstP0, dstP1); + const auto dstRectMax = MaxExtents(dstP0, dstP1); + const auto dstRectSize = dstRectMax - dstRectMin; + const WebGLContext::ScissorRect dstRect = {dstRectMin.x, dstRectMin.y, + dstRectSize.x, dstRectSize.y}; + dstRect.Apply(*gl); gl->fClearColor(0, 0, 0, 1); - - webgl->DoColorMask(0x8); + webgl->DoColorMask(1 << 3); gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); if (!webgl->mScissorTestEnabled) { diff --git a/dom/canvas/WebGLTypes.h b/dom/canvas/WebGLTypes.h index f04184e289..38006ab37a 100644 --- a/dom/canvas/WebGLTypes.h +++ b/dom/canvas/WebGLTypes.h @@ -15,6 +15,7 @@ #include "GLDefs.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" +#include "mozilla/MathAlgorithms.h" #include "mozilla/Range.h" #include "mozilla/RefCounted.h" #include "mozilla/gfx/Point.h" @@ -391,8 +392,9 @@ struct WebGLContextOptions { // - -template +template struct avec2 { + using T = _T; T x = T(); T y = T(); @@ -418,10 +420,54 @@ struct avec2 { bool operator==(const avec2& rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const avec2& rhs) const { return !(*this == rhs); } + +#define _(OP) \ + avec2 operator OP(const avec2& rhs) const { \ + return {x OP rhs.x, y OP rhs.y}; \ + } \ + avec2 operator OP(const T rhs) const { return {x OP rhs, y OP rhs}; } + + _(+) + _(-) + _(*) + _(/) + +#undef _ + + avec2 Clamp(const avec2& min, const avec2& max) const { + return {mozilla::Clamp(x, min.x, max.x), mozilla::Clamp(y, min.y, max.y)}; + } + + // mozilla::Clamp doesn't work on floats, so be clear that this is a min+max + // helper. + avec2 ClampMinMax(const avec2& min, const avec2& max) const { + const auto ClampScalar = [](const T v, const T min, const T max) { + return std::max(min, std::min(v, max)); + }; + return {ClampScalar(x, min.x, max.x), ClampScalar(y, min.y, max.y)}; + } + + template + U StaticCast() const { + return {static_cast(x), static_cast(y)}; + } }; -template +template +avec2 MinExtents(const avec2& a, const avec2& b) { + return {std::min(a.x, b.x), std::min(a.y, b.y)}; +} + +template +avec2 MaxExtents(const avec2& a, const avec2& b) { + return {std::max(a.x, b.x), std::max(a.y, b.y)}; +} + +// - + +template struct avec3 { + using T = _T; T x = T(); T y = T(); T z = T(); @@ -454,6 +500,7 @@ typedef avec3 ivec3; typedef avec2 uvec2; typedef avec3 uvec3; +inline ivec2 AsVec(const gfx::IntSize& s) { return {s.width, s.height}; } // - namespace webgl { diff --git a/dom/canvas/test/webgl-conf/generated-mochitest.ini b/dom/canvas/test/webgl-conf/generated-mochitest.ini index 0d47a180bd..246f83c465 100644 --- a/dom/canvas/test/webgl-conf/generated-mochitest.ini +++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini @@ -5328,7 +5328,6 @@ subsuite = webgl2-core fail-if = (os == 'linux') [generated/test_2_conformance2__rendering__blitframebuffer-outside-readbuffer.html] subsuite = webgl2-core -fail-if = (os == 'linux') || (os == 'mac') [generated/test_2_conformance2__rendering__blitframebuffer-r11f-g11f-b10f.html] subsuite = webgl2-core [generated/test_2_conformance2__rendering__blitframebuffer-resolve-to-back-buffer.html] diff --git a/dom/canvas/test/webgl-conf/mochitest-errata.ini b/dom/canvas/test/webgl-conf/mochitest-errata.ini index 2ef5365c9e..903e721b02 100644 --- a/dom/canvas/test/webgl-conf/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini @@ -335,8 +335,6 @@ skip-if = (os == 'android') skip-if = (os == 'win') || (os == 'mac') [generated/test_2_conformance2__glsl3__tricky-loop-conditions.html] fail-if = (os == 'win') -[generated/test_2_conformance2__rendering__blitframebuffer-outside-readbuffer.html] -fail-if = (os == 'linux') || (os == 'mac') [generated/test_2_conformance2__textures__misc__tex-srgb-mipmap.html] fail-if = (os == 'mac') [generated/test_2_conformance2__textures__video__tex-3d-r11f_g11f_b10f-rgb-float.html] -- 2.27.0