Chrome · ANGLE › D3D11
CVE-2026-113295
OOB in ANGLE (D3D11)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ResourceManager11src/libANGLE/renderer/d3d/d3d11/ResourceManager11.h |
modified |
Files Changed
src/libANGLE/renderer/d3d/d3d11/StateManager11.hsrc/libANGLE/renderer/d3d/IndexDataManager.hsrc/libANGLE/renderer/d3d/d3d11/Buffer11.cppsrc/libANGLE/renderer/d3d/d3d11/ResourceManager11.cppsrc/libANGLE/renderer/d3d/d3d11/Renderer11.cppsrc/tests/gl_tests/VertexAttributeTest.cppsrc/libANGLE/renderer/d3d/d3d11/ResourceManager11.hsrc/libANGLE/renderer/d3d/d3d11/VertexArray11.cppsrc/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
Patch
From 8988bdf12f3bad432b523897cc940ccfa2f67275 Mon Sep 17 00:00:00 2001
From: Ran Wang <wangra@google.com>
Date: Mon, 05 Oct 2026 18:15:04 -0400
Subject: [PATCH] D3D11: Fix stale vertex and index buffer pointers
Clear cached D3D11 vertex and index buffer pointers on invalidation
and buffer release, keep state updates atomic on errors, and propagate
vertex array binding dirty bits across shared attributes.
Test:
angle_end2end_tests --gtest_filter="*DeletedVertexBufferBeforeInternalDraw*:*VertexBufferOffsetOverflowRecovery*"
Bug: b/568897206
Change-Id: I9f10aae425328e514359c4008fe31f3fa05c2901
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8520817
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Ran Wang <wangra@google.com>
---
diff --git a/src/libANGLE/renderer/d3d/IndexDataManager.h b/src/libANGLE/renderer/d3d/IndexDataManager.h
index 53bfc72..40aea6e 100644
--- a/src/libANGLE/renderer/d3d/IndexDataManager.h
+++ b/src/libANGLE/renderer/d3d/IndexDataManager.h
@@ -45,7 +45,6 @@
const void *srcIndices;
unsigned int srcCount;
gl::DrawElementsType srcIndexType;
- bool srcIndicesChanged;
};
struct TranslatedIndexData
diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
index 483e354..ec25cd2 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
@@ -799,6 +799,11 @@
ANGLE_TRY(packStorage->packPixels(context, readAttachment, params));
onStorageUpdate(packStorage);
+ // Pixel pack writes new data into this buffer. If the buffer is also bound as a vertex or
+ // index buffer, we must invalidate any cached static translated vertex/index buffers and set
+ // feedback->bufferStateChanged so bound VertexArrays re-sync on the next draw.
+ invalidateStaticData(context, feedback);
+
return angle::Result::Continue;
}
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 0565a59..f6b3ba2 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -400,7 +400,8 @@
mCreateDebugDevice(false),
mStateCache(),
mStateManager(this),
- mDebug(nullptr)
+ mDebug(nullptr),
+ mResourceManager11(this)
{
mLineLoopIB = nullptr;
mTriangleFanIB = nullptr;
diff --git a/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.cpp
index c459f20..c1f3486 100644
--- a/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.cpp
@@ -446,7 +446,8 @@
} // anonymous namespace
// ResourceManager11 Implementation.
-ResourceManager11::ResourceManager11() : mInitializeAllocations(false)
+ResourceManager11::ResourceManager11(Renderer11 *renderer)
+ : mRenderer(renderer), mInitializeAllocations(false)
{
for (auto &count : mAllocatedResourceCounts)
{
@@ -526,6 +527,15 @@
void ResourceManager11::onReleaseGeneric(ResourceType resourceType, ID3D11DeviceChild *resource)
{
ASSERT(resource);
+ // Only Buffer needs to notify StateManager11 here: mCurrentVertexBuffers and mAppliedIB are the
+ // only caches in StateManager11 that store raw COM pointers and re-submit cached slots to D3D11
+ // (via IASetVertexBuffers). Other resource types (textures/views, shaders, input layouts, and
+ // state objects) are tracked via integer serials or value structs and never re-submit cached
+ // pointers to D3D11.
+ if (resourceType == ResourceType::Buffer)
+ {
+ mRenderer->getStateManager()->onReleaseBuffer(static_cast<ID3D11Buffer *>(resource));
+ }
decrResource(resourceType, ComputeGenericMemoryUsage(resourceType, resource));
}
diff --git a/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.h b/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.h
index e9ebad5..9057bf6 100644
--- a/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.h
@@ -371,7 +371,7 @@
class ResourceManager11 final : angle::NonCopyable
{
public:
- ResourceManager11();
+ ResourceManager11(Renderer11 *renderer);
~ResourceManager11();
template <typename T>
@@ -411,6 +411,7 @@
template <typename T>
GetInitDataFromD3D11<T> *createInitDataIfNeeded(const GetDescFromD3D11<T> *desc);
+ Renderer11 *mRenderer;
bool mInitializeAllocations;
std::array<std::atomic_size_t, NumResourceTypes> mAllocatedResourceCounts;
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index e707657..62b10ae 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -1329,9 +1329,10 @@
void StateManager11::invalidateVertexBuffer()
{
- unsigned int limit = std::min<unsigned int>(mRenderer->getNativeCaps().maxVertexAttributes,
- gl::MAX_VERTEX_ATTRIBS);
- mDirtyVertexBufferRange = gl::RangeUI(0, limit);
+ mCurrentVertexBuffers.fill(nullptr);
+ mCurrentVertexStrides.fill(std::numeric_limits<UINT>::max());
+ mCurrentVertexOffsets.fill(std::numeric_limits<UINT>::max());
+ mDirtyVertexBufferRange = gl::RangeUI(gl::MAX_VERTEX_ATTRIBS, 0);
invalidateInputLayout();
invalidateShaders();
mInternalDirtyBits.set(DIRTY_BIT_CURRENT_VALUE_ATTRIBS);
@@ -1420,6 +1421,9 @@
void StateManager11::invalidateIndexBuffer()
{
mIndexBufferIsDirty = true;
+ mAppliedIB = nullptr;
+ mAppliedIBFormat = DXGI_FORMAT_UNKNOWN;
+ mAppliedIBOffset = std::numeric_limits<UINT>::max();
}
void StateManager11::setRenderTarget(ID3D11RenderTargetView *rtv, ID3D11DepthStencilView *dsv)
@@ -1478,6 +1482,30 @@
mCurrentQueries.erase(query);
}
+void StateManager11::onReleaseBuffer(ID3D11Buffer *buffer)
+{
+ if (buffer == nullptr)
+ {
+ return;
+ }
+
+ for (size_t attribIndex = 0; attribIndex < gl::MAX_VERTEX_ATTRIBS; ++attribIndex)
+ {
+ if (mCurrentVertexBuffers[attribIndex] == buffer)
+ {
+ mCurrentVertexBuffers[attribIndex] = nullptr;
+ mCurrentVertexStrides[attribIndex] = std::numeric_limits<UINT>::max();
+ mCurrentVertexOffsets[attribIndex] = std::numeric_limits<UINT>::max();
+ invalidateInputLayout();
+ }
+ }
+
+ if (mAppliedIB == buffer)
+ {
+ invalidateIndexBuffer();
+ }
+}
+
angle::Result StateManager11::onMakeCurrent(const gl::Context *context)
{
ANGLE_TRY(ensureInitialized(context));
@@ -1659,6 +1687,8 @@
mInputLayoutCache.clear();
mVertexDataManager.deinitialize();
mIndexDataManager.deinitialize();
+ invalidateVertexBuffer();
+ invalidateIndexBuffer();
for (d3d11::Buffer &ShaderDriverConstantBuffer : mShaderDriverConstantBuffers)
{
@@ -1761,7 +1791,8 @@
angle::Result StateManager11::syncCurrentValueAttribs(
const gl::Context *context,
- const std::vector<gl::VertexAttribCurrentValueData> ¤tValues)
+ const std::vector<gl::VertexAttribCurrentValueData> ¤tValues,
+ DirtyBits::Iterator &iter)
{
const gl::ProgramExecutable *executable = mExecutableD3D->getExecutable();
const auto &activeAttribsMask = executable->getActiveAttribLocationsMask();
@@ -1774,12 +1805,12 @@
const auto &vertexAttributes = mVertexArray11->getState().getVertexAttributes();
const auto &vertexBindings = mVertexArray11->getState().getVertexBindings();
- mDirtyCurrentValueAttribs = (mDirtyCurrentValueAttribs & ~dirtyActiveAttribs);
for (auto attribIndex : dirtyActiveAttribs)
{
if (vertexAttributes[attribIndex].enabled)
{
+ mDirtyCurrentValueAttribs.reset(attribIndex);
continue;
}
@@ -1792,10 +1823,19 @@
currentValueAttrib->bufferBindingPointer =
&mVertexArray11->getBufferBindingPointer(attrib->bindingIndex);
- mDirtyVertexBufferRange.extend(static_cast<unsigned int>(attribIndex));
-
ANGLE_TRY(mVertexDataManager.storeCurrentValue(context, currentValue, currentValueAttrib,
static_cast<size_t>(attribIndex)));
+ // Only clear the dirty bit after storeCurrentValue succeeds so a failed allocation will be
+ // retried on a subsequent draw call.
+ mDirtyCurrentValueAttribs.reset(attribIndex);
+ // D3D11 emulates disabled ("current value") vertex attributes using a stride-0 vertex
+ // buffer. storeCurrentValue writes the new constant into a D3D11 buffer and updates
+ // mCurrentValueAttribs[attribIndex], so we must set
+ // DIRTY_BIT_VERTEX_BUFFERS_AND_INPUT_LAYOUT (via invalidateInputLayout and iter) so
+ // syncVertexBuffersAndInputLayout -> applyVertexBuffers binds the updated buffer and offset
+ // to D3D11 via IASetVertexBuffers.
+ invalidateInputLayout();
+ iter.setLaterBit(DIRTY_BIT_VERTEX_BUFFERS_AND_INPUT_LAYOUT);
}
return angle::Result::Continue;
@@ -1988,7 +2028,6 @@
for (auto iter = dirtyBitsCopy.begin(), end = dirtyBitsCopy.end(); iter != end; ++iter)
{
- mInternalDirtyBits.reset(*iter);
switch (*iter)
{
case DIRTY_BIT_RENDER_TARGET:
@@ -2037,7 +2076,8 @@
ANGLE_TRY(syncProgram(context, mode));
break;
case DIRTY_BIT_CURRENT_VALUE_ATTRIBS:
- ANGLE_TRY(syncCurrentValueAttribs(context, glState.getVertexAttribCurrentValues()));
+ ANGLE_TRY(
+ syncCurrentValueAttribs(context, glState.getVertexAttribCurrentValues(), iter));
break;
case DIRTY_BIT_TRANSFORM_FEEDBACK:
ANGLE_TRY(syncTransformFeedbackBuffers(context));
@@ -2054,6 +2094,10 @@
UNREACHABLE();
break;
}
+
+ // Clear the dirty bit only after its sync handler succeeds so that if an ANGLE_TRY fails,
+ // the bit remains set and will be retried on a subsequent draw call.
+ mInternalDirtyBits.reset(*iter);
}
// Check that we haven't set any dirty bits in the flushing of the dirty bits loop.
@@ -2687,10 +2731,11 @@
ANGLE_TRY(mInputLayoutCache.getInputLayout(GetImplAs<Context11>(context), state,
mCurrentAttributes, sortedSemanticIndices, mode,
vertexOrIndexCount, instanceCount, &inputLayout));
- setInputLayoutInternal(inputLayout);
- // Update the applied vertex buffers.
+ // Update the applied vertex buffers before committing inputLayout so that if applyVertexBuffers
+ // fails via ANGLE_TRY, we do not leave a partially updated input layout state.
ANGLE_TRY(applyVertexBuffers(context, mode, indexTypeOrInvalid, firstVertex));
+ setInputLayoutInternal(inputLayout);
return angle::Result::Continue;
}
@@ -2700,40 +2745,46 @@
gl::DrawElementsType indexTypeOrInvalid,
GLint firstVertex)
{
- for (size_t attribIndex = 0; attribIndex < gl::MAX_VERTEX_ATTRIBS; ++attribIndex)
+ // Resolve all vertex buffers and offsets into local arrays before calling
+ // queueVertexBufferChange. If getBuffer or computeOffset fails partway through the loop, this
+ // avoids leaving uncommitted buffer pointers queued in mCurrentVertexBuffers and
+ // mDirtyVertexBufferRange without applyVertexBufferChanges being called.
+ gl::AttribArray<ID3D11Buffer *> buffers = {};
+ gl::AttribArray<UINT> vertexStrides = {};
+ gl::AttribArray<UINT> vertexOffsets = {};
+
+ for (size_t attribIndex = 0; attribIndex < mCurrentAttributes.size(); ++attribIndex)
{
- ID3D11Buffer *buffer = nullptr;
- UINT vertexStride = 0;
- UINT vertexOffset = 0;
+ const TranslatedAttribute &attrib = *mCurrentAttributes[attribIndex];
+ Buffer11 *bufferStorage = attrib.storage ? GetAs<Buffer11>(attrib.storage) : nullptr;
- if (attribIndex < mCurrentAttributes.size())
+ // If indexed pointsprite emulation is active, then we need to take a less efficient code
+ // path. Emulated indexed pointsprite rendering requires that the vertex buffers match
+ // exactly to the indices passed by the caller. This could expand or shrink the vertex
+ // buffer depending on the number of points indicated by the index list or how many
+ // duplicates are found on the index list.
+ if (bufferStorage == nullptr)
{
- const TranslatedAttribute &attrib = *mCurrentAttributes[attribIndex];
- Buffer11 *bufferStorage = attrib.storage ? GetAs<Buffer11>(attrib.storage) : nullptr;
-
- // If indexed pointsprite emulation is active, then we need to take a less efficent code
- // path. Emulated indexed pointsprite rendering requires that the vertex buffers match
- // exactly to the indices passed by the caller. This could expand or shrink the vertex
- // buffer depending on the number of points indicated by the index list or how many
- // duplicates are found on the index list.
- if (bufferStorage == nullptr)
- {
- ASSERT(attrib.vertexBuffer.get());
- buffer = GetAs<VertexBuffer11>(attrib.vertexBuffer.get())->getBuffer().get();
- }
- else
- {
- BufferFeedback feedback;
- ANGLE_TRY(bufferStorage->getBuffer(
- context, BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK, &buffer, &feedback));
- attrib.bufferBindingPointer->get()->applyImplFeedback(context, feedback);
- }
-
- vertexStride = attrib.stride;
- ANGLE_TRY(attrib.computeOffset(context, firstVertex, &vertexOffset));
+ ASSERT(attrib.vertexBuffer.get());
+ buffers[attribIndex] =
+ GetAs<VertexBuffer11>(attrib.vertexBuffer.get())->getBuffer().get();
+ }
+ else
+ {
+ BufferFeedback feedback;
+ ANGLE_TRY(bufferStorage->getBuffer(context, BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK,
+ &buffers[attribIndex], &feedback));
+ attrib.bufferBindingPointer->get()->applyImplFeedback(context, feedback);
}
- queueVertexBufferChange(attribIndex, buffer, vertexStride, vertexOffset);
+ vertexStrides[attribIndex] = attrib.stride;
+ ANGLE_TRY(attrib.computeOffset(context, firstVertex, &vertexOffsets[attribIndex]));
+ }
+
+ for (size_t attribIndex = 0; attribIndex < gl::MAX_VERTEX_ATTRIBS; ++attribIndex)
+ {
+ queueVertexBufferChange(attribIndex, buffers[attribIndex], vertexStrides[attribIndex],
+ vertexOffsets[attribIndex]);
}
applyVertexBufferChanges();
@@ -2776,9 +2827,7 @@
buffer = indexBuffer->getBuffer().get();
}
- // Track dirty indices in the index range cache.
- indexInfo.srcIndexData.srcIndicesChanged =
- syncIndexBuffer(buffer, bufferFormat, indexInfo.startOffset);
+ syncIndexBuffer(buffer, bufferFormat, indexInfo.startOffset);
mIndexBufferIsDirty = false;
@@ -2792,7 +2841,10 @@
{
if (syncIndexBuffer(buffer, indexFormat, offset))
{
- invalidateIndexBuffer();
+ // Only mark the index buffer dirty for the next GL draw without calling
+ // invalidateIndexBuffer(), since syncIndexBuffer just updated mAppliedIB, mAppliedIBFormat,
+ // and mAppliedIBOffset to match the D3D11 device context.
+ mIndexBufferIsDirty = true;
}
}
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
index 619e718..faf5009 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
@@ -202,6 +202,7 @@
void onBeginQuery(Query11 *query);
void onDeleteQueryObject(Query11 *query);
+ void onReleaseBuffer(ID3D11Buffer *buffer);
angle::Result onMakeCurrent(const gl::Context *context);
void setInputLayout(const d3d11::InputLayout *inputLayout);
@@ -335,10 +336,6 @@
const gl::ImageUnit &imageUnit,
UAVList *uavList);
- angle::Result syncCurrentValueAttribs(
- const gl::Context *context,
- const std::vector<gl::VertexAttribCurrentValueData> ¤tValues);
-
angle::Result generateSwizzle(const gl::Context *context, gl::Texture *texture);
angle::Result generateSwizzlesForShader(const gl::Context *context, gl::ShaderType type);
angle::Result generateSwizzles(const gl::Context *context);
@@ -425,6 +422,11 @@
using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
+ angle::Result syncCurrentValueAttribs(
+ const gl::Context *context,
+ const std::vector<gl::VertexAttribCurrentValueData> ¤tValues,
+ DirtyBits::Iterator &iter);
+
Renderer11 *mRenderer;
... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/gl_tests/VertexAttributeTest.cpp b/src/tests/gl_tests/VertexAttributeTest.cpp
index c03fdc9..f84aece 100644
--- a/src/tests/gl_tests/VertexAttributeTest.cpp
+++ b/src/tests/gl_tests/VertexAttributeTest.cpp
@@ -7414,6 +7414,187 @@
EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}
+// Test that deleting a vertex buffer bound to an attribute slot >= 1, rebinding a new buffer, and
+// modifying a swizzled texture before the next draw (which triggers an internal blit pass prior to
+// the draw) does not reference the deleted vertex buffer.
+TEST_P(VertexAttributeTestES3, DeletedVertexBufferBeforeInternalDraw)
+{
+ constexpr char kVS[] = R"(#version 300 es
+layout(location = 0) in vec4 a_position;
+layout(location = 1) in vec4 a_color;
+out vec4 v_color;
+void main()
+{
+ gl_Position = a_position;
+ v_color = a_color;
+})";
+
+ constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+in vec4 v_color;
+uniform sampler2D u_tex;
+out vec4 fragColor;
+void main()
+{
+ fragColor = v_color * texture(u_tex, vec2(0.5, 0.5));
+})";
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ const GLColor whitePixel = GLColor::white;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &whitePixel);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ALPHA);
+
+ GLVertexArray vao;
+ glBindVertexArray(vao);
+
+ constexpr size_t kQuadVertices = 6;
+ const std::array<Vector3, kQuadVertices> &quadVert = GetQuadVertices();
+
+ std::vector<Vector4> redColors(kQuadVertices, Vector4(1.0f, 0.0f, 0.0f, 1.0f));
+ std::vector<Vector4> greenColors(kQuadVertices, Vector4(0.0f, 1.0f, 0.0f, 1.0f));
+
+ GLBuffer posBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
+ glBufferData(GL_ARRAY_BUFFER, quadVert.size() * sizeof(Vector3), quadVert.data(),
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+
+ GLBuffer newColorBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, newColorBuffer);
+ glBufferData(GL_ARRAY_BUFFER, greenColors.size() * sizeof(Vector4), greenColors.data(),
+ GL_STATIC_DRAW);
+
+ GLBuffer colorBufferToDelete;
+ glBindBuffer(GL_ARRAY_BUFFER, colorBufferToDelete);
+ glBufferData(GL_ARRAY_BUFFER, redColors.size() * sizeof(Vector4), redColors.data(),
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
+
+ // First draw binds the initial vertex buffers for attributes 0 and 1.
+ glDrawArrays(GL_TRIANGLES, 0, kQuadVertices);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+ // Delete colorBufferToDelete while it was bound to attribute 1.
+ colorBufferToDelete.reset();
+
+ // Bind newColorBuffer to attribute 1 and dirty the swizzled texture via glTexSubImage2D so the
+ // cached swizzled texture copy is invalidated and must be regenerated by an internal blit draw
+ // at the start of the next glDrawArrays call.
+ glBindBuffer(GL_ARRAY_BUFFER, newColorBuffer);
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &whitePixel);
+
+ glDrawArrays(GL_TRIANGLES, 0, kQuadVertices);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
+// Test that a draw call failing due to a 32-bit vertex buffer offset overflow at a non-zero
+// firstVertex leaves vertex buffer state consistent: repeating the same invalid draw must continue
+// to fail with GL_INVALID_OPERATION, and drawing after disabling the overflowing attribute must
+// render with the expected vertex data for that firstVertex.
+TEST_P(VertexAttributeTestES3, VertexBufferOffsetOverflowRecovery)
+{
+ // Folding firstVertex * stride into a 32-bit unsigned byte offset (and failing with
+ // GL_INVALID_OPERATION when it overflows 32 bits) is specific to the D3D11 backend.
+ ANGLE_SKIP_TEST_IF(!IsD3D11());
+
+ constexpr char kVS[] = R"(#version 300 es
+layout(location = 0) in vec4 a_position;
+layout(location = 1) in vec4 a_color;
+layout(location = 2) in vec4 a_extra;
+out vec4 v_color;
+void main()
+{
+ gl_Position = a_position + a_extra * 0.0;
+ v_color = a_color;
+})";
+
+ constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+in vec4 v_color;
+out vec4 fragColor;
+void main()
+{
+ fragColor = v_color;
+})";
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+
+ GLVertexArray vao;
+ glBindVertexArray(vao);
+
+ constexpr size_t kQuadVertices = 6;
+ constexpr size_t kTotalVertices = 12;
+ const std::array<Vector3, kQuadVertices> &quadVert = GetQuadVertices();
+ std::vector<Vector3> posData(kTotalVertices);
+ std::copy(quadVert.begin(), quadVert.end(), posData.begin());
+ std::copy(quadVert.begin(), quadVert.end(), posData.begin() + kQuadVertices);
+
+ std::vector<Vector4> colorData(kTotalVertices);
+ std::fill(colorData.begin(), colorData.begin() + kQuadVertices,
+ Vector4(1.0f, 0.0f, 0.0f, 1.0f));
+ std::fill(colorData.begin() + kQuadVertices, colorData.end(), Vector4(0.0f, 1.0f, 0.0f, 1.0f));
+
+ GLBuffer posBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
+ glBufferData(GL_ARRAY_BUFFER, posData.size() * sizeof(Vector3), posData.data(), GL_STATIC_DRAW);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+
+ GLBuffer colorBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
+ glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(Vector4), colorData.data(),
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
+
+ // Enable attribute 2 with a large base offset (0xFFFFFFF0) and stride 16.
+ // For firstVertex == 0, offset 0xFFFFFFF0 fits in 32 bits and succeeds.
+ // For firstVertex == 6, offset 0xFFFFFFF0 + 6 * 16 overflows 32 bits and fails.
+ GLBuffer extraBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, extraBuffer);
+ glBufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 16,
+ reinterpret_cast<const void *>(static_cast<uintptr_t>(0xFFFFFFF0u)));
+
+ // First draw with firstVertex == 0 succeeds and draws the red quad.
+ glDrawArrays(GL_TRIANGLES, 0, kQuadVertices);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+ // Draw with firstVertex == 6: attribute 0 and 1 offsets are valid, but attribute 2's offset
+ // overflows 32 bits and causes the draw to fail with GL_INVALID_OPERATION.
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDrawArrays(GL_TRIANGLES, 6, kQuadVertices);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // Repeating the same draw call with firstVertex == 6 must still fail and must not draw with
+ // partially updated or stale vertex buffer state.
+ glDrawArrays(GL_TRIANGLES, 6, kQuadVertices);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
+
+ // Disable the overflowing attribute 2 and draw with firstVertex == 6 (which uses the green
+ // half of colorBuffer).
+ glDisableVertexAttribArray(2);
+ glDrawArrays(GL_TRIANGLES, 6, kQuadVertices);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
ANGLE_INSTANTIATE_TEST_ES3(VertexAttributeResizeTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VertexAttributeUint8Test);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page