Chrome · ANGLE › D3D11
CVE-2026-113252
Input Validation in ANGLE (D3D11)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/tests/gl_tests/WebGLCompatibilityTest.cppsrc/libANGLE/validationES.hsrc/tests/gl_tests/TextureTest.cppsrc/libANGLE/validationES.cppsrc/libANGLE/validationES3.cppsrc/tests/angle_end2end_tests_expectations.txtsrc/libANGLE/validationES2.cppsrc/libANGLE/renderer/d3d/d3d11/PixelTransfer11.cpp
Patch
From 906ad3fd30715befeb5a6fb01a4fabf1c9cdb15a Mon Sep 17 00:00:00 2001 From: Antonio Maiorano <[email protected]> Date: Thu, 01 Oct 2026 15:29:41 -0400 Subject: [PATCH] D3D11: Fix UNPACK_ROW_LENGTH handling in fast PBO unpack In PixelTransfer11::setBufferToTextureCopyParams, PixelsPerRow was set to unpack.rowLength (when > 0) instead of destArea.width. Because PixelsPerRow controls the number of destination columns drawn per row, setting it to unpack.rowLength caused incomplete row writes when 0 < unpack.rowLength < destArea.width and out-of-bounds row writes when unpack.rowLength > destArea.width on partial-width updates. Set PixelsPerRow to destArea.width and compute RowStride from unpack.rowLength (when > 0) or destArea.width. Also move PBO unpack validation and hardened-context unpack parameter validation from ValidateES3TexImageParametersBase into ValidImageDataSize so ES2 contexts with GL_EXT_unpack_subimage and GL_NV_pixel_buffer_object enforce the same checks as ES3. Add tests: - Texture2DTestES3.TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO - WebGL1CompatibilityTest.UnpackStateAndPBOValidation Skip Texture2DTestES3.TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO on PIXEL10 GLES (PowerVR), where the native GLES driver fails with GL_INVALID_OPERATION when unpacking overlapping rows (0 < GL_UNPACK_ROW_LENGTH < width) from a PBO, matching existing skips under crbug.com/480069042. Bug: b/556750258 Change-Id: Idf1791c5e0eead05c9bbf2a1995cbc393b3e1127 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8502520 Reviewed-by: Shahbaz Youssefi <[email protected]> Commit-Queue: Antonio Maiorano <[email protected]> Reviewed-by: Ran Wang <[email protected]> --- diff --git a/src/libANGLE/renderer/d3d/d3d11/PixelTransfer11.cpp b/src/libANGLE/renderer/d3d/d3d11/PixelTransfer11.cpp index b806942..cf9097d 100644 --- a/src/libANGLE/renderer/d3d/d3d11/PixelTransfer11.cpp +++ b/src/libANGLE/renderer/d3d/d3d11/PixelTransfer11.cpp @@ -135,9 +135,10 @@ (alignmentBytes <= bytesPerPixel ? 1 : alignmentBytes / bytesPerPixel); parametersOut->FirstPixelOffset = offset / bytesPerPixel; - parametersOut->PixelsPerRow = - static_cast<unsigned int>((unpack.rowLength > 0) ? unpack.rowLength : destArea.width); - parametersOut->RowStride = roundUp(parametersOut->PixelsPerRow, alignmentPixels); + parametersOut->PixelsPerRow = static_cast<unsigned int>(destArea.width); + parametersOut->RowStride = roundUp( + static_cast<unsigned int>((unpack.rowLength > 0) ? unpack.rowLength : destArea.width), + alignmentPixels); parametersOut->RowsPerSlice = static_cast<unsigned int>(destArea.height); parametersOut->PositionOffset[0] = texelCenterX + (destArea.x / float(destSize.width)) * 2.0f - 1.0f; diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp index d2a5a26..7e0b75f 100644 --- a/src/libANGLE/validationES.cpp +++ b/src/libANGLE/validationES.cpp @@ -1228,6 +1228,7 @@ GLsizei width, GLsizei height, GLsizei depth, + bool isSubImage, GLenum format, GLenum type, const void *pixels, @@ -1273,12 +1274,82 @@ ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kParamOverflow); return false; } + + if (context->isHardenedContext() && pixelUnpackBuffer->hasTFBBindingConflict()) + { + ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, + kPixelUnpackBufferBoundForTransformFeedback); + return false; + } + + // ...data is not evenly divisible into the number of bytes needed to store in memory a + // datum indicated by type. + { + size_t offset = reinterpret_cast<size_t>(pixels); + size_t dataBytesPerPixel = static_cast<size_t>(GetTypeInfo(type).bytes); + + if ((offset % dataBytesPerPixel) != 0) + { + ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kDataTypeNotAligned); + return false; + } + } + + // ...the buffer object's data store is currently mapped but not persistently. + if (pixelUnpackBuffer->isMapped() && !pixelUnpackBuffer->isPersistentlyMapped()) + { + ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kBufferMapped); + return false; + } } else if (outImageSize != nullptr) { *outImageSize = endByte; } + if (context->isHardenedContext()) + { + // Define: + // DataStoreWidth = (GL_UNPACK_ROW_LENGTH ? GL_UNPACK_ROW_LENGTH : width) + // DataStoreHeight = (GL_UNPACK_IMAGE_HEIGHT ? GL_UNPACK_IMAGE_HEIGHT : height) + // + // WebGL 2.0 / hardened contexts impose the following additional constraints: + // + // 1) texImage2D and texSubImage2D generate INVALID_OPERATION if: + // GL_UNPACK_SKIP_PIXELS + width > DataStoreWidth + // except for texImage2D if no GL_PIXEL_UNPACK_BUFFER is + // bound and _pixels_ is null. + // + // 2) texImage3D and texSubImage3D generate INVALID_OPERATION if: + // GL_UNPACK_SKIP_PIXELS + width > DataStoreWidth + // GL_UNPACK_SKIP_ROWS + height > DataStoreHeight + // except for texImage3D if no GL_PIXEL_UNPACK_BUFFER is + // bound and _pixels_ is null. + if (!pixelUnpackBuffer && !pixels && !isSubImage) + { + // Exception case for texImage2D or texImage3D, above. + } + else + { + GLint dataStoreWidth = unpack.rowLength ? unpack.rowLength : width; + if (unpack.skipPixels + width > dataStoreWidth) + { + ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kInvalidUnpackParametersForWebGL); + return false; + } + if (texType == TextureType::_3D || texType == TextureType::_2DArray) + { + // Note that CubeMapArray is not supported as a texture target in WebGL. + GLint dataStoreHeight = unpack.imageHeight ? unpack.imageHeight : height; + if (unpack.skipRows + height > dataStoreHeight) + { + ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kInvalidUnpackParametersForWebGL); + return false; + } + } + } + } + return true; } diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h index 811244c..58fec91 100644 --- a/src/libANGLE/validationES.h +++ b/src/libANGLE/validationES.h @@ -79,6 +79,7 @@ GLsizei width, GLsizei height, GLsizei depth, + bool isSubImage, GLenum format, GLenum type, const void *pixels, diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp index 40d9b54..95d89a2 100644 --- a/src/libANGLE/validationES2.cpp +++ b/src/libANGLE/validationES2.cpp @@ -1769,8 +1769,8 @@ } GLenum sizeCheckFormat = isSubImage ? format : internalformat; - return ValidImageDataSize(context, entryPoint, texType, width, height, 1, sizeCheckFormat, type, - pixels, outImageSize); + return ValidImageDataSize(context, entryPoint, texType, width, height, 1, isSubImage, + sizeCheckFormat, type, pixels, outImageSize); } bool ValidateCompressedTexImage(const Context *context, diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp index 21b661c..9528cf7 100644 --- a/src/libANGLE/validationES3.cpp +++ b/src/libANGLE/validationES3.cpp @@ -647,88 +647,14 @@ } GLenum sizeCheckFormat = isSubImage ? format : internalformat; - if (!ValidImageDataSize(context, entryPoint, texType, width, height, depth, sizeCheckFormat, - type, pixels, outImageSize)) + if (!ValidImageDataSize(context, entryPoint, texType, width, height, depth, isSubImage, + sizeCheckFormat, type, pixels, outImageSize)) { return false; } - // Check for pixel unpack buffer related API errors - Buffer *pixelUnpackBuffer = context->getState().getTargetBuffer(BufferBinding::PixelUnpack); - if (pixelUnpackBuffer != nullptr) - { - if (context->isHardenedContext() && pixelUnpackBuffer->hasTFBBindingConflict()) - { - ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, - kPixelUnpackBufferBoundForTransformFeedback); - return false; - } - - // ...data is not evenly divisible into the number of bytes needed to store in memory a - // datum - // indicated by type. - { - size_t offset = reinterpret_cast<size_t>(pixels); - size_t dataBytesPerPixel = static_cast<size_t>(GetTypeInfo(type).bytes); - - if ((offset % dataBytesPerPixel) != 0) - { - ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kDataTypeNotAligned); - return false; - } - } - - // ...the buffer object's data store is currently mapped but not persistently. - if (pixelUnpackBuffer->isMapped() && !pixelUnpackBuffer->isPersistentlyMapped()) - { - ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kBufferMapped); - return false; - } - } - if (context->isHardenedContext()) { - // Define: - // DataStoreWidth = (GL_UNPACK_ROW_LENGTH ? GL_UNPACK_ROW_LENGTH : width) - // DataStoreHeight = (GL_UNPACK_IMAGE_HEIGHT ? GL_UNPACK_IMAGE_HEIGHT : height) - // - // WebGL 2.0 imposes the following additional constraints: - // - // 1) texImage2D and texSubImage2D generate INVALID_OPERATION if: - // GL_UNPACK_SKIP_PIXELS + width > DataStoreWidth - // except for texImage2D if no GL_PIXEL_UNPACK_BUFFER is - // bound and _pixels_ is null. - // - // 2) texImage3D and texSubImage3D generate INVALID_OPERATION if: - // GL_UNPACK_SKIP_PIXELS + width > DataStoreWidth - // GL_UNPACK_SKIP_ROWS + height > DataStoreHeight - // except for texImage3D if no GL_PIXEL_UNPACK_BUFFER is - // bound and _pixels_ is null. - if (!pixelUnpackBuffer && !pixels && !isSubImage) - { - // Exception case for texImage2D or texImage3D, above. - } - else - { - const auto &unpack = context->getState().getUnpackState(); - GLint dataStoreWidth = unpack.rowLength ? unpack.rowLength : width; - if (unpack.skipPixels + width > dataStoreWidth) - { - ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kInvalidUnpackParametersForWebGL); - return false; - } - if (target == TextureTarget::_3D || target == TextureTarget::_2DArray) - { - // Note that CubeMapArray is not supported as a texture target in WebGL. - GLint dataStoreHeight = unpack.imageHeight ? unpack.imageHeight : height; - if (unpack.skipRows + height > dataStoreHeight) - { - ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kInvalidUnpackParametersForWebGL); - return false; - } - } - } - if (!isSubImage && !ValidateHardenedContextTextureLevelRedefine(context, entryPoint, texture, level, width, height, depth, actualFormatInfo)) diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 28d17ad..ba55695 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -1327,6 +1327,7 @@ 480069042 PIXEL10 GLES : Texture2DTestES3.DrawWithLevelZeroUndefined/* = SKIP 480069042 PIXEL10 GLES : Texture2DTestES3.DrawWithLevelsOutsideRangeUndefined/* = SKIP 480069042 PIXEL10 GLES : Texture2DTestES3.MipmapIncompleteMipmappingDisabled/* = SKIP +480069042 PIXEL10 GLES : Texture2DTestES3.TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO/* = SKIP 480069042 PIXEL10 GLES : Texture2DTestES3.TextureFormatChangesWithBaseLevel/* = SKIP 480069042 PIXEL10 GLES : Texture2DTestES3.UnpackOverlappingRowsFromUnpackBuffer/* = SKIP 480069042 PIXEL10 GLES : Texture3DTestES2.CopySubImageAlpha/* = SKIP diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index a05dea1..e657db9 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -4324,6 +4324,74 @@ EXPECT_PIXEL_COLOR_EQ(0, height - 1, GLColor::blue); } +// Test that PBO unpack respects GL_UNPACK_ROW_LENGTH when 0 < rowLength < width +// and when rowLength > subWidth for a partial-width glTexSubImage2D. +TEST_P(Texture2DTestES3, TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO) +{ + const int width = getWindowWidth(); + const int height = getWindowHeight(); + + GLTexture tex2D; + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex2D); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Pre-fill the entire texture with red so unwritten texels are deterministically detected. + std::vector<GLColor> redPixels(width * height, GLColor::red); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, + redPixels.data()); + + // 1. Unpack full texture from PBO with 0 < rowLength < width. + const int smallRowLength = width / 2; + const int smallBufferSize = (height - 1) * smallRowLength + width; + std::vector<GLColor> greenPixels(smallBufferSize, GLColor::green); + + GLBuffer pbo; + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, smallBufferSize * sizeof(GLColor), greenPixels.data(), + GL_STATIC_DRAW); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, smallRowLength); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_GL_NO_ERROR(); + + glUseProgram(mProgram); + glUniform1i(mTexture2DUniformLocation, 0); + drawQuad(mProgram, "position", 0.5f); + EXPECT_GL_NO_ERROR(); + + // Both the left half [0, width/2) and right half [width/2, width) must be green. + EXPECT_PIXEL_RECT_EQ(0, 0, width, height, GLColor::green); + + // 2. Unpack a partial-width sub-rectangle (subWidth = width / 4) with rowLength > subWidth. + const int subWidth = width / 4; + const int largeRowLength = width / 2; + const int largeBufferSize = (height - 1) * largeRowLength + subWidth; + std::vector<GLColor> bluePixels(largeBufferSize, GLColor::yellow); + for (int r = 0; r < height; ++r) + { + for (int c = 0; c < subWidth; ++c) + { + bluePixels[r * largeRowLength + c] = GLColor::blue; + } + } + glBufferData(GL_PIXEL_UNPACK_BUFFER, largeBufferSize * sizeof(GLColor), bluePixels.data(), + GL_STATIC_DRAW); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, largeRowLength); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, subWidth, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_GL_NO_ERROR(); + + drawQuad(mProgram, "position", 0.5f); + EXPECT_GL_NO_ERROR(); + + // [0, width/4) must be blue, and [width/4, width) must remain green. + EXPECT_PIXEL_RECT_EQ(0, 0, subWidth, height, GLColor::blue); + EXPECT_PIXEL_RECT_EQ(subWidth, 0, width - subWidth, height, GLColor::green); +} + // Test if the KHR debug label is set and passed to D3D correctly using glCopyTexImage2D. TEST_P(Texture2DTest, TextureKHRDebugLabelWithCopyTexImage2D) { diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp index 0c21744..f304aa2 100644 --- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp +++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp @@ -8164,6 +8164,81 @@ test(GL_TEXTURE_3D, 1, 4, 1, 4, GL_INVALID_OPERATION); // 1 + 4 > 4 } +// Tests that hardened-context unpack state validation and PBO validation are enforced on ES2 +// contexts when GL_EXT_unpack_subimage and GL_NV_pixel_buffer_object are enabled. +TEST_P(WebGL1CompatibilityTest, UnpackStateAndPBOValidation) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_unpack_subimage")); + + constexpr size_t kPixelCount = 8 * 8; + std::vector<GLColor> data(kPixelCount); + + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + ASSERT_GL_NO_ERROR(); + + auto testClientMemory = [&](GLint skipPixels, GLint rowLength, GLenum expectedError) { + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, rowLength); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, skipPixels); + + // When no PBO is bound and pixels is nullptr, glTexImage2D must succeed regardless of + // unpack parameters. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_GL_NO_ERROR(); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data()); + EXPECT_GL_ERROR(expectedError); + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data()); ... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 28d17ad..ba55695 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -1327,6 +1327,7 @@
480069042 PIXEL10 GLES : Texture2DTestES3.DrawWithLevelZeroUndefined/* = SKIP
480069042 PIXEL10 GLES : Texture2DTestES3.DrawWithLevelsOutsideRangeUndefined/* = SKIP
480069042 PIXEL10 GLES : Texture2DTestES3.MipmapIncompleteMipmappingDisabled/* = SKIP
+480069042 PIXEL10 GLES : Texture2DTestES3.TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO/* = SKIP
480069042 PIXEL10 GLES : Texture2DTestES3.TextureFormatChangesWithBaseLevel/* = SKIP
480069042 PIXEL10 GLES : Texture2DTestES3.UnpackOverlappingRowsFromUnpackBuffer/* = SKIP
480069042 PIXEL10 GLES : Texture3DTestES2.CopySubImageAlpha/* = SKIP
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index a05dea1..e657db9 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -4324,6 +4324,74 @@
EXPECT_PIXEL_COLOR_EQ(0, height - 1, GLColor::blue);
}
+// Test that PBO unpack respects GL_UNPACK_ROW_LENGTH when 0 < rowLength < width
+// and when rowLength > subWidth for a partial-width glTexSubImage2D.
+TEST_P(Texture2DTestES3, TexImageUnpackRowLengthSmallerThanWidthAndSubRectPBO)
+{
+ const int width = getWindowWidth();
+ const int height = getWindowHeight();
+
+ GLTexture tex2D;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex2D);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // Pre-fill the entire texture with red so unwritten texels are deterministically detected.
+ std::vector<GLColor> redPixels(width * height, GLColor::red);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+ redPixels.data());
+
+ // 1. Unpack full texture from PBO with 0 < rowLength < width.
+ const int smallRowLength = width / 2;
+ const int smallBufferSize = (height - 1) * smallRowLength + width;
+ std::vector<GLColor> greenPixels(smallBufferSize, GLColor::green);
+
+ GLBuffer pbo;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, smallBufferSize * sizeof(GLColor), greenPixels.data(),
+ GL_STATIC_DRAW);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, smallRowLength);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ glUseProgram(mProgram);
+ glUniform1i(mTexture2DUniformLocation, 0);
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_GL_NO_ERROR();
+
+ // Both the left half [0, width/2) and right half [width/2, width) must be green.
+ EXPECT_PIXEL_RECT_EQ(0, 0, width, height, GLColor::green);
+
+ // 2. Unpack a partial-width sub-rectangle (subWidth = width / 4) with rowLength > subWidth.
+ const int subWidth = width / 4;
+ const int largeRowLength = width / 2;
+ const int largeBufferSize = (height - 1) * largeRowLength + subWidth;
+ std::vector<GLColor> bluePixels(largeBufferSize, GLColor::yellow);
+ for (int r = 0; r < height; ++r)
+ {
+ for (int c = 0; c < subWidth; ++c)
+ {
+ bluePixels[r * largeRowLength + c] = GLColor::blue;
+ }
+ }
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, largeBufferSize * sizeof(GLColor), bluePixels.data(),
+ GL_STATIC_DRAW);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, largeRowLength);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, subWidth, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_GL_NO_ERROR();
+
+ // [0, width/4) must be blue, and [width/4, width) must remain green.
+ EXPECT_PIXEL_RECT_EQ(0, 0, subWidth, height, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(subWidth, 0, width - subWidth, height, GLColor::green);
+}
+
// Test if the KHR debug label is set and passed to D3D correctly using glCopyTexImage2D.
TEST_P(Texture2DTest, TextureKHRDebugLabelWithCopyTexImage2D)
{
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index 0c21744..f304aa2 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -8164,6 +8164,81 @@
test(GL_TEXTURE_3D, 1, 4, 1, 4, GL_INVALID_OPERATION); // 1 + 4 > 4
}
+// Tests that hardened-context unpack state validation and PBO validation are enforced on ES2
+// contexts when GL_EXT_unpack_subimage and GL_NV_pixel_buffer_object are enabled.
+TEST_P(WebGL1CompatibilityTest, UnpackStateAndPBOValidation)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_unpack_subimage"));
+
+ constexpr size_t kPixelCount = 8 * 8;
+ std::vector<GLColor> data(kPixelCount);
+
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ ASSERT_GL_NO_ERROR();
+
+ auto testClientMemory = [&](GLint skipPixels, GLint rowLength, GLenum expectedError) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, rowLength);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, skipPixels);
+
+ // When no PBO is bound and pixels is nullptr, glTexImage2D must succeed regardless of
+ // unpack parameters.
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
+ EXPECT_GL_ERROR(expectedError);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
+ EXPECT_GL_NO_ERROR();
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, rowLength);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, skipPixels);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
+ EXPECT_GL_ERROR(expectedError);
+ };
+
+ testClientMemory(0, 0, GL_NO_ERROR);
+ testClientMemory(0, 4, GL_NO_ERROR); // 0 + 4 <= 4
+ testClientMemory(1, 5, GL_NO_ERROR); // 1 + 4 <= 5
+ testClientMemory(0, 3, GL_INVALID_OPERATION); // 0 + 4 > 3
+ testClientMemory(1, 0, GL_INVALID_OPERATION); // 1 + 4 > 4
+ testClientMemory(1, 4, GL_INVALID_OPERATION); // 1 + 4 > 4
+
+ if (EnsureGLExtensionEnabled("GL_NV_pixel_buffer_object"))
+ {
+ GLBuffer pbo;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, kPixelCount * sizeof(GLColor), data.data(),
+ GL_STATIC_DRAW);
+ ASSERT_GL_NO_ERROR();
+
+ // With a bound PBO, glTexImage2D(..., nullptr) must also reject overlapping unpack rows.
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 1);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // Valid row length with PBO succeeds.
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 4);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ // Unaligned PBO offset for GL_UNSIGNED_SHORT_5_6_5 (2 bytes per datum) must fail.
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 4, 4, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
+ reinterpret_cast<const void *>(1));
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+}
+
// Tests that using cube map arrays is not supported in WebGL.
TEST_P(WebGL2CompatibilityTest, CubeMapArrayNotSupported)
{
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