Division by zero in Tensorflow
Description
TensorFlow's convolution cost estimator performs division by zero when stride is zero, causing a crash or undefined behavior; fixed in versions 2.8.0, 2.7.1, 2.6.3, 2.5.3.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
TensorFlow's convolution cost estimator performs division by zero when stride is zero, causing a crash or undefined behavior; fixed in versions 2.8.0, 2.7.1, 2.6.3, 2.5.3.
Vulnerability
The cost estimator for convolution operations in TensorFlow, located in tensorflow/core/grappler/costs/op_level_cost_estimator.cc, fails to validate that the stride argument is strictly positive [1][2]. When a stride value of zero is provided, the function executes a division by zero, leading to undefined behavior or a crash. This affects all TensorFlow versions prior to the fix, including 2.5.x, 2.6.x, and 2.7.x, and will be addressed in TensorFlow 2.8.0, with cherry-picks for 2.7.1, 2.6.3, and 2.5.3 [1][4].
Exploitation
An attacker needs the ability to supply a zero stride value to a convolution operation that is processed by the cost estimator. This can be achieved by crafting a TensorFlow model or graph that includes a convolution with strides=0 and then triggering the estimator (e.g., via grappler passes). No authentication or special network position is required if the attacker can control the model input. The code path is reachable through the grappler cost estimation functionality, which is used during graph optimization [2].
Impact
Successful exploitation results in a division by zero, which can cause a denial-of-service (crash) or undefined behavior in the TensorFlow process. The impact is limited to computational errors or process termination; there is no evidence of remote code execution or information disclosure based on the available references [1].
Mitigation
The vulnerability is fixed in TensorFlow 2.8.0. Cherry-pick commits are provided for TensorFlow 2.7.1, 2.6.3, and 2.5.3 [1][4]. Users should upgrade to one of these patched versions. No workaround is available for unpatched versions, and the vulnerability is not currently listed in the CISA KEV [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tensorflowPyPI | < 2.5.3 | 2.5.3 |
tensorflowPyPI | >= 2.6.0, < 2.6.3 | 2.6.3 |
tensorflowPyPI | >= 2.7.0, < 2.7.1 | 2.7.1 |
tensorflow-cpuPyPI | < 2.5.3 | 2.5.3 |
tensorflow-cpuPyPI | >= 2.6.0, < 2.6.3 | 2.6.3 |
tensorflow-cpuPyPI | >= 2.7.0, < 2.7.1 | 2.7.1 |
tensorflow-gpuPyPI | < 2.5.3 | 2.5.3 |
tensorflow-gpuPyPI | >= 2.6.0, < 2.6.3 | 2.6.3 |
tensorflow-gpuPyPI | >= 2.7.0, < 2.7.1 | 2.7.1 |
Affected products
5- osv-coords4 versions
< 2.5.3+ 3 more
- (no CPE)range: < 2.5.3
- (no CPE)range: < 2.5.3
- (no CPE)range: < 2.5.3
- (no CPE)range: < 2.5.3
Patches
13218043d6d3aInternal change
4 files changed · +83 −17
tensorflow/core/grappler/costs/BUILD+1 −0 modified@@ -355,6 +355,7 @@ tf_cc_test( "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", + "//tensorflow/core/platform:status_matchers", ], )
tensorflow/core/grappler/costs/op_level_cost_estimator.cc+24 −13 modified@@ -2153,7 +2153,7 @@ OpInfo::TensorProperties OpLevelCostEstimator::DescribeTensor( } /* static */ -OpLevelCostEstimator::ConvolutionDimensions +StatusOr<OpLevelCostEstimator::ConvolutionDimensions> OpLevelCostEstimator::OpDimensionsFromInputs( const TensorShapeProto& original_image_shape, const OpInfo& op_info, bool* found_unknown_shapes) { @@ -2190,6 +2190,11 @@ OpLevelCostEstimator::OpDimensionsFromInputs( std::vector<int64_t> strides = GetStrides(op_info); int64_t sx = strides[x_index]; int64_t sy = strides[y_index]; + if (sx == 0 || sy == 0) { + return errors::InvalidArgument( + "Stride must be > 0 for Height and Width, but got (", sy, ", ", sx, + ")"); + } const auto padding = GetPadding(op_info); int64_t ox = GetOutputSize(ix, kx, sx, padding); @@ -2206,8 +2211,9 @@ Status OpLevelCostEstimator::PredictMaxPool(const OpContext& op_context, bool found_unknown_shapes = false; const auto& op_info = op_context.op_info; // x: op_info.inputs(0) - ConvolutionDimensions dims = OpDimensionsFromInputs( - op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN(ConvolutionDimensions dims, + OpDimensionsFromInputs(op_info.inputs(0).shape(), op_info, + &found_unknown_shapes)); // kx * ky - 1 comparisons per output (kx * xy > 1) // or 1 copy per output (kx * k1 = 1). int per_output_ops = dims.kx * dims.ky == 1 ? 1 : dims.kx * dims.ky - 1; @@ -2248,8 +2254,9 @@ Status OpLevelCostEstimator::PredictMaxPoolGrad(const OpContext& op_context, op_info.ShortDebugString()); } - ConvolutionDimensions dims = OpDimensionsFromInputs( - op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN(ConvolutionDimensions dims, + OpDimensionsFromInputs(op_info.inputs(0).shape(), op_info, + &found_unknown_shapes)); int64_t ops = 0; if (dims.kx == 1 && dims.ky == 1) { @@ -2324,8 +2331,9 @@ Status OpLevelCostEstimator::PredictAvgPool(const OpContext& op_context, bool found_unknown_shapes = false; const auto& op_info = op_context.op_info; // x: op_info.inputs(0) - ConvolutionDimensions dims = OpDimensionsFromInputs( - op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN(ConvolutionDimensions dims, + OpDimensionsFromInputs(op_info.inputs(0).shape(), op_info, + &found_unknown_shapes)); // kx * ky - 1 additions and 1 multiplication per output. int64_t ops = dims.batch * dims.ox * dims.oy * dims.oz * dims.kx * dims.ky; @@ -2382,8 +2390,9 @@ Status OpLevelCostEstimator::PredictAvgPoolGrad(const OpContext& op_context, found_unknown_shapes = true; } - ConvolutionDimensions dims = - OpDimensionsFromInputs(x_shape, op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN( + ConvolutionDimensions dims, + OpDimensionsFromInputs(x_shape, op_info, &found_unknown_shapes)); int64_t ops = 0; if (dims.kx <= dims.sx && dims.ky <= dims.sy) { @@ -2409,8 +2418,9 @@ Status OpLevelCostEstimator::PredictFusedBatchNorm( // offset: op_info.inputs(2) // mean: op_info.inputs(3) --> only for inference // variance: op_info.inputs(4) --> only for inference - ConvolutionDimensions dims = OpDimensionsFromInputs( - op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN(ConvolutionDimensions dims, + OpDimensionsFromInputs(op_info.inputs(0).shape(), op_info, + &found_unknown_shapes)); const bool is_training = IsTraining(op_info); int64_t ops = 0; @@ -2459,8 +2469,9 @@ Status OpLevelCostEstimator::PredictFusedBatchNormGrad( // scale: op_info.inputs(2) // mean: op_info.inputs(3) // variance or inverse of variance: op_info.inputs(4) - ConvolutionDimensions dims = OpDimensionsFromInputs( - op_info.inputs(1).shape(), op_info, &found_unknown_shapes); + TF_ASSIGN_OR_RETURN(ConvolutionDimensions dims, + OpDimensionsFromInputs(op_info.inputs(1).shape(), op_info, + &found_unknown_shapes)); int64_t ops = 0; const auto rsqrt_cost = Eigen::internal::functor_traits<
tensorflow/core/grappler/costs/op_level_cost_estimator.h+1 −1 modified@@ -290,7 +290,7 @@ class OpLevelCostEstimator { bool* found_unknown_shapes); // For Pooling, FusedBatchNorm, and their grad ops. - static ConvolutionDimensions OpDimensionsFromInputs( + static StatusOr<ConvolutionDimensions> OpDimensionsFromInputs( const TensorShapeProto& original_image_shape, const OpInfo& op_info, bool* found_unknown_shapes);
tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc+57 −3 modified@@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/framework/types.h" +#include "tensorflow/core/platform/status_matchers.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/protobuf/device_properties.pb.h" @@ -558,9 +559,10 @@ class OpLevelCostEstimatorTest : public ::testing::Test { } bool found_unknown_shapes; - auto dims = OpLevelCostEstimator::OpDimensionsFromInputs( - op_context.op_info.inputs(0).shape(), op_context.op_info, - &found_unknown_shapes); + TF_ASSERT_OK_AND_ASSIGN( + auto dims, OpLevelCostEstimator::OpDimensionsFromInputs( + op_context.op_info.inputs(0).shape(), op_context.op_info, + &found_unknown_shapes)); Padding padding_enum; if (padding == "VALID") { padding_enum = Padding::VALID; @@ -581,6 +583,38 @@ class OpLevelCostEstimatorTest : public ::testing::Test { EXPECT_EQ(padding_enum, dims.padding); } + StatusOr<OpLevelCostEstimator::ConvolutionDimensions> + CallOpDimensionsFromInputs(const int n, const int h, const int w, const int c, + const int kx, const int ky, const int sx, + const int sy, const string& data_format, + const string& padding) { + OpContext op_context; + + const std::vector<int> x = {n, h, w, c}; + const std::vector<int> ksize = {1, kx, ky, 1}; + std::vector<int> strides; + if (data_format == "NHWC") { + strides = {1, sy, sx, 1}; + } else { + strides = {1, 1, sy, sx}; + } + + auto& op_info = op_context.op_info; + SetCpuDevice(&op_info); + op_info.set_op("MaxPool"); + + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_inputs()); + auto* attr = op_info.mutable_attr(); + SetAttrValue(data_format, &(*attr)["data_format"]); + SetAttrValue(padding, &(*attr)["padding"]); + SetAttrValue(strides, &(*attr)["strides"]); + SetAttrValue(ksize, &(*attr)["ksize"]); + bool found_unknown_shapes; + return OpLevelCostEstimator::OpDimensionsFromInputs( + op_context.op_info.inputs(0).shape(), op_context.op_info, + &found_unknown_shapes); + } + OpLevelCostEstimator estimator_; }; @@ -1383,6 +1417,26 @@ TEST_F(OpLevelCostEstimatorTest, OpDimensionsFromInputs) { } } +TEST_F(OpLevelCostEstimatorTest, OpDimensionsFromInputsError) { + std::vector<string> paddings = {"VALID", "SAME"}; + std::vector<string> formats = {"NHWC", "NCHW"}; + for (const auto& p : paddings) { + for (const auto& f : formats) { + // n, h, w, c, kx, ky, sx, sy, data_format, padding. + ASSERT_THAT( + CallOpDimensionsFromInputs(10, 14, 14, 3840, 3, 3, 0, 2, f, p), + testing::StatusIs( + error::INVALID_ARGUMENT, + "Stride must be > 0 for Height and Width, but got (2, 0)")); + ASSERT_THAT( + CallOpDimensionsFromInputs(10, 14, 14, 3840, 3, 3, 2, 0, f, p), + testing::StatusIs( + error::INVALID_ARGUMENT, + "Stride must be > 0 for Height and Width, but got (0, 2)")); + } + } +} + TEST_F(OpLevelCostEstimatorTest, PredictMaxPool) { auto predict_max_pool = [this](const int n, const int in, const int c, const int k, const int s,
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-v3f7-j968-4h5fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-21725ghsaADVISORY
- github.com/pypa/advisory-database/tree/main/vulns/tensorflow-cpu/PYSEC-2022-49.yamlghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/tensorflow-gpu/PYSEC-2022-104.yamlghsaWEB
- github.com/tensorflow/tensorflow/blob/ffa202a17ab7a4a10182b746d230ea66f021fe16/tensorflow/core/grappler/costs/op_level_cost_estimator.ccghsax_refsource_MISCWEB
- github.com/tensorflow/tensorflow/commit/3218043d6d3a019756607643cf65574fbfef5d7aghsax_refsource_MISCWEB
- github.com/tensorflow/tensorflow/security/advisories/GHSA-v3f7-j968-4h5fghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.