From 062e7f33377a88c8747cade7fd05561017d07516 Mon Sep 17 00:00:00 2001 From: 0xASTRA Date: Fri, 29 May 2026 22:05:11 +0100 Subject: [PATCH] =?UTF-8?q?Saturate=20array-size=20product=20on=20overflow?= =?UTF-8?q?=20(integer=20overflow=20=E2=86=92=20guard=20bypass)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TType::getArraySizeProduct() and gl::ArraySizeProduct()/InnerArraySizeProduct() multiplied attacker-controlled per-dimension array sizes as plain unsigned int with no overflow handling. A WebGL shader declaring an array whose dimension product exceeds UINT_MAX (e.g. float x[65536][65536], product = 2^32 ≡ 0) caused these functions to return a wrapped, near-zero value. That wrapped value is consumed by security-relevant arithmetic, notably CalculateVariableSize() which feeds TParseContext::checkVariableSize() — the WebGL oversize-variable guard (rejectWebglShadersWithLargeVariables). The CheckedNumeric there cannot detect that the unsigned int operand already wrapped, so the oversize variable passes the guard. VariablePacker's packing limit check is bypassed the same way. Saturate to UINT_MAX on overflow, mirroring the existing saturating behavior of TType::getObjectSize() and getLocationCount(), and the CheckedNumeric hardening recently added to TIntermAggregate::getConstantValue() (crbug.com/498400132). --- src/common/utilities.cpp | 16 +++++++++++++++- src/compiler/translator/Types.cpp | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/common/utilities.cpp b/src/common/utilities.cpp index d1dc4c68fb1..2d6ee682fb5 100644 --- a/src/common/utilities.cpp +++ b/src/common/utilities.cpp @@ -16,6 +16,7 @@ #include "common/platform.h" #include "common/string_utils.h" +#include #include #if defined(ANGLE_ENABLE_WINDOWS_UWP) @@ -976,9 +977,15 @@ bool SamplerNameContainsNonZeroArrayElement(const std::string &name) unsigned int ArraySizeProduct(const std::vector &arraySizes) { + // Saturate on overflow; a wrapped (small) product defeats downstream size/limit checks. unsigned int arraySizeProduct = 1u; for (unsigned int arraySize : arraySizes) { + if (arraySize != 0u && + arraySizeProduct > std::numeric_limits::max() / arraySize) + { + return std::numeric_limits::max(); + } arraySizeProduct *= arraySize; } return arraySizeProduct; @@ -986,10 +993,17 @@ unsigned int ArraySizeProduct(const std::vector &arraySizes) unsigned int InnerArraySizeProduct(const std::vector &arraySizes) { + // Saturate on overflow; a wrapped (small) product defeats downstream size/limit checks. unsigned int arraySizeProduct = 1u; for (size_t index = 0; index + 1 < arraySizes.size(); ++index) { - arraySizeProduct *= arraySizes[index]; + const unsigned int arraySize = arraySizes[index]; + if (arraySize != 0u && + arraySizeProduct > std::numeric_limits::max() / arraySize) + { + return std::numeric_limits::max(); + } + arraySizeProduct *= arraySize; } return arraySizeProduct; } diff --git a/src/compiler/translator/Types.cpp b/src/compiler/translator/Types.cpp index 42ccdcc6861..81e0205e483 100644 --- a/src/compiler/translator/Types.cpp +++ b/src/compiler/translator/Types.cpp @@ -559,10 +559,20 @@ int TType::getLocationCount() const unsigned int TType::getArraySizeProduct() const { + // Saturate on overflow instead of silently wrapping. A wrapped (small) product would + // defeat downstream size/limit checks that treat this value as the element count of the + // type (e.g. CalculateVariableSize() feeding TParseContext::checkVariableSize(), and the + // packing limit in VariablePacker), leading to under-sized allocations / out-of-bounds + // access for attacker-controlled array dimensions such as float x[65536][65536]. + // Mirrors the saturating behavior already used by getObjectSize()/getLocationCount(). unsigned int product = 1u; for (unsigned int arraySize : mArraySizes) { + if (arraySize != 0u && product > std::numeric_limits::max() / arraySize) + { + return std::numeric_limits::max(); + } product *= arraySize; } return product;